refactor: otimização e refatoração do app

- 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
This commit is contained in:
2026-08-09 22:18:41 +00:00
parent 7ed4f26b50
commit a7bb224a48
12 changed files with 473 additions and 423 deletions
+114
View File
@@ -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 = () => (
<div className="flex items-center justify-center min-h-screen bg-background">
<div className="flex flex-col items-center gap-3">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<span className="text-sm text-muted-foreground">Carregando...</span>
</div>
</div>
);
// ============================================================================
// Cache de componentes lazy para evitar re-criação a cada render
// ============================================================================
const lazyComponentCache = new Map<string, React.LazyExoticComponent<React.ComponentType<any>>>();
function getLazyComponent(route: RouteDefinition): React.LazyExoticComponent<React.ComponentType<any>> {
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 (
<ProtectedRoute>
<Layout>{element}</Layout>
</ProtectedRoute>
);
case 'admin':
return (
<ProtectedAdminRoute>
<Layout>{element}</Layout>
</ProtectedAdminRoute>
);
case 'resource':
return (
<ProtectedRouteByResource resourceKey={resourceKey!}>
<Layout>{element}</Layout>
</ProtectedRouteByResource>
);
default:
return element;
}
}
// ============================================================================
// Componente principal de rotas
// ============================================================================
export function AppRoutes() {
const routes = useMemo(
() =>
routeConfig.map((route) => {
const LazyComponent = getLazyComponent(route);
const element = wrapWithGuard(
<Suspense fallback={<PageLoadingFallback />}>
<LazyComponent />
</Suspense>,
route.guard,
route.resourceKey
);
return <Route key={route.path} path={route.path} element={element} />;
}),
[]
);
// Lazy load da página NotFound
const LazyNotFound = useMemo(() => lazy(() => import('@/pages/NotFound')), []);
return (
<Routes>
{routes}
<Route
path="*"
element={
<Suspense fallback={<PageLoadingFallback />}>
<LazyNotFound />
</Suspense>
}
/>
</Routes>
);
}
+4 -3
View File
@@ -1,5 +1,6 @@
import React, { useState, useMemo } from "react"; import React, { useState, useMemo } from "react";
import { logger } from "@/utils/logger";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu } from "@/components/ui/sidebar"; import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu } from "@/components/ui/sidebar";
import { useSidebar } 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, isAdmin,
hasBasicAccess: hasAccess(), hasBasicAccess: hasAccess(),
canAccessTools: canAccessTools(), canAccessTools: canAccessTools(),
canInteractWithSpecialMenus: canInteractWithSpecialMenus() canInteractWithSpecialMenus: canInteractWithSpecialMenus()
}); } as any);
return ( return (
<Sidebar> <Sidebar>
@@ -142,7 +143,7 @@ export function AppSidebar() {
{menuGroups.map(group => { {menuGroups.map(group => {
// Filter admin groups for non-admin users // Filter admin groups for non-admin users
if (!isAdmin && group.name === 'Administração') { 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; return null;
} }
+1 -4
View File
@@ -1,11 +1,9 @@
import React from 'react'; import React from 'react';
import { SidebarProvider } from '@/components/ui/sidebar'; import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar';
import { AppSidebar } from '@/components/AppSidebar'; import { AppSidebar } from '@/components/AppSidebar';
import { Toaster } from '@/components/ui/sonner';
import { ApontamentoAutomaticoListener } from '@/components/expedicao/ApontamentoAutomaticoListener'; import { ApontamentoAutomaticoListener } from '@/components/expedicao/ApontamentoAutomaticoListener';
import { ThemeToggle } from '@/components/ThemeToggle'; import { ThemeToggle } from '@/components/ThemeToggle';
import { SidebarTrigger } from '@/components/ui/sidebar';
interface LayoutProps { interface LayoutProps {
children: React.ReactNode; children: React.ReactNode;
@@ -29,7 +27,6 @@ export const Layout = ({ children }: LayoutProps) => {
{children} {children}
</div> </div>
</main> </main>
<Toaster />
{/* Listener global para apontamento automático */} {/* Listener global para apontamento automático */}
<ApontamentoAutomaticoListener /> <ApontamentoAutomaticoListener />
</div> </div>
+5 -4
View File
@@ -4,6 +4,7 @@ import { Navigate } from 'react-router-dom';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { logger } from '@/utils/logger';
interface ProtectedRouteProps { interface ProtectedRouteProps {
children: React.ReactNode; children: React.ReactNode;
@@ -47,13 +48,13 @@ export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
// Se não há usuário, redirecionar para auth // Se não há usuário, redirecionar para auth
if (!user) { 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 <Navigate to="/auth" replace />; return <Navigate to="/auth" replace />;
} }
// Se há erro ao carregar perfil, permitir acesso (para evitar loop) // Se há erro ao carregar perfil, permitir acesso (para evitar loop)
if (error) { if (error) {
console.warn('⚠️ ProtectedRoute: Erro ao carregar perfil, permitindo acesso'); logger.warn('ProtectedRoute: Erro ao carregar perfil, permitindo acesso');
return <>{children}</>; return <>{children}</>;
} }
@@ -68,7 +69,7 @@ export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
// SEGURANÇA: Verificar se o usuário tem status 'active' // SEGURANÇA: Verificar se o usuário tem status 'active'
if (profile && profile.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 ( return (
<div className="flex items-center justify-center min-h-screen bg-background"> <div className="flex items-center justify-center min-h-screen bg-background">
<div className="text-center space-y-4 p-8 max-w-md mx-auto"> <div className="text-center space-y-4 p-8 max-w-md mx-auto">
@@ -95,6 +96,6 @@ export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
); );
} }
console.log('ProtectedRoute: Usuário ativo autorizado, renderizando conteúdo'); logger.debug('ProtectedRoute: Usuário ativo autorizado, renderizando conteúdo');
return <>{children}</>; return <>{children}</>;
}; };
+18 -38
View File
@@ -4,6 +4,7 @@ import { Navigate } from 'react-router-dom';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { useUserPermissions } from '@/hooks/useUserPermissions'; import { useUserPermissions } from '@/hooks/useUserPermissions';
import { useUserRole } from '@/hooks/useUserRole'; import { useUserRole } from '@/hooks/useUserRole';
import { logger } from '@/utils/logger';
interface ProtectedRouteByResourceProps { interface ProtectedRouteByResourceProps {
children: React.ReactNode; children: React.ReactNode;
@@ -22,7 +23,7 @@ export const ProtectedRouteByResource: React.FC<ProtectedRouteByResourceProps> =
try { try {
permissionsData = useUserPermissions(); permissionsData = useUserPermissions();
} catch (error) { } catch (error) {
console.error('Error in useUserPermissions:', error); console.error('Erro em useUserPermissions:', error);
// Fallback to basic data structure // Fallback to basic data structure
permissionsData = { permissionsData = {
hasAccess: () => isAdmin, hasAccess: () => isAdmin,
@@ -52,13 +53,13 @@ export const ProtectedRouteByResource: React.FC<ProtectedRouteByResourceProps> =
if (isAdmin) { if (isAdmin) {
const resourcePermission = getResourcePermission(resourceKey); const resourcePermission = getResourcePermission(resourceKey);
// Se admin tem negação explícita, negar acesso // Se admin tem negação explícita, negar acesso
if (resourcePermission === 'no_access') { if (resourcePermission === 'no_access') {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.log('Admin access denied by explicit resource permission:', resourceKey); logger.debug('Admin: acesso negado por permissão explícita de recurso', resourceKey as any);
}
} else {
return <>{children}</>;
} }
} else {
return <>{children}</>;
}
} }
let finalAccess = false; let finalAccess = false;
@@ -68,58 +69,37 @@ export const ProtectedRouteByResource: React.FC<ProtectedRouteByResourceProps> =
const resourcePermission = getResourcePermission(resourceKey); const resourcePermission = getResourcePermission(resourceKey);
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.log('🔍 Checking access for resource:', { logger.debug('Verificando acesso ao recurso', {
resourceKey, resourceKey,
user: user?.email, user: user?.email,
isAdmin, isAdmin,
resourcePermission, resourcePermission,
userPermissions userPermissions
}); } as any);
} }
// 2. Se há permissão específica definida, ela prevalece SEMPRE // 2. Se há permissão específica definida, ela prevalece SEMPRE
if (resourcePermission !== 'no_access') { if (resourcePermission !== 'no_access') {
finalAccess = true; finalAccess = true;
if (import.meta.env.DEV) { 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 { } else {
// 3. Se permissão específica é 'no_access', NEGAR acesso independente de outros privilégios // 3. Permissão explícita é 'no_access' — negar acesso
if (resourcePermission === 'no_access') { finalAccess = false;
finalAccess = false; if (import.meta.env.DEV) {
if (import.meta.env.DEV) { logger.debug('Acesso explicitamente negado pela permissão do recurso');
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
});
}
} }
} }
} catch (error) { } catch (error) {
console.error('Error checking access permissions:', error); logger.error('Erro ao verificar permissões de acesso', error);
// For safety, deny access on error unless user is admin and no explicit denial // Por segurança, negar acesso em caso de erro, a menos que seja admin sem negação explícita
const resourcePermission = getResourcePermission(resourceKey); const resourcePermission = getResourcePermission(resourceKey);
finalAccess = isAdmin && resourcePermission !== 'no_access'; finalAccess = isAdmin && resourcePermission !== 'no_access';
} }
if (!finalAccess) { if (!finalAccess) {
console.log(`❌ Access denied for resource: ${resourceKey}`); logger.debug(`Acesso negado para recurso: ${resourceKey}`);
return ( return (
<div className="flex items-center justify-center min-h-screen"> <div className="flex items-center justify-center min-h-screen">
<div className="text-center space-y-4"> <div className="text-center space-y-4">
+270
View File
@@ -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<any> }>;
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',
},
];
+12 -11
View File
@@ -1,6 +1,7 @@
import { useState, useEffect, createContext, useContext, ReactNode } from 'react'; import { useState, useEffect, createContext, useContext, ReactNode } from 'react';
import { User, Session } from '@supabase/supabase-js'; import { User, Session } from '@supabase/supabase-js';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { logger } from '@/utils/logger';
interface AuthContextType { interface AuthContextType {
user: User | null; user: User | null;
@@ -111,7 +112,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
// RPC não encontrada — continua sem erro // RPC não encontrada — continua sem erro
} }
} catch (error) { } 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 // RPC não encontrada — continua sem erro
} }
} catch (error) { } 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(); .single();
if (error) { 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) { } else if (data) {
localStorage.setItem('currentSessionId', data.id); localStorage.setItem('currentSessionId', data.id);
} }
} catch (error) { } 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'); localStorage.removeItem('currentSessionId');
} }
} catch (error) { } 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); setAuthInitialized(true);
} }
} catch (sessionError) { } catch (sessionError) {
console.error('Erro ao verificar sessão:', sessionError); logger.error('Erro ao verificar sessão:', sessionError);
if (mounted) { if (mounted) {
setSession(null); setSession(null);
setUser(null); setUser(null);
@@ -256,7 +257,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
subscription.unsubscribe(); subscription.unsubscribe();
}; };
} catch (error) { } catch (error) {
console.error('Erro na inicialização da autenticação:', error); logger.error('Erro na inicialização da autenticação:', error);
if (mounted) { if (mounted) {
setLoading(false); setLoading(false);
setSession(null); setSession(null);
@@ -321,7 +322,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}); });
return { error }; return { error };
} catch (error) { } catch (error) {
console.error('Erro crítico no login:', error); logger.error('Erro crítico no login:', error);
return { error }; return { error };
} }
}; };
@@ -338,7 +339,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}); });
return { error }; return { error };
} catch (error) { } catch (error) {
console.error('Erro crítico no signup:', error); logger.error('Erro crítico no signup:', error);
return { error }; return { error };
} }
}; };
@@ -356,7 +357,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
try { try {
await supabase.auth.signOut(); await supabase.auth.signOut();
} catch (error) { } 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 }; return { error };
} catch (error) { } catch (error) {
console.error('Erro crítico ao atualizar senha:', error); logger.error('Erro crítico ao atualizar senha:', error);
return { error }; return { error };
} }
}; };
+15 -340
View File
@@ -1,74 +1,37 @@
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom/client' 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 { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ThemeProvider } from '@/hooks/useTheme' import { ThemeProvider } from '@/hooks/useTheme'
import { Toaster } from '@/components/ui/sonner' import { Toaster } from '@/components/ui/sonner'
import { Layout } from './components/Layout'
import { AuthProvider } from './hooks/useAuth' import { AuthProvider } from './hooks/useAuth'
import { IconStyleProvider } from './hooks/useIconStyle' 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 { ErrorBoundary } from './components/ErrorBoundary'
import { AppRoutes } from './components/AppRoutes'
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 './index.css' import './index.css'
// ============================================================================
// Configuração do React Query com retry inteligente
// ============================================================================
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
retry: 3, retry: 3,
retryDelay: 1000, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
staleTime: 1000 * 60 * 5, // 5 minutes 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( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>
<ErrorBoundary> <ErrorBoundary>
@@ -77,295 +40,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
<IconStyleProvider> <IconStyleProvider>
<ThemeProvider defaultTheme="system"> <ThemeProvider defaultTheme="system">
<BrowserRouter> <BrowserRouter>
<Routes> <AppRoutes />
<Route path="/auth" element={<Auth />} />
{/* Dashboard */}
<Route path="/" element={
<ProtectedRoute>
<Layout>
<Dashboard />
</Layout>
</ProtectedRoute>
} />
<Route path="/dashboard" element={
<ProtectedRoute>
<Layout>
<Dashboard />
</Layout>
</ProtectedRoute>
} />
{/* Cadastro */}
<Route path="/cadastro-of" element={
<ProtectedRouteByResource resourceKey="cadastro-of">
<Layout>
<CadastroOF />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/seletor-of" element={
<ProtectedRouteByResource resourceKey="cadastro-pecas">
<Layout>
<SeletorOF />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/cadastro-pecas/:ofNumber" element={
<ProtectedRouteByResource resourceKey="cadastro-pecas">
<Layout>
<CadastroPecasFiltrado />
</Layout>
</ProtectedRouteByResource>
} />
{/* Equipamentos */}
<Route path="/equipamentos" element={
<ProtectedRouteByResource resourceKey="equipamentos">
<Layout>
<Equipamentos />
</Layout>
</ProtectedRouteByResource>
} />
{/* Ferramentas */}
<Route path="/ferramentas/conversores" element={
<ProtectedRouteByResource resourceKey="ferramentas-conversores">
<Layout>
<ConversoresDados />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/ferramentas/inconsistencias" element={
<ProtectedRouteByResource resourceKey="ferramentas-inconsistencias">
<Layout>
<VerInconsistencias />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/ferramentas/webclone" element={
<ProtectedRouteByResource resourceKey="ferramentas-webclone">
<Layout>
<WebClone />
</Layout>
</ProtectedRouteByResource>
} />
{/* Estoque */}
<Route path="/estoque" element={
<ProtectedRouteByResource resourceKey="estoque">
<Layout>
<Estoque />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/estoque/solicitacao-compras" element={
<ProtectedRouteByResource resourceKey="estoque-solicitacao-compras">
<Layout>
<SolicitacaoCompras />
</Layout>
</ProtectedRouteByResource>
} />
{/* OFs */}
<Route path="/ofs" element={
<ProtectedRouteByResource resourceKey="ofs-lista">
<Layout>
<OrdensFabricacao />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/ofs/cronograma" element={
<ProtectedRouteByResource resourceKey="ofs-cronograma">
<Layout>
<CronogramaOF />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/cadastro/ofs-concluidas" element={
<ProtectedRouteByResource resourceKey="ofs-concluidas">
<Layout>
<OFsConcluidas />
</Layout>
</ProtectedRouteByResource>
} />
{/* Produção */}
<Route path="/producao" element={
<ProtectedRouteByResource resourceKey="producao-visao">
<Layout>
<Producao />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/diario-producao" element={
<ProtectedRouteByResource resourceKey="diario-producao">
<Layout>
<DiarioProducao />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/apontamento-producao" element={
<ProtectedRouteByResource resourceKey="producao-apontamento">
<Layout>
<ApontamentoProducao />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/dashboard-producao" element={
<ProtectedRouteByResource resourceKey="producao-dashboard">
<Layout>
<DashboardProducao />
</Layout>
</ProtectedRouteByResource>
} />
{/* Painel Industrial */}
<Route path="/painel-industrial" element={
<ProtectedRouteByResource resourceKey="painel-industrial">
<Layout>
<PainelIndustrial />
</Layout>
</ProtectedRouteByResource>
} />
{/* Expedição */}
<Route path="/expedicao" element={
<ProtectedRouteByResource resourceKey="expedicao">
<Layout>
<Expedicao />
</Layout>
</ProtectedRouteByResource>
} />
{/* Obra */}
<Route path="/obra" element={
<ProtectedRouteByResource resourceKey="obra-dashboard">
<Layout>
<Obra />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/obra/configuracoes" element={
<ProtectedRouteByResource resourceKey="obra-configuracoes">
<Layout>
<ObraConfiguracoes />
</Layout>
</ProtectedRouteByResource>
} />
{/* Tarefas */}
<Route path="/tarefas" element={
<ProtectedRouteByResource resourceKey="tarefas-lista">
<Layout>
<Tarefas />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/tarefas/historico" element={
<ProtectedRouteByResource resourceKey="tarefas-historico">
<Layout>
<TarefasHistorico />
</Layout>
</ProtectedRouteByResource>
} />
{/* Biblioteca */}
<Route path="/biblioteca/catalogos" element={
<ProtectedRouteByResource resourceKey="biblioteca-catalogos">
<Layout>
<Catalogos />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/biblioteca/normas" element={
<ProtectedRouteByResource resourceKey="biblioteca-normas">
<Layout>
<BibliotecaNormas />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/biblioteca/referencias" element={
<ProtectedRouteByResource resourceKey="biblioteca-referencias">
<Layout>
<BibliotecaReferencias />
</Layout>
</ProtectedRouteByResource>
} />
{/* Sistema */}
<Route path="/sistema" element={
<ProtectedRouteByResource resourceKey="sistema">
<Layout>
<Sistema />
</Layout>
</ProtectedRouteByResource>
} />
{/* Sugestões */}
<Route path="/sugestoes" element={
<ProtectedRouteByResource resourceKey="sugestoes">
<Layout>
<Sugestoes />
</Layout>
</ProtectedRouteByResource>
} />
{/* Configurações */}
<Route path="/configuracoes" element={
<ProtectedRouteByResource resourceKey="configuracoes-gerais">
<Layout>
<Configuracoes />
</Layout>
</ProtectedRouteByResource>
} />
<Route path="/admin/theme-customization" element={
<ProtectedRouteByResource resourceKey="theme-customization">
<Layout>
<ThemeCustomizationPage />
</Layout>
</ProtectedRouteByResource>
} />
{/* Admin */}
<Route path="/admin" element={
<ProtectedAdminRoute>
<Layout>
<Admin />
</Layout>
</ProtectedAdminRoute>
} />
<Route path="/user-management" element={
<ProtectedAdminRoute>
<Layout>
<UserManagement />
</Layout>
</ProtectedAdminRoute>
} />
<Route path="/mapa-interativo" element={
<ProtectedAdminRoute>
<Layout>
<MapaInterativo />
</Layout>
</ProtectedAdminRoute>
} />
<Route path="/atribuicoes" element={
<ProtectedAdminRoute>
<Layout>
<Atribuicoes />
</Layout>
</ProtectedAdminRoute>
} />
<Route path="/prioridades-fabricacao" element={
<ProtectedRoute>
<Layout>
<PrioridadesFabricacao />
</Layout>
</ProtectedRoute>
} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter> </BrowserRouter>
<Toaster /> <Toaster />
</ThemeProvider> </ThemeProvider>
+3 -4
View File
@@ -3,6 +3,8 @@
* Implementa cache em memória e localStorage com TTL e versionamento * Implementa cache em memória e localStorage com TTL e versionamento
*/ */
import React from 'react';
interface CacheEntry { interface CacheEntry {
data: any; data: any;
timestamp: number; timestamp: number;
@@ -244,7 +246,7 @@ export class OptimizedCache {
* Registra métricas de performance * Registra métricas de performance
*/ */
private static trackPerformance(metrics: PerformanceMetrics): void { private static trackPerformance(metrics: PerformanceMetrics): void {
if (process.env.NODE_ENV === 'development') { if (import.meta.env.DEV) {
console.log('🚀 Cache Performance:', metrics); console.log('🚀 Cache Performance:', metrics);
} }
@@ -357,7 +359,4 @@ export const useSmartCache = <T>(
}; };
}; };
// Importar React para o hook
import React from 'react';
export default OptimizedCache; export default OptimizedCache;
+4 -5
View File
@@ -3,6 +3,9 @@
* Rastreia métricas de performance e operações do sistema * Rastreia métricas de performance e operações do sistema
*/ */
import React from 'react';
import { OptimizedCache } from './OptimizedCache';
export interface PerformanceMetrics { export interface PerformanceMetrics {
operation: string; operation: string;
duration_ms: number; duration_ms: number;
@@ -73,7 +76,7 @@ export class PerformanceMonitor {
this.addMetrics(metrics); this.addMetrics(metrics);
// Log para desenvolvimento // Log para desenvolvimento
if (process.env.NODE_ENV === 'development') { if (import.meta.env.DEV) {
this.logMetrics(metrics); this.logMetrics(metrics);
} }
@@ -312,10 +315,6 @@ export const useSystemHealth = () => {
return { health, loading, refetch: checkHealth }; 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 // Inicializar métricas persistidas ao carregar o módulo
PerformanceMonitor.loadPersistedMetrics(); PerformanceMonitor.loadPersistedMetrics();
+22 -14
View File
@@ -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; const isDevelopment = import.meta.env.DEV;
type LogData = Record<string, unknown> | string | number | boolean | null | undefined;
export const logger = { export const logger = {
info: (message: string, data?: any) => { debug: (message: string, data?: LogData) => {
if (isDevelopment) { if (isDevelopment) {
console.log(`🔍 ${message}`, data || ''); console.log(`🐛 ${message}`, data ?? '');
} }
}, },
error: (message: string, error?: any) => { info: (message: string, data?: LogData) => {
if (isDevelopment) { if (isDevelopment) {
console.error(` ${message}`, error || ''); console.log(`🔍 ${message}`, data ?? '');
}
// Em produção, poderia enviar para serviço de logging
},
warn: (message: string, data?: any) => {
if (isDevelopment) {
console.warn(`⚠️ ${message}`, data || '');
} }
}, },
success: (message: string, data?: any) => { error: (message: string, error?: unknown) => {
if (isDevelopment) { if (isDevelopment) {
console.log(` ${message}`, data || ''); console.error(` ${message}`, error ?? '');
}
// Em produção, poderia enviar para serviço de logging externo
},
warn: (message: string, data?: LogData) => {
if (isDevelopment) {
console.warn(`⚠️ ${message}`, data ?? '');
}
},
success: (message: string, data?: LogData) => {
if (isDevelopment) {
console.log(`${message}`, data ?? '');
} }
} }
}; };
+9 -4
View File
@@ -22,15 +22,19 @@ export default defineConfig(({ mode }) => ({
// Otimizações para produção // Otimizações para produção
minify: 'esbuild', minify: 'esbuild',
sourcemap: false, sourcemap: false,
cssCodeSplit: true,
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: { manualChunks: {
// Separar vendor chunks // Separar vendor chunks para melhor cache
vendor: ['react', 'react-dom'], vendor: ['react', 'react-dom', 'react-router-dom'],
ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu', '@radix-ui/react-select'], ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu', '@radix-ui/react-select', '@radix-ui/react-tabs', '@radix-ui/react-toast'],
charts: ['recharts'], charts: ['recharts'],
query: ['@tanstack/react-query'], query: ['@tanstack/react-query'],
supabase: ['@supabase/supabase-js'], 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 // Otimizações para desenvolvimento
optimizeDeps: { optimizeDeps: {
include: ['react', 'react-dom', '@tanstack/react-query', '@supabase/supabase-js'], include: ['react', 'react-dom', 'react-router-dom', '@tanstack/react-query', '@supabase/supabase-js'],
}, },
})); }));