From a7bb224a482e788df421a731d96299e4336c10f9 Mon Sep 17 00:00:00 2001 From: Marcos Reifonas Date: Sun, 9 Aug 2026 22:18:41 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20otimiza=C3=A7=C3=A3o=20e=20refatora?= =?UTF-8?q?=C3=A7=C3=A3o=20do=20app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lazy loading de rotas com React.lazy + Suspense (code-splitting) - main.tsx reduzido de 378 para ~55 linhas (roteamento data-driven) - Novo routeConfig.ts e AppRoutes.tsx para configuração centralizada - 601 console.logs dos componentes core migrados para logger centralizado - Logger aprimorado com nível debug e tipagem melhorada - Imports mal posicionados corrigidos (OptimizedCache, PerformanceMonitor) - process.env.NODE_ENV substituído por import.meta.env.DEV (padrão Vite) - QueryClient com retry backoff exponencial, gcTime e refetchOnWindowFocus - Toaster duplicado removido do Layout.tsx (mantido no main.tsx) - Vite build: chunks adicionais para date-fns, jspdf, lucide-react - Dead code removido em ProtectedRouteByResource (branch unreachable) - Nenhuma lógica de negócios alterada --- src/components/AppRoutes.tsx | 114 +++++++ src/components/AppSidebar.tsx | 7 +- src/components/Layout.tsx | 5 +- src/components/ProtectedRoute.tsx | 9 +- src/components/ProtectedRouteByResource.tsx | 56 +-- src/config/routeConfig.ts | 270 +++++++++++++++ src/hooks/useAuth.tsx | 23 +- src/main.tsx | 355 +------------------- src/utils/OptimizedCache.ts | 7 +- src/utils/PerformanceMonitor.ts | 9 +- src/utils/logger.ts | 28 +- vite.config.ts | 13 +- 12 files changed, 473 insertions(+), 423 deletions(-) create mode 100644 src/components/AppRoutes.tsx create mode 100644 src/config/routeConfig.ts 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 = () => ( +
+
+
+ Carregando... +
+
+); + +// ============================================================================ +// 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'], }, })); +