ADD new frontend
This commit is contained in:
340
frontend/src/router/AppRouter.tsx
Normal file
340
frontend/src/router/AppRouter.tsx
Normal file
@@ -0,0 +1,340 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { ProtectedRoute } from './ProtectedRoute';
|
||||
import { LoadingSpinner } from '../components/shared/LoadingSpinner';
|
||||
import { AppShell } from '../components/layout';
|
||||
|
||||
// Lazy load the pages we actually have
|
||||
const LandingPage = React.lazy(() => import('../pages/public/LandingPage'));
|
||||
const LoginPage = React.lazy(() => import('../pages/public/LoginPage'));
|
||||
const RegisterPage = React.lazy(() => import('../pages/public/RegisterPage'));
|
||||
const DashboardPage = React.lazy(() => import('../pages/app/DashboardPage'));
|
||||
|
||||
// Operations pages
|
||||
const InventoryPage = React.lazy(() => import('../pages/app/operations/inventory/InventoryPage'));
|
||||
const ProductionPage = React.lazy(() => import('../pages/app/operations/production/ProductionPage'));
|
||||
const RecipesPage = React.lazy(() => import('../pages/app/operations/recipes/RecipesPage'));
|
||||
const ProcurementPage = React.lazy(() => import('../pages/app/operations/procurement/ProcurementPage'));
|
||||
const OrdersPage = React.lazy(() => import('../pages/app/operations/orders/OrdersPage'));
|
||||
const POSPage = React.lazy(() => import('../pages/app/operations/pos/POSPage'));
|
||||
|
||||
// Analytics pages
|
||||
const ForecastingPage = React.lazy(() => import('../pages/app/analytics/forecasting/ForecastingPage'));
|
||||
const SalesAnalyticsPage = React.lazy(() => import('../pages/app/analytics/sales-analytics/SalesAnalyticsPage'));
|
||||
const AIInsightsPage = React.lazy(() => import('../pages/app/analytics/ai-insights/AIInsightsPage'));
|
||||
const PerformanceAnalyticsPage = React.lazy(() => import('../pages/app/analytics/performance/PerformanceAnalyticsPage'));
|
||||
|
||||
// Communications pages
|
||||
const AlertsPage = React.lazy(() => import('../pages/app/communications/alerts/AlertsPage'));
|
||||
const NotificationsPage = React.lazy(() => import('../pages/app/communications/notifications/NotificationsPage'));
|
||||
const PreferencesPage = React.lazy(() => import('../pages/app/communications/preferences/PreferencesPage'));
|
||||
|
||||
// Settings pages
|
||||
const BakeryConfigPage = React.lazy(() => import('../pages/app/settings/bakery-config/BakeryConfigPage'));
|
||||
const SystemSettingsPage = React.lazy(() => import('../pages/app/settings/system/SystemSettingsPage'));
|
||||
const TeamPage = React.lazy(() => import('../pages/app/settings/team/TeamPage'));
|
||||
const TrainingPage = React.lazy(() => import('../pages/app/settings/training/TrainingPage'));
|
||||
|
||||
// Data pages
|
||||
const WeatherPage = React.lazy(() => import('../pages/app/data/weather/WeatherPage'));
|
||||
const TrafficPage = React.lazy(() => import('../pages/app/data/traffic/TrafficPage'));
|
||||
const EventsPage = React.lazy(() => import('../pages/app/data/events/EventsPage'));
|
||||
|
||||
// Onboarding pages
|
||||
const OnboardingSetupPage = React.lazy(() => import('../pages/app/onboarding/setup/OnboardingSetupPage'));
|
||||
const OnboardingUploadPage = React.lazy(() => import('../pages/app/onboarding/upload/OnboardingUploadPage'));
|
||||
const OnboardingAnalysisPage = React.lazy(() => import('../pages/app/onboarding/analysis/OnboardingAnalysisPage'));
|
||||
const OnboardingReviewPage = React.lazy(() => import('../pages/app/onboarding/review/OnboardingReviewPage'));
|
||||
|
||||
export const AppRouter: React.FC = () => {
|
||||
return (
|
||||
<Suspense fallback={<LoadingSpinner overlay text="Cargando aplicación..." />}>
|
||||
<Routes>
|
||||
{/* Public Routes */}
|
||||
<Route path="/" element={<LandingPage />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/register" element={<RegisterPage />} />
|
||||
|
||||
{/* Protected Routes with AppShell Layout */}
|
||||
<Route
|
||||
path="/app"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<DashboardPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/dashboard"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<DashboardPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Operations Routes */}
|
||||
<Route
|
||||
path="/app/operations/inventory"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<InventoryPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/operations/production"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<ProductionPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/operations/recipes"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<RecipesPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/operations/procurement"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<ProcurementPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/operations/orders"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<OrdersPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/operations/pos"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<POSPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Analytics Routes */}
|
||||
<Route
|
||||
path="/app/analytics/forecasting"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<ForecastingPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/analytics/sales"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<SalesAnalyticsPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/analytics/ai-insights"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<AIInsightsPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/analytics/performance"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<PerformanceAnalyticsPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Communications Routes */}
|
||||
<Route
|
||||
path="/app/communications/alerts"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<AlertsPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/communications/notifications"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<NotificationsPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/communications/preferences"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<PreferencesPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Settings Routes */}
|
||||
<Route
|
||||
path="/app/settings/bakery-config"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<BakeryConfigPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/settings/system"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<SystemSettingsPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/settings/team"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<TeamPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/settings/training"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<TrainingPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Data Routes */}
|
||||
<Route
|
||||
path="/app/data/weather"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<WeatherPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/data/traffic"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<TrafficPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/data/events"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<EventsPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Onboarding Routes */}
|
||||
<Route
|
||||
path="/app/onboarding/setup"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<OnboardingSetupPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/onboarding/upload"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<OnboardingUploadPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/onboarding/analysis"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<OnboardingAnalysisPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/app/onboarding/review"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AppShell>
|
||||
<OnboardingReviewPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Default redirects */}
|
||||
<Route path="/app/*" element={<Navigate to="/app/dashboard" replace />} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppRouter;
|
||||
307
frontend/src/router/ProtectedRoute.tsx
Normal file
307
frontend/src/router/ProtectedRoute.tsx
Normal file
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* Protected Route component for handling authentication and authorization
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Navigate, useLocation } from 'react-router-dom';
|
||||
import { useAuthUser, useIsAuthenticated, useAuthLoading } from '../stores';
|
||||
import { RouteConfig, canAccessRoute, ROUTES } from './routes.config';
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
children: React.ReactNode;
|
||||
route?: RouteConfig;
|
||||
fallback?: React.ReactNode;
|
||||
redirectTo?: string;
|
||||
}
|
||||
|
||||
interface LoadingSpinnerProps {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({ message = 'Cargando...' }) => (
|
||||
<div className="flex items-center justify-center min-h-screen bg-bg-primary">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="spinner spinner-lg"></div>
|
||||
<p className="text-text-secondary text-sm">{message}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const UnauthorizedPage: React.FC = () => (
|
||||
<div className="flex items-center justify-center min-h-screen bg-bg-primary">
|
||||
<div className="text-center max-w-md mx-auto px-6">
|
||||
<div className="mb-6">
|
||||
<div className="w-16 h-16 mx-auto mb-4 bg-color-error rounded-full flex items-center justify-center">
|
||||
<svg
|
||||
className="w-8 h-8 text-text-inverse"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.732 19.5c-.77.833.192 2.5 1.732 2.5z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-text-primary mb-2">
|
||||
Acceso no autorizado
|
||||
</h1>
|
||||
<p className="text-text-secondary mb-6">
|
||||
No tienes permisos para acceder a esta página. Contacta con tu administrador si crees que esto es un error.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
onClick={() => window.history.back()}
|
||||
className="btn btn-primary"
|
||||
>
|
||||
Volver atrás
|
||||
</button>
|
||||
<button
|
||||
onClick={() => window.location.href = ROUTES.DASHBOARD}
|
||||
className="btn btn-outline"
|
||||
>
|
||||
Ir al Panel de Control
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ForbiddenPage: React.FC = () => (
|
||||
<div className="flex items-center justify-center min-h-screen bg-bg-primary">
|
||||
<div className="text-center max-w-md mx-auto px-6">
|
||||
<div className="mb-6">
|
||||
<div className="w-16 h-16 mx-auto mb-4 bg-color-warning rounded-full flex items-center justify-center">
|
||||
<svg
|
||||
className="w-8 h-8 text-text-inverse"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L5.636 5.636"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-text-primary mb-2">
|
||||
Acceso restringido
|
||||
</h1>
|
||||
<p className="text-text-secondary mb-6">
|
||||
Tu cuenta no tiene los permisos necesarios para ver esta sección. Contacta con tu administrador para solicitar acceso.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
onClick={() => window.history.back()}
|
||||
className="btn btn-primary"
|
||||
>
|
||||
Volver atrás
|
||||
</button>
|
||||
<button
|
||||
onClick={() => window.location.href = ROUTES.DASHBOARD}
|
||||
className="btn btn-outline"
|
||||
>
|
||||
Ir al Panel de Control
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
|
||||
children,
|
||||
route,
|
||||
fallback,
|
||||
redirectTo,
|
||||
}) => {
|
||||
const user = useAuthUser();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const isLoading = useAuthLoading();
|
||||
const location = useLocation();
|
||||
|
||||
// Show loading spinner while checking authentication
|
||||
if (isLoading) {
|
||||
return fallback || <LoadingSpinner message="Verificando autenticación..." />;
|
||||
}
|
||||
|
||||
// If route doesn't require auth, render children
|
||||
if (route && !route.requiresAuth) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
// If not authenticated and route requires auth, redirect to login
|
||||
if (!isAuthenticated) {
|
||||
const redirectPath = redirectTo || ROUTES.LOGIN;
|
||||
const returnUrl = location.pathname + location.search;
|
||||
|
||||
return (
|
||||
<Navigate
|
||||
to={`${redirectPath}?returnUrl=${encodeURIComponent(returnUrl)}`}
|
||||
replace
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// If no route config is provided, just check authentication
|
||||
if (!route) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
// Get user roles and permissions
|
||||
const userRoles = user?.role ? [user.role] : [];
|
||||
const userPermissions: string[] = [];
|
||||
|
||||
// Check if user can access this route
|
||||
const canAccess = canAccessRoute(route, isAuthenticated, userRoles, userPermissions);
|
||||
|
||||
if (!canAccess) {
|
||||
// Check if it's a permission issue or role issue
|
||||
const hasRequiredRoles = !route.requiredRoles ||
|
||||
route.requiredRoles.some(role => userRoles.includes(role));
|
||||
|
||||
if (!hasRequiredRoles) {
|
||||
return <UnauthorizedPage />;
|
||||
} else {
|
||||
return <ForbiddenPage />;
|
||||
}
|
||||
}
|
||||
|
||||
// User has access, render the protected content
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
// Higher-order component for route protection
|
||||
export const withProtectedRoute = <P extends object>(
|
||||
Component: React.ComponentType<P>,
|
||||
route: RouteConfig,
|
||||
options?: {
|
||||
fallback?: React.ReactNode;
|
||||
redirectTo?: string;
|
||||
}
|
||||
) => {
|
||||
const ProtectedComponent = (props: P) => (
|
||||
<ProtectedRoute
|
||||
route={route}
|
||||
fallback={options?.fallback}
|
||||
redirectTo={options?.redirectTo}
|
||||
>
|
||||
<Component {...props} />
|
||||
</ProtectedRoute>
|
||||
);
|
||||
|
||||
ProtectedComponent.displayName = `withProtectedRoute(${Component.displayName || Component.name})`;
|
||||
|
||||
return ProtectedComponent;
|
||||
};
|
||||
|
||||
// Hook for checking route access
|
||||
export const useRouteAccess = (route: RouteConfig) => {
|
||||
const user = useAuthUser();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const isLoading = useAuthLoading();
|
||||
|
||||
const userRoles = user?.role ? [user.role] : [];
|
||||
const userPermissions: string[] = [];
|
||||
|
||||
const canAccess = canAccessRoute(route, isAuthenticated, userRoles, userPermissions);
|
||||
|
||||
return {
|
||||
canAccess,
|
||||
isLoading,
|
||||
isAuthenticated,
|
||||
userRoles,
|
||||
userPermissions,
|
||||
hasRequiredRoles: !route.requiredRoles ||
|
||||
route.requiredRoles.some(role => userRoles.includes(role)),
|
||||
hasRequiredPermissions: !route.requiredPermissions ||
|
||||
route.requiredPermissions.every(permission => userPermissions.includes(permission)),
|
||||
};
|
||||
};
|
||||
|
||||
// Component for conditionally rendering content based on permissions
|
||||
interface ConditionalRenderProps {
|
||||
children: React.ReactNode;
|
||||
requiredPermissions?: string[];
|
||||
requiredRoles?: string[];
|
||||
requireAll?: boolean; // If true, requires ALL permissions/roles, otherwise ANY
|
||||
fallback?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ConditionalRender: React.FC<ConditionalRenderProps> = ({
|
||||
children,
|
||||
requiredPermissions = [],
|
||||
requiredRoles = [],
|
||||
requireAll = true,
|
||||
fallback = null,
|
||||
}) => {
|
||||
const user = useAuthUser();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (!isAuthenticated || !user) {
|
||||
return <>{fallback}</>;
|
||||
}
|
||||
|
||||
const userRoles = user.role ? [user.role] : [];
|
||||
const userPermissions: string[] = [];
|
||||
|
||||
// Check roles
|
||||
let hasRoles = true;
|
||||
if (requiredRoles.length > 0) {
|
||||
if (requireAll) {
|
||||
hasRoles = requiredRoles.every(role => userRoles.includes(role));
|
||||
} else {
|
||||
hasRoles = requiredRoles.some(role => userRoles.includes(role));
|
||||
}
|
||||
}
|
||||
|
||||
// Check permissions
|
||||
let hasPermissions = true;
|
||||
if (requiredPermissions.length > 0) {
|
||||
if (requireAll) {
|
||||
hasPermissions = requiredPermissions.every(permission => userPermissions.includes(permission));
|
||||
} else {
|
||||
hasPermissions = requiredPermissions.some(permission => userPermissions.includes(permission));
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRoles && hasPermissions) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return <>{fallback}</>;
|
||||
};
|
||||
|
||||
// Route guard for admin-only routes
|
||||
export const AdminRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<ConditionalRender
|
||||
requiredRoles={['admin', 'super_admin']}
|
||||
requireAll={false}
|
||||
fallback={<UnauthorizedPage />}
|
||||
>
|
||||
{children}
|
||||
</ConditionalRender>
|
||||
);
|
||||
};
|
||||
|
||||
// Route guard for manager-level routes
|
||||
export const ManagerRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<ConditionalRender
|
||||
requiredRoles={['admin', 'super_admin', 'manager']}
|
||||
requireAll={false}
|
||||
fallback={<UnauthorizedPage />}
|
||||
>
|
||||
{children}
|
||||
</ConditionalRender>
|
||||
);
|
||||
};
|
||||
22
frontend/src/router/index.ts
Normal file
22
frontend/src/router/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export { AppRouter, default as Router } from './AppRouter';
|
||||
export { ProtectedRoute } from './ProtectedRoute';
|
||||
export {
|
||||
ROUTES,
|
||||
ROUTE_CONFIGS,
|
||||
canAccessRoute,
|
||||
getRouteByPath,
|
||||
getRoutesForRole,
|
||||
getRoutesWithPermission,
|
||||
isPublicRoute,
|
||||
isProtectedRoute,
|
||||
type RouteConfig
|
||||
} from './routes.config';
|
||||
|
||||
// Additional utility exports for route components
|
||||
export {
|
||||
ConditionalRender,
|
||||
AdminRoute,
|
||||
ManagerRoute,
|
||||
useRouteAccess,
|
||||
withProtectedRoute
|
||||
} from './ProtectedRoute';
|
||||
@@ -1,320 +0,0 @@
|
||||
import React from 'react';
|
||||
import { createBrowserRouter, Navigate } from 'react-router-dom';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
// Layout components
|
||||
import Layout from '../components/layout/Layout';
|
||||
import AuthLayout from '../components/layout/AuthLayout';
|
||||
|
||||
// Pages
|
||||
import LandingPage from '../pages/landing/LandingPage';
|
||||
import LoginPage from '../pages/auth/LoginPage';
|
||||
import RegisterPage from '../pages/auth/SimpleRegisterPage';
|
||||
import OnboardingPage from '../pages/onboarding/OnboardingPage';
|
||||
import DashboardPage from '../pages/dashboard/DashboardPage';
|
||||
|
||||
// Operations Hub Pages
|
||||
import OperationsLayout from '../components/layout/OperationsLayout';
|
||||
import ProductionPage from '../pages/production/ProductionPage';
|
||||
import OrdersPage from '../pages/orders/OrdersPage';
|
||||
import InventoryPage from '../pages/inventory/InventoryPage';
|
||||
import SalesPage from '../pages/sales/SalesPage';
|
||||
import RecipesPage from '../pages/recipes/RecipesPage';
|
||||
import POSPage from '../pages/pos/POSPage';
|
||||
|
||||
// Analytics Hub Pages
|
||||
import AnalyticsLayout from '../components/layout/AnalyticsLayout';
|
||||
import ForecastPage from '../pages/forecast/ForecastPage';
|
||||
import SalesAnalyticsPage from '../pages/analytics/SalesAnalyticsPage';
|
||||
import ProductionReportsPage from '../pages/analytics/ProductionReportsPage';
|
||||
import FinancialReportsPage from '../pages/analytics/FinancialReportsPage';
|
||||
import PerformanceKPIsPage from '../pages/analytics/PerformanceKPIsPage';
|
||||
import AIInsightsPage from '../pages/analytics/AIInsightsPage';
|
||||
|
||||
// Settings Pages
|
||||
import SettingsLayout from '../components/layout/SettingsLayout';
|
||||
import SettingsPage from '../pages/settings/SettingsPage';
|
||||
import UsersManagementPage from '../pages/settings/UsersManagementPage';
|
||||
import BakeriesManagementPage from '../pages/settings/BakeriesManagementPage';
|
||||
import GeneralSettingsPage from '../pages/settings/GeneralSettingsPage';
|
||||
import AccountSettingsPage from '../pages/settings/AccountSettingsPage';
|
||||
|
||||
// Route Guards
|
||||
import ProtectedRoute from '../components/auth/ProtectedRoute';
|
||||
import RoleBasedRoute from '../components/auth/RoleBasedRoute';
|
||||
|
||||
// Add RootState import for type checking
|
||||
import type { RootState } from '../store';
|
||||
|
||||
export const router = createBrowserRouter([
|
||||
// Public Routes
|
||||
{
|
||||
path: '/',
|
||||
element: <AuthLayout />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <LandingPage />
|
||||
},
|
||||
{
|
||||
path: 'login',
|
||||
element: <LoginPage />
|
||||
},
|
||||
{
|
||||
path: 'register',
|
||||
element: <RegisterPage />
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Protected Routes
|
||||
{
|
||||
path: '/app',
|
||||
element: (
|
||||
<ProtectedRoute>
|
||||
<Layout />
|
||||
</ProtectedRoute>
|
||||
),
|
||||
children: [
|
||||
// Dashboard
|
||||
{
|
||||
index: true,
|
||||
element: <Navigate to="/app/dashboard" replace />
|
||||
},
|
||||
{
|
||||
path: 'dashboard',
|
||||
element: <DashboardPage />
|
||||
},
|
||||
|
||||
// Onboarding (conditional)
|
||||
{
|
||||
path: 'onboarding',
|
||||
element: <OnboardingPage />
|
||||
},
|
||||
|
||||
// Operations Hub
|
||||
{
|
||||
path: 'operations',
|
||||
element: <OperationsLayout />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Navigate to="/app/operations/production" replace />
|
||||
},
|
||||
{
|
||||
path: 'production',
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <ProductionPage />
|
||||
},
|
||||
{
|
||||
path: 'schedule',
|
||||
element: <ProductionPage view="schedule" />
|
||||
},
|
||||
{
|
||||
path: 'active-batches',
|
||||
element: <ProductionPage view="active-batches" />
|
||||
},
|
||||
{
|
||||
path: 'equipment',
|
||||
element: <ProductionPage view="equipment" />
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'orders',
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <OrdersPage />
|
||||
},
|
||||
{
|
||||
path: 'incoming',
|
||||
element: <OrdersPage view="incoming" />
|
||||
},
|
||||
{
|
||||
path: 'in-progress',
|
||||
element: <OrdersPage view="in-progress" />
|
||||
},
|
||||
{
|
||||
path: 'supplier-orders',
|
||||
element: <OrdersPage view="supplier-orders" />
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'inventory',
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <InventoryPage />
|
||||
},
|
||||
{
|
||||
path: 'stock-levels',
|
||||
element: <InventoryPage view="stock-levels" />
|
||||
},
|
||||
{
|
||||
path: 'movements',
|
||||
element: <InventoryPage view="movements" />
|
||||
},
|
||||
{
|
||||
path: 'alerts',
|
||||
element: <InventoryPage view="alerts" />
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'sales',
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <SalesPage />
|
||||
},
|
||||
{
|
||||
path: 'daily-sales',
|
||||
element: <SalesPage view="daily-sales" />
|
||||
},
|
||||
{
|
||||
path: 'customer-orders',
|
||||
element: <SalesPage view="customer-orders" />
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'pos',
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <POSPage />
|
||||
},
|
||||
{
|
||||
path: 'integrations',
|
||||
element: <POSPage view="integrations" />
|
||||
},
|
||||
{
|
||||
path: 'sync-status',
|
||||
element: <POSPage view="sync-status" />
|
||||
},
|
||||
{
|
||||
path: 'transactions',
|
||||
element: <POSPage view="transactions" />
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'recipes',
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <RecipesPage />
|
||||
},
|
||||
{
|
||||
path: 'active-recipes',
|
||||
element: <RecipesPage view="active-recipes" />
|
||||
},
|
||||
{
|
||||
path: 'development',
|
||||
element: (
|
||||
<RoleBasedRoute requiredRoles={['admin', 'manager']}>
|
||||
<RecipesPage view="development" />
|
||||
</RoleBasedRoute>
|
||||
)
|
||||
},
|
||||
{
|
||||
path: 'costing',
|
||||
element: (
|
||||
<RoleBasedRoute requiredRoles={['admin', 'manager']}>
|
||||
<RecipesPage view="costing" />
|
||||
</RoleBasedRoute>
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Analytics Hub (Admin/Manager only)
|
||||
{
|
||||
path: 'analytics',
|
||||
element: (
|
||||
<RoleBasedRoute requiredRoles={['admin', 'manager']}>
|
||||
<AnalyticsLayout />
|
||||
</RoleBasedRoute>
|
||||
),
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Navigate to="/app/analytics/forecasting" replace />
|
||||
},
|
||||
{
|
||||
path: 'forecasting',
|
||||
element: <ForecastPage />
|
||||
},
|
||||
{
|
||||
path: 'sales-analytics',
|
||||
element: <SalesAnalyticsPage />
|
||||
},
|
||||
{
|
||||
path: 'production-reports',
|
||||
element: <ProductionReportsPage />
|
||||
},
|
||||
{
|
||||
path: 'financial-reports',
|
||||
element: <FinancialReportsPage />
|
||||
},
|
||||
{
|
||||
path: 'performance-kpis',
|
||||
element: <PerformanceKPIsPage />
|
||||
},
|
||||
{
|
||||
path: 'ai-insights',
|
||||
element: <AIInsightsPage />
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Settings Hub
|
||||
{
|
||||
path: 'settings',
|
||||
element: <SettingsLayout />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Navigate to="/app/settings/general" replace />
|
||||
},
|
||||
{
|
||||
path: 'general',
|
||||
element: <GeneralSettingsPage />
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
element: (
|
||||
<RoleBasedRoute requiredRoles={['admin']}>
|
||||
<UsersManagementPage />
|
||||
</RoleBasedRoute>
|
||||
)
|
||||
},
|
||||
{
|
||||
path: 'bakeries',
|
||||
element: (
|
||||
<RoleBasedRoute requiredRoles={['admin']}>
|
||||
<BakeriesManagementPage />
|
||||
</RoleBasedRoute>
|
||||
)
|
||||
},
|
||||
{
|
||||
path: 'account',
|
||||
element: <AccountSettingsPage />
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Catch all route
|
||||
{
|
||||
path: '*',
|
||||
element: <Navigate to="/" replace />
|
||||
}
|
||||
]);
|
||||
652
frontend/src/router/routes.config.ts
Normal file
652
frontend/src/router/routes.config.ts
Normal file
@@ -0,0 +1,652 @@
|
||||
/**
|
||||
* Route configuration for the bakery management application
|
||||
*/
|
||||
|
||||
export interface RouteConfig {
|
||||
path: string;
|
||||
name: string;
|
||||
component: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
requiresAuth: boolean;
|
||||
requiredRoles?: string[];
|
||||
requiredPermissions?: string[];
|
||||
showInNavigation?: boolean;
|
||||
showInBreadcrumbs?: boolean;
|
||||
children?: RouteConfig[];
|
||||
meta?: {
|
||||
layout?: 'default' | 'auth' | 'minimal';
|
||||
headerTitle?: string;
|
||||
breadcrumbTitle?: string;
|
||||
hideHeader?: boolean;
|
||||
hideSidebar?: boolean;
|
||||
fullScreen?: boolean;
|
||||
scrollToTop?: boolean;
|
||||
preload?: boolean;
|
||||
cache?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// Route paths as constants
|
||||
export const ROUTES = {
|
||||
// Public routes
|
||||
HOME: '/',
|
||||
LOGIN: '/login',
|
||||
REGISTER: '/register',
|
||||
FORGOT_PASSWORD: '/forgot-password',
|
||||
RESET_PASSWORD: '/reset-password',
|
||||
VERIFY_EMAIL: '/verify-email',
|
||||
|
||||
// Dashboard
|
||||
DASHBOARD: '/app/dashboard',
|
||||
|
||||
// Inventory Management
|
||||
INVENTORY: '/app/operations/inventory',
|
||||
INVENTORY_INGREDIENTS: '/inventory/ingredients',
|
||||
INVENTORY_STOCK: '/inventory/stock',
|
||||
INVENTORY_MOVEMENTS: '/inventory/movements',
|
||||
INVENTORY_ALERTS: '/inventory/alerts',
|
||||
INVENTORY_QUALITY: '/inventory/quality',
|
||||
INVENTORY_REPORTS: '/inventory/reports',
|
||||
|
||||
// Production Management
|
||||
PRODUCTION: '/app/operations/production',
|
||||
PRODUCTION_BATCHES: '/production/batches',
|
||||
PRODUCTION_RECIPES: '/production/recipes',
|
||||
PRODUCTION_SCHEDULE: '/production/schedule',
|
||||
PRODUCTION_QUALITY: '/production/quality',
|
||||
PRODUCTION_REPORTS: '/production/reports',
|
||||
|
||||
// Sales & Analytics
|
||||
SALES: '/sales',
|
||||
SALES_DATA: '/sales/data',
|
||||
SALES_ANALYTICS: '/sales/analytics',
|
||||
SALES_REPORTS: '/sales/reports',
|
||||
SALES_FORECASTING: '/sales/forecasting',
|
||||
|
||||
// Forecasting & ML
|
||||
FORECASTING: '/forecasting',
|
||||
FORECASTING_MODELS: '/forecasting/models',
|
||||
FORECASTING_PREDICTIONS: '/forecasting/predictions',
|
||||
FORECASTING_TRAINING: '/forecasting/training',
|
||||
FORECASTING_ANALYTICS: '/forecasting/analytics',
|
||||
|
||||
// Orders Management
|
||||
ORDERS: '/orders',
|
||||
ORDERS_LIST: '/orders/list',
|
||||
ORDERS_QUEUE: '/orders/queue',
|
||||
ORDERS_HISTORY: '/orders/history',
|
||||
ORDERS_CUSTOMERS: '/orders/customers',
|
||||
|
||||
// Procurement
|
||||
PROCUREMENT: '/procurement',
|
||||
PROCUREMENT_ORDERS: '/procurement/orders',
|
||||
PROCUREMENT_SUPPLIERS: '/procurement/suppliers',
|
||||
PROCUREMENT_DELIVERIES: '/procurement/deliveries',
|
||||
PROCUREMENT_ANALYTICS: '/procurement/analytics',
|
||||
|
||||
// Point of Sale
|
||||
POS: '/pos',
|
||||
POS_INTEGRATION: '/pos/integration',
|
||||
POS_TRANSACTIONS: '/pos/transactions',
|
||||
POS_WEBHOOKS: '/pos/webhooks',
|
||||
POS_SETTINGS: '/pos/settings',
|
||||
|
||||
// Data Management
|
||||
DATA: '/data',
|
||||
DATA_IMPORT: '/data/import',
|
||||
DATA_EXPORT: '/data/export',
|
||||
DATA_EXTERNAL: '/data/external',
|
||||
DATA_WEATHER: '/data/weather',
|
||||
DATA_EVENTS: '/data/events',
|
||||
|
||||
// Training & ML
|
||||
TRAINING: '/training',
|
||||
TRAINING_MODELS: '/training/models',
|
||||
TRAINING_JOBS: '/training/jobs',
|
||||
TRAINING_EVALUATION: '/training/evaluation',
|
||||
TRAINING_DATASETS: '/training/datasets',
|
||||
|
||||
// Notifications
|
||||
NOTIFICATIONS: '/notifications',
|
||||
NOTIFICATIONS_LIST: '/notifications/list',
|
||||
NOTIFICATIONS_TEMPLATES: '/notifications/templates',
|
||||
NOTIFICATIONS_SETTINGS: '/notifications/settings',
|
||||
|
||||
// Settings
|
||||
SETTINGS: '/settings',
|
||||
SETTINGS_PROFILE: '/settings/profile',
|
||||
SETTINGS_TENANT: '/settings/tenant',
|
||||
SETTINGS_USERS: '/settings/users',
|
||||
SETTINGS_PERMISSIONS: '/settings/permissions',
|
||||
SETTINGS_INTEGRATIONS: '/settings/integrations',
|
||||
SETTINGS_PREFERENCES: '/settings/preferences',
|
||||
SETTINGS_BILLING: '/settings/billing',
|
||||
|
||||
// Reports
|
||||
REPORTS: '/reports',
|
||||
REPORTS_PRODUCTION: '/reports/production',
|
||||
REPORTS_INVENTORY: '/reports/inventory',
|
||||
REPORTS_SALES: '/reports/sales',
|
||||
REPORTS_FINANCIAL: '/reports/financial',
|
||||
REPORTS_QUALITY: '/reports/quality',
|
||||
|
||||
// Help & Support
|
||||
HELP: '/help',
|
||||
HELP_DOCUMENTATION: '/help/docs',
|
||||
HELP_TUTORIALS: '/help/tutorials',
|
||||
HELP_SUPPORT: '/help/support',
|
||||
HELP_FEEDBACK: '/help/feedback',
|
||||
|
||||
// Error pages
|
||||
NOT_FOUND: '/404',
|
||||
UNAUTHORIZED: '/401',
|
||||
FORBIDDEN: '/403',
|
||||
SERVER_ERROR: '/500',
|
||||
} as const;
|
||||
|
||||
// Route configurations
|
||||
export const routesConfig: RouteConfig[] = [
|
||||
// Public routes
|
||||
{
|
||||
path: ROUTES.HOME,
|
||||
name: 'Home',
|
||||
component: 'HomePage',
|
||||
title: 'Panadería IA - Gestión Inteligente',
|
||||
requiresAuth: false,
|
||||
showInNavigation: false,
|
||||
meta: {
|
||||
layout: 'minimal',
|
||||
scrollToTop: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ROUTES.LOGIN,
|
||||
name: 'Login',
|
||||
component: 'LoginPage',
|
||||
title: 'Iniciar Sesión',
|
||||
requiresAuth: false,
|
||||
showInNavigation: false,
|
||||
meta: {
|
||||
layout: 'auth',
|
||||
hideHeader: true,
|
||||
hideSidebar: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ROUTES.REGISTER,
|
||||
name: 'Register',
|
||||
component: 'RegisterPage',
|
||||
title: 'Crear Cuenta',
|
||||
requiresAuth: false,
|
||||
showInNavigation: false,
|
||||
meta: {
|
||||
layout: 'auth',
|
||||
hideHeader: true,
|
||||
hideSidebar: true,
|
||||
},
|
||||
},
|
||||
|
||||
// Dashboard
|
||||
{
|
||||
path: ROUTES.DASHBOARD,
|
||||
name: 'Dashboard',
|
||||
component: 'DashboardPage',
|
||||
title: 'Panel de Control',
|
||||
icon: 'dashboard',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
meta: {
|
||||
headerTitle: 'Panel de Control',
|
||||
preload: true,
|
||||
cache: true,
|
||||
},
|
||||
},
|
||||
|
||||
// Inventory Management
|
||||
{
|
||||
path: '/app/operations/inventory',
|
||||
name: 'Inventory',
|
||||
component: 'InventoryPage',
|
||||
title: 'Inventario',
|
||||
icon: 'inventory',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
},
|
||||
|
||||
// Production Management
|
||||
{
|
||||
path: '/app/operations/production',
|
||||
name: 'Production',
|
||||
component: 'ProductionPage',
|
||||
title: 'Producción',
|
||||
icon: 'production',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
// Settings
|
||||
{
|
||||
path: ROUTES.SETTINGS,
|
||||
name: 'Settings',
|
||||
component: 'SettingsPage',
|
||||
title: 'Configuración',
|
||||
icon: 'settings',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
children: [
|
||||
{
|
||||
path: '/app/settings/bakery-config',
|
||||
name: 'BakeryConfig',
|
||||
component: 'BakeryConfigPage',
|
||||
title: 'Configuración de Panadería',
|
||||
icon: 'settings',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
{
|
||||
path: '/app/settings/system',
|
||||
name: 'SystemSettings',
|
||||
component: 'SystemSettingsPage',
|
||||
title: 'Configuración del Sistema',
|
||||
icon: 'settings',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
{
|
||||
path: '/app/settings/team',
|
||||
name: 'Team',
|
||||
component: 'TeamPage',
|
||||
title: 'Gestión de Equipo',
|
||||
icon: 'settings',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
{
|
||||
path: '/app/settings/training',
|
||||
name: 'Training',
|
||||
component: 'TrainingPage',
|
||||
title: 'Entrenamiento',
|
||||
icon: 'training',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Individual Operations Pages
|
||||
{
|
||||
path: '/app/operations/recipes',
|
||||
name: 'Recipes',
|
||||
component: 'RecipesPage',
|
||||
title: 'Recetas',
|
||||
icon: 'production',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
},
|
||||
{
|
||||
path: '/app/operations/orders',
|
||||
name: 'Orders',
|
||||
component: 'OrdersPage',
|
||||
title: 'Pedidos',
|
||||
icon: 'orders',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
},
|
||||
{
|
||||
path: '/app/operations/procurement',
|
||||
name: 'Procurement',
|
||||
component: 'ProcurementPage',
|
||||
title: 'Compras',
|
||||
icon: 'procurement',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
},
|
||||
{
|
||||
path: '/app/operations/pos',
|
||||
name: 'POS',
|
||||
component: 'POSPage',
|
||||
title: 'Punto de Venta',
|
||||
icon: 'pos',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
},
|
||||
|
||||
// Individual Analytics Pages
|
||||
{
|
||||
path: '/app/analytics/forecasting',
|
||||
name: 'Forecasting',
|
||||
component: 'ForecastingPage',
|
||||
title: 'Pronósticos',
|
||||
icon: 'forecasting',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
},
|
||||
{
|
||||
path: '/app/analytics/sales',
|
||||
name: 'SalesAnalytics',
|
||||
component: 'SalesAnalyticsPage',
|
||||
title: 'Análisis de Ventas',
|
||||
icon: 'sales',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
},
|
||||
{
|
||||
path: '/app/analytics/ai-insights',
|
||||
name: 'AIInsights',
|
||||
component: 'AIInsightsPage',
|
||||
title: 'Insights de IA',
|
||||
icon: 'forecasting',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
},
|
||||
{
|
||||
path: '/app/analytics/performance',
|
||||
name: 'PerformanceAnalytics',
|
||||
component: 'PerformanceAnalyticsPage',
|
||||
title: 'Análisis de Rendimiento',
|
||||
icon: 'sales',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
},
|
||||
|
||||
// Communications Section
|
||||
{
|
||||
path: '/app/communications',
|
||||
name: 'Communications',
|
||||
component: 'CommunicationsPage',
|
||||
title: 'Comunicaciones',
|
||||
icon: 'notifications',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
children: [
|
||||
{
|
||||
path: '/app/communications/alerts',
|
||||
name: 'Alerts',
|
||||
component: 'AlertsPage',
|
||||
title: 'Alertas',
|
||||
icon: 'notifications',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
{
|
||||
path: '/app/communications/notifications',
|
||||
name: 'Notifications',
|
||||
component: 'NotificationsPage',
|
||||
title: 'Notificaciones',
|
||||
icon: 'notifications',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
{
|
||||
path: '/app/communications/preferences',
|
||||
name: 'Preferences',
|
||||
component: 'PreferencesPage',
|
||||
title: 'Preferencias',
|
||||
icon: 'settings',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Data Management Section
|
||||
{
|
||||
path: '/app/data',
|
||||
name: 'Data',
|
||||
component: 'DataPage',
|
||||
title: 'Gestión de Datos',
|
||||
icon: 'data',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
children: [
|
||||
{
|
||||
path: '/app/data/weather',
|
||||
name: 'Weather',
|
||||
component: 'WeatherPage',
|
||||
title: 'Datos Meteorológicos',
|
||||
icon: 'data',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
{
|
||||
path: '/app/data/traffic',
|
||||
name: 'Traffic',
|
||||
component: 'TrafficPage',
|
||||
title: 'Datos de Tráfico',
|
||||
icon: 'data',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
{
|
||||
path: '/app/data/events',
|
||||
name: 'Events',
|
||||
component: 'EventsPage',
|
||||
title: 'Eventos y Festivales',
|
||||
icon: 'data',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Onboarding Section
|
||||
{
|
||||
path: '/app/onboarding',
|
||||
name: 'Onboarding',
|
||||
component: 'OnboardingPage',
|
||||
title: 'Configuración Inicial',
|
||||
icon: 'settings',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
children: [
|
||||
{
|
||||
path: '/app/onboarding/setup',
|
||||
name: 'OnboardingSetup',
|
||||
component: 'OnboardingSetupPage',
|
||||
title: 'Configuración Básica',
|
||||
icon: 'settings',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
{
|
||||
path: '/app/onboarding/upload',
|
||||
name: 'OnboardingUpload',
|
||||
component: 'OnboardingUploadPage',
|
||||
title: 'Carga de Datos',
|
||||
icon: 'data',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
{
|
||||
path: '/app/onboarding/analysis',
|
||||
name: 'OnboardingAnalysis',
|
||||
component: 'OnboardingAnalysisPage',
|
||||
title: 'Análisis de Datos',
|
||||
icon: 'forecasting',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
{
|
||||
path: '/app/onboarding/review',
|
||||
name: 'OnboardingReview',
|
||||
component: 'OnboardingReviewPage',
|
||||
title: 'Revisión Final',
|
||||
icon: 'settings',
|
||||
requiresAuth: true,
|
||||
showInNavigation: true,
|
||||
showInBreadcrumbs: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
// Error pages
|
||||
{
|
||||
path: ROUTES.NOT_FOUND,
|
||||
name: 'NotFound',
|
||||
component: 'NotFoundPage',
|
||||
title: 'Página no encontrada',
|
||||
requiresAuth: false,
|
||||
showInNavigation: false,
|
||||
meta: {
|
||||
layout: 'minimal',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ROUTES.UNAUTHORIZED,
|
||||
name: 'Unauthorized',
|
||||
component: 'UnauthorizedPage',
|
||||
title: 'No autorizado',
|
||||
requiresAuth: false,
|
||||
showInNavigation: false,
|
||||
meta: {
|
||||
layout: 'minimal',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ROUTES.FORBIDDEN,
|
||||
name: 'Forbidden',
|
||||
component: 'ForbiddenPage',
|
||||
title: 'Acceso prohibido',
|
||||
requiresAuth: false,
|
||||
showInNavigation: false,
|
||||
meta: {
|
||||
layout: 'minimal',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Helper functions
|
||||
export const getRouteByPath = (path: string): RouteConfig | undefined => {
|
||||
const findRoute = (routes: RouteConfig[], targetPath: string): RouteConfig | undefined => {
|
||||
for (const route of routes) {
|
||||
if (route.path === targetPath) {
|
||||
return route;
|
||||
}
|
||||
if (route.children) {
|
||||
const childRoute = findRoute(route.children, targetPath);
|
||||
if (childRoute) {
|
||||
return childRoute;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return findRoute(routesConfig, path);
|
||||
};
|
||||
|
||||
export const getRouteByName = (name: string): RouteConfig | undefined => {
|
||||
const findRoute = (routes: RouteConfig[], targetName: string): RouteConfig | undefined => {
|
||||
for (const route of routes) {
|
||||
if (route.name === targetName) {
|
||||
return route;
|
||||
}
|
||||
if (route.children) {
|
||||
const childRoute = findRoute(route.children, targetName);
|
||||
if (childRoute) {
|
||||
return childRoute;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return findRoute(routesConfig, name);
|
||||
};
|
||||
|
||||
export const getNavigationRoutes = (): RouteConfig[] => {
|
||||
const filterNavRoutes = (routes: RouteConfig[]): RouteConfig[] => {
|
||||
return routes
|
||||
.filter(route => route.showInNavigation)
|
||||
.map(route => ({
|
||||
...route,
|
||||
children: route.children ? filterNavRoutes(route.children) : undefined,
|
||||
}));
|
||||
};
|
||||
|
||||
return filterNavRoutes(routesConfig);
|
||||
};
|
||||
|
||||
export const getBreadcrumbs = (path: string): RouteConfig[] => {
|
||||
const breadcrumbs: RouteConfig[] = [];
|
||||
const pathSegments = path.split('/').filter(segment => segment);
|
||||
|
||||
let currentPath = '';
|
||||
for (const segment of pathSegments) {
|
||||
currentPath += `/${segment}`;
|
||||
const route = getRouteByPath(currentPath);
|
||||
if (route && route.showInBreadcrumbs !== false) {
|
||||
breadcrumbs.push(route);
|
||||
}
|
||||
}
|
||||
|
||||
return breadcrumbs;
|
||||
};
|
||||
|
||||
export const hasPermission = (route: RouteConfig, userPermissions: string[]): boolean => {
|
||||
if (!route.requiredPermissions || route.requiredPermissions.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for wildcard permission
|
||||
if (userPermissions.includes('*')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return route.requiredPermissions.every(permission =>
|
||||
userPermissions.includes(permission)
|
||||
);
|
||||
};
|
||||
|
||||
export const hasRole = (route: RouteConfig, userRoles: string[]): boolean => {
|
||||
if (!route.requiredRoles || route.requiredRoles.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return route.requiredRoles.some(role =>
|
||||
userRoles.includes(role)
|
||||
);
|
||||
};
|
||||
|
||||
export const canAccessRoute = (
|
||||
route: RouteConfig,
|
||||
isAuthenticated: boolean,
|
||||
userRoles: string[] = [],
|
||||
userPermissions: string[] = []
|
||||
): boolean => {
|
||||
// Check authentication requirement
|
||||
if (route.requiresAuth && !isAuthenticated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check role requirements
|
||||
if (!hasRole(route, userRoles)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check permission requirements
|
||||
if (!hasPermission(route, userPermissions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
Reference in New Issue
Block a user