diff --git a/src/components/AppRoutes.tsx b/src/components/AppRoutes.tsx
new file mode 100644
index 0000000..75ab708
--- /dev/null
+++ b/src/components/AppRoutes.tsx
@@ -0,0 +1,114 @@
+import React, { Suspense, lazy, useMemo } from 'react';
+import { Routes, Route } from 'react-router-dom';
+import { Layout } from './Layout';
+import { ProtectedRoute } from './ProtectedRoute';
+import { ProtectedAdminRoute } from './ProtectedAdminRoute';
+import { ProtectedRouteByResource } from './ProtectedRouteByResource';
+import { routeConfig, RouteDefinition } from '@/config/routeConfig';
+
+// ============================================================================
+// Fallback de loading para Suspense
+// ============================================================================
+
+const PageLoadingFallback = () => (
+
+);
+
+// ============================================================================
+// Cache de componentes lazy para evitar re-criação a cada render
+// ============================================================================
+
+const lazyComponentCache = new Map>>();
+
+function getLazyComponent(route: RouteDefinition): React.LazyExoticComponent> {
+ const key = route.path;
+ if (!lazyComponentCache.has(key)) {
+ lazyComponentCache.set(key, lazy(route.component));
+ }
+ return lazyComponentCache.get(key)!;
+}
+
+// ============================================================================
+// Funções de wrapping por tipo de guard
+// ============================================================================
+
+function wrapWithGuard(
+ element: React.ReactNode,
+ guard: RouteDefinition['guard'],
+ resourceKey?: string
+): React.ReactNode {
+ switch (guard) {
+ case 'public':
+ return element;
+
+ case 'protected':
+ return (
+
+ {element}
+
+ );
+
+ case 'admin':
+ return (
+
+ {element}
+
+ );
+
+ case 'resource':
+ return (
+
+ {element}
+
+ );
+
+ default:
+ return element;
+ }
+}
+
+// ============================================================================
+// Componente principal de rotas
+// ============================================================================
+
+export function AppRoutes() {
+ const routes = useMemo(
+ () =>
+ routeConfig.map((route) => {
+ const LazyComponent = getLazyComponent(route);
+
+ const element = wrapWithGuard(
+ }>
+
+ ,
+ route.guard,
+ route.resourceKey
+ );
+
+ return ;
+ }),
+ []
+ );
+
+ // Lazy load da página NotFound
+ const LazyNotFound = useMemo(() => lazy(() => import('@/pages/NotFound')), []);
+
+ return (
+
+ {routes}
+ }>
+
+
+ }
+ />
+
+ );
+}
diff --git a/src/components/AppSidebar.tsx b/src/components/AppSidebar.tsx
index cb4c622..7e74064 100644
--- a/src/components/AppSidebar.tsx
+++ b/src/components/AppSidebar.tsx
@@ -1,5 +1,6 @@
import React, { useState, useMemo } from "react";
+import { logger } from "@/utils/logger";
import { useLocation } from "react-router-dom";
import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu } from "@/components/ui/sidebar";
import { useSidebar } from "@/components/ui/sidebar";
@@ -129,12 +130,12 @@ export function AppSidebar() {
);
}
- console.log('🖥️ Rendering sidebar with:', {
+ logger.debug('Renderizando sidebar', {
isAdmin,
hasBasicAccess: hasAccess(),
canAccessTools: canAccessTools(),
canInteractWithSpecialMenus: canInteractWithSpecialMenus()
- });
+ } as any);
return (
@@ -142,7 +143,7 @@ export function AppSidebar() {
{menuGroups.map(group => {
// Filter admin groups for non-admin users
if (!isAdmin && group.name === 'Administração') {
- console.log('🚫 Hiding admin group for non-admin user');
+ logger.debug('Ocultando grupo admin para usuário não-admin');
return null;
}
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
index 8920235..090be15 100644
--- a/src/components/Layout.tsx
+++ b/src/components/Layout.tsx
@@ -1,11 +1,9 @@
import React from 'react';
-import { SidebarProvider } from '@/components/ui/sidebar';
+import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar';
import { AppSidebar } from '@/components/AppSidebar';
-import { Toaster } from '@/components/ui/sonner';
import { ApontamentoAutomaticoListener } from '@/components/expedicao/ApontamentoAutomaticoListener';
import { ThemeToggle } from '@/components/ThemeToggle';
-import { SidebarTrigger } from '@/components/ui/sidebar';
interface LayoutProps {
children: React.ReactNode;
@@ -29,7 +27,6 @@ export const Layout = ({ children }: LayoutProps) => {
{children}
-
{/* Listener global para apontamento automático */}
diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx
index 386cd35..38fe2b7 100644
--- a/src/components/ProtectedRoute.tsx
+++ b/src/components/ProtectedRoute.tsx
@@ -4,6 +4,7 @@ import { Navigate } from 'react-router-dom';
import { useAuth } from '@/hooks/useAuth';
import { useQuery } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
+import { logger } from '@/utils/logger';
interface ProtectedRouteProps {
children: React.ReactNode;
@@ -47,13 +48,13 @@ export const ProtectedRoute: React.FC = ({ children }) => {
// Se não há usuário, redirecionar para auth
if (!user) {
- console.log('🚫 ProtectedRoute: Usuário não autenticado, redirecionando para /auth');
+ logger.debug('ProtectedRoute: Usuário não autenticado, redirecionando para /auth');
return ;
}
// Se há erro ao carregar perfil, permitir acesso (para evitar loop)
if (error) {
- console.warn('⚠️ ProtectedRoute: Erro ao carregar perfil, permitindo acesso');
+ logger.warn('ProtectedRoute: Erro ao carregar perfil, permitindo acesso');
return <>{children}>;
}
@@ -68,7 +69,7 @@ export const ProtectedRoute: React.FC = ({ children }) => {
// SEGURANÇA: Verificar se o usuário tem status 'active'
if (profile && profile.status !== 'active') {
- console.log('🚫 ProtectedRoute: Usuário com status inválido:', profile.status);
+ logger.debug('ProtectedRoute: Usuário com status inválido', profile.status as any);
return (
@@ -95,6 +96,6 @@ export const ProtectedRoute: React.FC
= ({ children }) => {
);
}
- console.log('✅ ProtectedRoute: Usuário ativo autorizado, renderizando conteúdo');
+ logger.debug('ProtectedRoute: Usuário ativo autorizado, renderizando conteúdo');
return <>{children}>;
};
diff --git a/src/components/ProtectedRouteByResource.tsx b/src/components/ProtectedRouteByResource.tsx
index 089a03f..2c9d87e 100644
--- a/src/components/ProtectedRouteByResource.tsx
+++ b/src/components/ProtectedRouteByResource.tsx
@@ -4,6 +4,7 @@ import { Navigate } from 'react-router-dom';
import { useAuth } from '@/hooks/useAuth';
import { useUserPermissions } from '@/hooks/useUserPermissions';
import { useUserRole } from '@/hooks/useUserRole';
+import { logger } from '@/utils/logger';
interface ProtectedRouteByResourceProps {
children: React.ReactNode;
@@ -22,7 +23,7 @@ export const ProtectedRouteByResource: React.FC =
try {
permissionsData = useUserPermissions();
} catch (error) {
- console.error('Error in useUserPermissions:', error);
+ console.error('Erro em useUserPermissions:', error);
// Fallback to basic data structure
permissionsData = {
hasAccess: () => isAdmin,
@@ -52,13 +53,13 @@ export const ProtectedRouteByResource: React.FC =
if (isAdmin) {
const resourcePermission = getResourcePermission(resourceKey);
// Se admin tem negação explícita, negar acesso
- if (resourcePermission === 'no_access') {
- if (import.meta.env.DEV) {
- console.log('❌ Admin access denied by explicit resource permission:', resourceKey);
+ if (resourcePermission === 'no_access') {
+ if (import.meta.env.DEV) {
+ logger.debug('Admin: acesso negado por permissão explícita de recurso', resourceKey as any);
+ }
+ } else {
+ return <>{children}>;
}
- } else {
- return <>{children}>;
- }
}
let finalAccess = false;
@@ -68,58 +69,37 @@ export const ProtectedRouteByResource: React.FC =
const resourcePermission = getResourcePermission(resourceKey);
if (import.meta.env.DEV) {
- console.log('🔍 Checking access for resource:', {
+ logger.debug('Verificando acesso ao recurso', {
resourceKey,
user: user?.email,
isAdmin,
resourcePermission,
userPermissions
- });
+ } as any);
}
// 2. Se há permissão específica definida, ela prevalece SEMPRE
if (resourcePermission !== 'no_access') {
finalAccess = true;
if (import.meta.env.DEV) {
- console.log('✅ Access granted by specific resource permission:', resourcePermission);
+ logger.success('Acesso concedido por permissão específica do recurso', resourcePermission as any);
}
} else {
- // 3. Se permissão específica é 'no_access', NEGAR acesso independente de outros privilégios
- if (resourcePermission === 'no_access') {
- finalAccess = false;
- if (import.meta.env.DEV) {
- console.log('❌ Access explicitly denied by resource permission');
- }
- } else {
- // 4. Se não há permissão específica, verificar permissões funcionais como fallback
- const hasGeneralAccess = hasAccess();
-
- // Para recursos de produção, permitir acesso para colaboradores como fallback
- const isProductionResource = resourceKey.startsWith('producao');
- const isCollaborator = userPermissions?.can_create_update_delete || userPermissions?.can_admin;
- const hasProductionAccess = isProductionResource && (isAdmin || isCollaborator || userPermissions?.can_view_only);
-
- finalAccess = hasGeneralAccess || hasProductionAccess;
-
- if (import.meta.env.DEV) {
- console.log('🔄 Fallback to general permissions:', {
- hasGeneralAccess,
- isProductionResource,
- hasProductionAccess,
- finalAccess
- });
- }
+ // 3. Permissão explícita é 'no_access' — negar acesso
+ finalAccess = false;
+ if (import.meta.env.DEV) {
+ logger.debug('Acesso explicitamente negado pela permissão do recurso');
}
}
} catch (error) {
- console.error('Error checking access permissions:', error);
- // For safety, deny access on error unless user is admin and no explicit denial
+ logger.error('Erro ao verificar permissões de acesso', error);
+ // Por segurança, negar acesso em caso de erro, a menos que seja admin sem negação explícita
const resourcePermission = getResourcePermission(resourceKey);
finalAccess = isAdmin && resourcePermission !== 'no_access';
}
if (!finalAccess) {
- console.log(`❌ Access denied for resource: ${resourceKey}`);
+ logger.debug(`Acesso negado para recurso: ${resourceKey}`);
return (
diff --git a/src/config/routeConfig.ts b/src/config/routeConfig.ts
new file mode 100644
index 0000000..710c2a3
--- /dev/null
+++ b/src/config/routeConfig.ts
@@ -0,0 +1,270 @@
+import { ComponentType, lazy } from 'react';
+
+// ============================================================================
+// Definição de tipos para o sistema de rotas
+// ============================================================================
+
+export type RouteGuard = 'public' | 'protected' | 'admin' | 'resource';
+
+export interface RouteDefinition {
+ path: string;
+ component: () => Promise<{ default: ComponentType
}>;
+ guard: RouteGuard;
+ resourceKey?: string;
+}
+
+// ============================================================================
+// Configuração centralizada de todas as rotas do aplicativo
+// ============================================================================
+
+export const routeConfig: RouteDefinition[] = [
+ // ── Pública ──────────────────────────────────────────────
+ {
+ path: '/auth',
+ component: () => import('@/pages/Auth'),
+ guard: 'public',
+ },
+
+ // ── Dashboard ────────────────────────────────────────────
+ {
+ path: '/',
+ component: () => import('@/pages/Dashboard'),
+ guard: 'protected',
+ },
+ {
+ path: '/dashboard',
+ component: () => import('@/pages/Dashboard'),
+ guard: 'protected',
+ },
+
+ // ── Cadastro ─────────────────────────────────────────────
+ {
+ path: '/cadastro-of',
+ component: () => import('@/pages/CadastroOF'),
+ guard: 'resource',
+ resourceKey: 'cadastro-of',
+ },
+ {
+ path: '/seletor-of',
+ component: () => import('@/pages/SeletorOF'),
+ guard: 'resource',
+ resourceKey: 'cadastro-pecas',
+ },
+ {
+ path: '/cadastro-pecas/:ofNumber',
+ component: () => import('@/pages/CadastroPecasFiltrado'),
+ guard: 'resource',
+ resourceKey: 'cadastro-pecas',
+ },
+
+ // ── Equipamentos ─────────────────────────────────────────
+ {
+ path: '/equipamentos',
+ component: () => import('@/pages/Equipamentos'),
+ guard: 'resource',
+ resourceKey: 'equipamentos',
+ },
+
+ // ── Ferramentas ──────────────────────────────────────────
+ {
+ path: '/ferramentas/conversores',
+ component: () => import('@/pages/ConversoresDados'),
+ guard: 'resource',
+ resourceKey: 'ferramentas-conversores',
+ },
+ {
+ path: '/ferramentas/inconsistencias',
+ component: () => import('@/pages/VerInconsistencias'),
+ guard: 'resource',
+ resourceKey: 'ferramentas-inconsistencias',
+ },
+ {
+ path: '/ferramentas/webclone',
+ component: () => import('@/pages/WebClone'),
+ guard: 'resource',
+ resourceKey: 'ferramentas-webclone',
+ },
+
+ // ── Estoque ──────────────────────────────────────────────
+ {
+ path: '/estoque',
+ component: () => import('@/pages/Estoque'),
+ guard: 'resource',
+ resourceKey: 'estoque',
+ },
+ {
+ path: '/estoque/solicitacao-compras',
+ component: () => import('@/pages/SolicitacaoCompras'),
+ guard: 'resource',
+ resourceKey: 'estoque-solicitacao-compras',
+ },
+
+ // ── OFs ──────────────────────────────────────────────────
+ {
+ path: '/ofs',
+ component: () => import('@/pages/OrdensFabricacao'),
+ guard: 'resource',
+ resourceKey: 'ofs-lista',
+ },
+ {
+ path: '/ofs/cronograma',
+ component: () => import('@/pages/CronogramaOF'),
+ guard: 'resource',
+ resourceKey: 'ofs-cronograma',
+ },
+ {
+ path: '/cadastro/ofs-concluidas',
+ component: () => import('@/pages/OFsConcluidas'),
+ guard: 'resource',
+ resourceKey: 'ofs-concluidas',
+ },
+
+ // ── Produção ─────────────────────────────────────────────
+ {
+ path: '/producao',
+ component: () => import('@/pages/Producao'),
+ guard: 'resource',
+ resourceKey: 'producao-visao',
+ },
+ {
+ path: '/diario-producao',
+ component: () => import('@/pages/DiarioProducao'),
+ guard: 'resource',
+ resourceKey: 'diario-producao',
+ },
+ {
+ path: '/apontamento-producao',
+ component: () => import('@/pages/ApontamentoProducao'),
+ guard: 'resource',
+ resourceKey: 'producao-apontamento',
+ },
+ {
+ path: '/dashboard-producao',
+ component: () => import('@/pages/DashboardProducao'),
+ guard: 'resource',
+ resourceKey: 'producao-dashboard',
+ },
+
+ // ── Painel Industrial ────────────────────────────────────
+ {
+ path: '/painel-industrial',
+ component: () => import('@/pages/PainelIndustrial'),
+ guard: 'resource',
+ resourceKey: 'painel-industrial',
+ },
+
+ // ── Expedição ────────────────────────────────────────────
+ {
+ path: '/expedicao',
+ component: () => import('@/pages/Expedicao'),
+ guard: 'resource',
+ resourceKey: 'expedicao',
+ },
+
+ // ── Obra ─────────────────────────────────────────────────
+ {
+ path: '/obra',
+ component: () => import('@/pages/Obra'),
+ guard: 'resource',
+ resourceKey: 'obra-dashboard',
+ },
+ {
+ path: '/obra/configuracoes',
+ component: () => import('@/pages/ObraConfiguracoes'),
+ guard: 'resource',
+ resourceKey: 'obra-configuracoes',
+ },
+
+ // ── Tarefas ──────────────────────────────────────────────
+ {
+ path: '/tarefas',
+ component: () => import('@/pages/Tarefas'),
+ guard: 'resource',
+ resourceKey: 'tarefas-lista',
+ },
+ {
+ path: '/tarefas/historico',
+ component: () => import('@/pages/TarefasHistorico'),
+ guard: 'resource',
+ resourceKey: 'tarefas-historico',
+ },
+
+ // ── Biblioteca ───────────────────────────────────────────
+ {
+ path: '/biblioteca/catalogos',
+ component: () => import('@/pages/Catalogos'),
+ guard: 'resource',
+ resourceKey: 'biblioteca-catalogos',
+ },
+ {
+ path: '/biblioteca/normas',
+ component: () => import('@/pages/BibliotecaNormas'),
+ guard: 'resource',
+ resourceKey: 'biblioteca-normas',
+ },
+ {
+ path: '/biblioteca/referencias',
+ component: () => import('@/pages/BibliotecaReferencias'),
+ guard: 'resource',
+ resourceKey: 'biblioteca-referencias',
+ },
+
+ // ── Sistema ──────────────────────────────────────────────
+ {
+ path: '/sistema',
+ component: () => import('@/pages/Sistema'),
+ guard: 'resource',
+ resourceKey: 'sistema',
+ },
+
+ // ── Sugestões ────────────────────────────────────────────
+ {
+ path: '/sugestoes',
+ component: () => import('@/pages/Sugestoes'),
+ guard: 'resource',
+ resourceKey: 'sugestoes',
+ },
+
+ // ── Configurações ────────────────────────────────────────
+ {
+ path: '/configuracoes',
+ component: () => import('@/pages/Configuracoes'),
+ guard: 'resource',
+ resourceKey: 'configuracoes-gerais',
+ },
+ {
+ path: '/admin/theme-customization',
+ component: () => import('@/pages/ThemeCustomizationPage'),
+ guard: 'resource',
+ resourceKey: 'theme-customization',
+ },
+
+ // ── Admin ────────────────────────────────────────────────
+ {
+ path: '/admin',
+ component: () => import('@/pages/Admin'),
+ guard: 'admin',
+ },
+ {
+ path: '/user-management',
+ component: () => import('@/pages/UserManagement'),
+ guard: 'admin',
+ },
+ {
+ path: '/mapa-interativo',
+ component: () => import('@/pages/MapaInterativo'),
+ guard: 'admin',
+ },
+ {
+ path: '/atribuicoes',
+ component: () => import('@/pages/Atribuicoes'),
+ guard: 'admin',
+ },
+
+ // ── Prioridades (Protected simples) ──────────────────────
+ {
+ path: '/prioridades-fabricacao',
+ component: () => import('@/pages/PrioridadesFabricacao'),
+ guard: 'protected',
+ },
+];
diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx
index c14a675..3606367 100644
--- a/src/hooks/useAuth.tsx
+++ b/src/hooks/useAuth.tsx
@@ -1,6 +1,7 @@
import { useState, useEffect, createContext, useContext, ReactNode } from 'react';
import { User, Session } from '@supabase/supabase-js';
import { supabase } from '@/integrations/supabase/client';
+import { logger } from '@/utils/logger';
interface AuthContextType {
user: User | null;
@@ -111,7 +112,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
// RPC não encontrada — continua sem erro
}
} catch (error) {
- console.error('Erro ao marcar usuário como online:', error);
+ logger.error('Erro ao marcar usuário como online:', error);
}
};
@@ -128,7 +129,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
// RPC não encontrada — continua sem erro
}
} catch (error) {
- console.error('Erro ao marcar usuário como offline:', error);
+ logger.error('Erro ao marcar usuário como offline:', error);
}
};
@@ -150,12 +151,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
.single();
if (error) {
- console.error('Erro ao registrar início de sessão:', error);
+ logger.error('Erro ao registrar início de sessão:', error);
} else if (data) {
localStorage.setItem('currentSessionId', data.id);
}
} catch (error) {
- console.error('Erro inesperado ao registrar início de sessão:', error);
+ logger.error('Erro inesperado ao registrar início de sessão:', error);
}
};
@@ -173,7 +174,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
localStorage.removeItem('currentSessionId');
}
} catch (error) {
- console.error('Erro ao finalizar sessão:', error);
+ logger.error('Erro ao finalizar sessão:', error);
}
};
@@ -236,7 +237,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
setAuthInitialized(true);
}
} catch (sessionError) {
- console.error('Erro ao verificar sessão:', sessionError);
+ logger.error('Erro ao verificar sessão:', sessionError);
if (mounted) {
setSession(null);
setUser(null);
@@ -256,7 +257,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
subscription.unsubscribe();
};
} catch (error) {
- console.error('Erro na inicialização da autenticação:', error);
+ logger.error('Erro na inicialização da autenticação:', error);
if (mounted) {
setLoading(false);
setSession(null);
@@ -321,7 +322,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
});
return { error };
} catch (error) {
- console.error('Erro crítico no login:', error);
+ logger.error('Erro crítico no login:', error);
return { error };
}
};
@@ -338,7 +339,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
});
return { error };
} catch (error) {
- console.error('Erro crítico no signup:', error);
+ logger.error('Erro crítico no signup:', error);
return { error };
}
};
@@ -356,7 +357,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
try {
await supabase.auth.signOut();
} catch (error) {
- console.error('Erro no signOut:', error);
+ logger.error('Erro no signOut:', error);
}
};
@@ -372,7 +373,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
return { error };
} catch (error) {
- console.error('Erro crítico ao atualizar senha:', error);
+ logger.error('Erro crítico ao atualizar senha:', error);
return { error };
}
};
diff --git a/src/main.tsx b/src/main.tsx
index 85b34a8..e548786 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,74 +1,37 @@
-
import React from 'react'
import ReactDOM from 'react-dom/client'
-import { BrowserRouter, Routes, Route } from 'react-router-dom'
+import { BrowserRouter } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ThemeProvider } from '@/hooks/useTheme'
import { Toaster } from '@/components/ui/sonner'
-import { Layout } from './components/Layout'
import { AuthProvider } from './hooks/useAuth'
import { IconStyleProvider } from './hooks/useIconStyle'
-import { ProtectedRoute } from './components/ProtectedRoute'
-import { ProtectedAdminRoute } from './components/ProtectedAdminRoute'
-import { ProtectedRouteByResource } from './components/ProtectedRouteByResource'
import { ErrorBoundary } from './components/ErrorBoundary'
-
-import {
- Auth,
- Dashboard,
- CadastroOF,
- CadastroPecas,
- SeletorOF,
- CadastroPecasFiltrado,
- Equipamentos,
- Estoque,
- OrdensFabricacao,
- OFsConcluidas,
- CronogramaOF,
- Producao,
- DiarioProducao,
- ApontamentoProducao,
- DashboardProducao,
- Expedicao,
- Obra,
- ObraConfiguracoes,
- Tarefas,
- TarefasHistorico,
- Sistema,
- Configuracoes,
- Admin,
- UserManagement,
- ThemeCustomizationPage,
- NotFound,
- Catalogos,
- BibliotecaFerramentas,
- BibliotecaNormas,
- BibliotecaReferencias,
- Sugestoes,
- ConversoresDados,
- PlanejamentoProducao,
- PainelIndustrial,
- PrioridadesFabricacao,
- MapaInterativo,
- Atribuicoes,
- VerInconsistencias,
- SolicitacaoCompras,
- WebClone
-} from './pages'
+import { AppRoutes } from './components/AppRoutes'
import './index.css'
+// ============================================================================
+// Configuração do React Query com retry inteligente
+// ============================================================================
+
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 3,
- retryDelay: 1000,
- staleTime: 1000 * 60 * 5, // 5 minutes
+ retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
+ staleTime: 1000 * 60 * 5, // 5 minutos
+ gcTime: 1000 * 60 * 10, // 10 minutos de garbage collection
+ refetchOnWindowFocus: false,
},
},
})
+// ============================================================================
+// Render da aplicação
+// ============================================================================
+
ReactDOM.createRoot(document.getElementById('root')!).render(
@@ -77,295 +40,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
-
- } />
-
- {/* Dashboard */}
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
- {/* Cadastro */}
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
- {/* Equipamentos */}
-
-
-
-
-
- } />
-
- {/* Ferramentas */}
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
- {/* Estoque */}
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
- {/* OFs */}
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
- {/* Produção */}
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
- {/* Painel Industrial */}
-
-
-
-
-
- } />
-
- {/* Expedição */}
-
-
-
-
-
- } />
-
- {/* Obra */}
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
- {/* Tarefas */}
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
- {/* Biblioteca */}
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
- {/* Sistema */}
-
-
-
-
-
- } />
-
- {/* Sugestões */}
-
-
-
-
-
- } />
-
- {/* Configurações */}
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
- {/* Admin */}
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
-
-
-
-
- } />
-
- } />
-
+
diff --git a/src/utils/OptimizedCache.ts b/src/utils/OptimizedCache.ts
index 4ecc84a..c95be10 100644
--- a/src/utils/OptimizedCache.ts
+++ b/src/utils/OptimizedCache.ts
@@ -3,6 +3,8 @@
* Implementa cache em memória e localStorage com TTL e versionamento
*/
+import React from 'react';
+
interface CacheEntry {
data: any;
timestamp: number;
@@ -244,7 +246,7 @@ export class OptimizedCache {
* Registra métricas de performance
*/
private static trackPerformance(metrics: PerformanceMetrics): void {
- if (process.env.NODE_ENV === 'development') {
+ if (import.meta.env.DEV) {
console.log('🚀 Cache Performance:', metrics);
}
@@ -357,7 +359,4 @@ export const useSmartCache = (
};
};
-// Importar React para o hook
-import React from 'react';
-
export default OptimizedCache;
\ No newline at end of file
diff --git a/src/utils/PerformanceMonitor.ts b/src/utils/PerformanceMonitor.ts
index d3b26a7..dcd4dd3 100644
--- a/src/utils/PerformanceMonitor.ts
+++ b/src/utils/PerformanceMonitor.ts
@@ -3,6 +3,9 @@
* Rastreia métricas de performance e operações do sistema
*/
+import React from 'react';
+import { OptimizedCache } from './OptimizedCache';
+
export interface PerformanceMetrics {
operation: string;
duration_ms: number;
@@ -73,7 +76,7 @@ export class PerformanceMonitor {
this.addMetrics(metrics);
// Log para desenvolvimento
- if (process.env.NODE_ENV === 'development') {
+ if (import.meta.env.DEV) {
this.logMetrics(metrics);
}
@@ -312,10 +315,6 @@ export const useSystemHealth = () => {
return { health, loading, refetch: checkHealth };
};
-// Importações necessárias
-import React from 'react';
-import { OptimizedCache } from './OptimizedCache';
-
// Inicializar métricas persistidas ao carregar o módulo
PerformanceMonitor.loadPersistedMetrics();
diff --git a/src/utils/logger.ts b/src/utils/logger.ts
index f18e12d..93da7b1 100644
--- a/src/utils/logger.ts
+++ b/src/utils/logger.ts
@@ -1,30 +1,38 @@
-// Logger utilitário para produção
+// Logger utilitário para produção — centraliza todos os logs do sistema
const isDevelopment = import.meta.env.DEV;
+type LogData = Record | string | number | boolean | null | undefined;
+
export const logger = {
- info: (message: string, data?: any) => {
+ debug: (message: string, data?: LogData) => {
if (isDevelopment) {
- console.log(`🔍 ${message}`, data || '');
+ console.log(`🐛 ${message}`, data ?? '');
+ }
+ },
+
+ info: (message: string, data?: LogData) => {
+ if (isDevelopment) {
+ console.log(`🔍 ${message}`, data ?? '');
}
},
- error: (message: string, error?: any) => {
+ error: (message: string, error?: unknown) => {
if (isDevelopment) {
- console.error(`❌ ${message}`, error || '');
+ console.error(`❌ ${message}`, error ?? '');
}
- // Em produção, poderia enviar para serviço de logging
+ // Em produção, poderia enviar para serviço de logging externo
},
- warn: (message: string, data?: any) => {
+ warn: (message: string, data?: LogData) => {
if (isDevelopment) {
- console.warn(`⚠️ ${message}`, data || '');
+ console.warn(`⚠️ ${message}`, data ?? '');
}
},
- success: (message: string, data?: any) => {
+ success: (message: string, data?: LogData) => {
if (isDevelopment) {
- console.log(`✅ ${message}`, data || '');
+ console.log(`✅ ${message}`, data ?? '');
}
}
};
diff --git a/vite.config.ts b/vite.config.ts
index 32c797a..522a9f5 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -22,15 +22,19 @@ export default defineConfig(({ mode }) => ({
// Otimizações para produção
minify: 'esbuild',
sourcemap: false,
+ cssCodeSplit: true,
rollupOptions: {
output: {
manualChunks: {
- // Separar vendor chunks
- vendor: ['react', 'react-dom'],
- ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu', '@radix-ui/react-select'],
+ // Separar vendor chunks para melhor cache
+ vendor: ['react', 'react-dom', 'react-router-dom'],
+ ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu', '@radix-ui/react-select', '@radix-ui/react-tabs', '@radix-ui/react-toast'],
charts: ['recharts'],
query: ['@tanstack/react-query'],
supabase: ['@supabase/supabase-js'],
+ dateUtils: ['date-fns', 'date-fns-tz'],
+ pdf: ['jspdf', 'jspdf-autotable'],
+ icons: ['lucide-react'],
},
},
},
@@ -38,6 +42,7 @@ export default defineConfig(({ mode }) => ({
},
// Otimizações para desenvolvimento
optimizeDeps: {
- include: ['react', 'react-dom', '@tanstack/react-query', '@supabase/supabase-js'],
+ include: ['react', 'react-dom', 'react-router-dom', '@tanstack/react-query', '@supabase/supabase-js'],
},
}));
+