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:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<Sidebar>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
</div>
|
||||
</main>
|
||||
<Toaster />
|
||||
{/* Listener global para apontamento automático */}
|
||||
<ApontamentoAutomaticoListener />
|
||||
</div>
|
||||
|
||||
@@ -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<ProtectedRouteProps> = ({ 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 <Navigate to="/auth" replace />;
|
||||
}
|
||||
|
||||
// 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<ProtectedRouteProps> = ({ 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 (
|
||||
<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">
|
||||
@@ -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}</>;
|
||||
};
|
||||
|
||||
@@ -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<ProtectedRouteByResourceProps> =
|
||||
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,
|
||||
@@ -54,7 +55,7 @@ export const ProtectedRouteByResource: React.FC<ProtectedRouteByResourceProps> =
|
||||
// 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);
|
||||
logger.debug('Admin: acesso negado por permissão explícita de recurso', resourceKey as any);
|
||||
}
|
||||
} else {
|
||||
return <>{children}</>;
|
||||
@@ -68,58 +69,37 @@ export const ProtectedRouteByResource: React.FC<ProtectedRouteByResourceProps> =
|
||||
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') {
|
||||
// 3. Permissão explícita é 'no_access' — negar acesso
|
||||
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
|
||||
});
|
||||
}
|
||||
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 (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-center space-y-4">
|
||||
|
||||
@@ -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
@@ -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 };
|
||||
}
|
||||
};
|
||||
|
||||
+15
-340
@@ -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(
|
||||
<React.StrictMode>
|
||||
<ErrorBoundary>
|
||||
@@ -77,295 +40,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<IconStyleProvider>
|
||||
<ThemeProvider defaultTheme="system">
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<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>
|
||||
<AppRoutes />
|
||||
</BrowserRouter>
|
||||
<Toaster />
|
||||
</ThemeProvider>
|
||||
|
||||
@@ -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 = <T>(
|
||||
};
|
||||
};
|
||||
|
||||
// Importar React para o hook
|
||||
import React from 'react';
|
||||
|
||||
export default OptimizedCache;
|
||||
@@ -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();
|
||||
|
||||
|
||||
+22
-14
@@ -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, unknown> | 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 ?? '');
|
||||
}
|
||||
},
|
||||
|
||||
error: (message: string, error?: any) => {
|
||||
info: (message: string, data?: LogData) => {
|
||||
if (isDevelopment) {
|
||||
console.error(`❌ ${message}`, error || '');
|
||||
}
|
||||
// Em produção, poderia enviar para serviço de logging
|
||||
},
|
||||
|
||||
warn: (message: string, data?: any) => {
|
||||
if (isDevelopment) {
|
||||
console.warn(`⚠️ ${message}`, data || '');
|
||||
console.log(`🔍 ${message}`, data ?? '');
|
||||
}
|
||||
},
|
||||
|
||||
success: (message: string, data?: any) => {
|
||||
error: (message: string, error?: unknown) => {
|
||||
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
@@ -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'],
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user