From 5781713b1396588adf256ad2329d5d7d0b223cd7 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Tue, 12 Aug 2025 10:33:36 -0300 Subject: [PATCH] =?UTF-8?q?Fix:=20Men=C3=BA=20Reportes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix del menú de reportes que impedía el recorrido del mismo. - Se quita la apertura predeterminada de una opción del menú de Reportes. --- Frontend/src/hooks/usePermissions.ts | 18 +-- .../src/pages/Reportes/ReportesIndexPage.tsx | 123 ++++++------------ Frontend/src/routes/AppRoutes.tsx | 3 - 3 files changed, 49 insertions(+), 95 deletions(-) diff --git a/Frontend/src/hooks/usePermissions.ts b/Frontend/src/hooks/usePermissions.ts index 0d6f5e3..38d4d24 100644 --- a/Frontend/src/hooks/usePermissions.ts +++ b/Frontend/src/hooks/usePermissions.ts @@ -1,22 +1,22 @@ -// src/hooks/usePermissions.ts import { useAuth } from '../contexts/AuthContext'; +import { useCallback } from 'react'; export const usePermissions = () => { - const { user } = useAuth(); // user aquí es de tipo UserContextData | null + const { user } = useAuth(); - const tienePermiso = (codigoPermisoRequerido: string): boolean => { - if (!user) { // Si no hay usuario logueado + // Envolvemos la función en useCallback. + // Su dependencia es [user], por lo que la función solo se + // volverá a crear si el objeto 'user' cambia (ej. al iniciar/cerrar sesión). + const tienePermiso = useCallback((codigoPermisoRequerido: string): boolean => { + if (!user) { return false; } - if (user.esSuperAdmin) { // SuperAdmin tiene todos los permisos + if (user.esSuperAdmin) { return true; } - // Verificar si la lista de permisos del usuario incluye el código requerido return user.permissions?.includes(codigoPermisoRequerido) ?? false; - }; + }, [user]); - // También puede exportar el objeto user completo si se necesita en otros lugares - // o propiedades específicas como idPerfil, esSuperAdmin. return { tienePermiso, isSuperAdmin: user?.esSuperAdmin ?? false, diff --git a/Frontend/src/pages/Reportes/ReportesIndexPage.tsx b/Frontend/src/pages/Reportes/ReportesIndexPage.tsx index a824bf5..b540911 100644 --- a/Frontend/src/pages/Reportes/ReportesIndexPage.tsx +++ b/Frontend/src/pages/Reportes/ReportesIndexPage.tsx @@ -40,14 +40,12 @@ const ReportesIndexPage: React.FC = () => { const [isLoadingInitialNavigation, setIsLoadingInitialNavigation] = useState(true); const { tienePermiso, isSuperAdmin } = usePermissions(); - // 1. Creamos una lista memoizada de reportes a los que el usuario SÍ tiene acceso. const accessibleReportModules = useMemo(() => { return allReportModules.filter(module => isSuperAdmin || tienePermiso(module.requiredPermission) ); }, [isSuperAdmin, tienePermiso]); - // 2. Creamos una lista de categorías que SÍ tienen al menos un reporte accesible. const accessibleCategories = useMemo(() => { const categoriesWithAccess = new Set(accessibleReportModules.map(r => r.category)); return predefinedCategoryOrder.filter(category => categoriesWithAccess.has(category)); @@ -62,18 +60,23 @@ const ReportesIndexPage: React.FC = () => { const activeReport = accessibleReportModules.find(module => module.path === subPathSegment); if (activeReport) { setExpandedCategory(activeReport.category); + } else { + // Si la URL apunta a un reporte que no es accesible, no expandimos nada + setExpandedCategory(false); + } + } else { + // Si estamos en la página base (/reportes), expandimos la primera categoría disponible. + if (accessibleCategories.length > 0) { + setExpandedCategory(accessibleCategories[0]); + } else { + setExpandedCategory(false); } } - // 4. Navegamos al PRIMER REPORTE ACCESIBLE si estamos en la ruta base. - if (location.pathname === currentBasePath && accessibleReportModules.length > 0 && isLoadingInitialNavigation) { - const firstReportToNavigate = accessibleReportModules[0]; - navigate(firstReportToNavigate.path, { replace: true }); - } - + // No hay navegación automática, solo manejamos el estado de carga. setIsLoadingInitialNavigation(false); - }, [location.pathname, navigate, accessibleReportModules, isLoadingInitialNavigation]); + }, [location.pathname, accessibleReportModules, accessibleCategories]); const handleCategoryClick = (categoryName: string) => { setExpandedCategory(prev => (prev === categoryName ? false : categoryName)); @@ -84,12 +87,10 @@ const ReportesIndexPage: React.FC = () => { }; const isReportActive = (reportPath: string) => { - return location.pathname === `/reportes/${reportPath}` || location.pathname.startsWith(`/reportes/${reportPath}/`); + return location.pathname.startsWith(`/reportes/${reportPath}`); }; - // Si isLoadingInitialNavigation es true Y estamos en /reportes, mostrar loader - // Esto evita mostrar el loader si se navega directamente a un sub-reporte. - if (isLoadingInitialNavigation && (location.pathname === '/reportes' || location.pathname === '/reportes/')) { + if (isLoadingInitialNavigation) { return ( @@ -98,48 +99,25 @@ const ReportesIndexPage: React.FC = () => { } return ( - // Contenedor principal que se adaptará a su padre - // Eliminamos 'height: calc(100vh - 64px)' y cualquier margen/padding que controle el espacio exterior - {/* Panel Lateral para Navegación */} - `1px solid ${theme.palette.divider}`, overflowY: 'auto', - bgcolor: 'background.paper', // O el color que desees para el menú - // display: 'flex', flexDirection: 'column' // Para que el título y la lista usen el espacio vertical - }} - > - {/* Título del Menú Lateral */} - `1px solid ${theme.palette.divider}`, // Opcional: separador - // position: 'sticky', // Si quieres que el título quede fijo al hacer scroll en la lista - // top: 0, - // zIndex: 1, - // bgcolor: 'background.paper' // Necesario si es sticky y tiene scroll la lista - }} - > - + bgcolor: 'background.paper', + }}> + + Reportes - {/* 5. Renderizamos el menú usando la lista de categorías ACCESIBLES. */} {accessibleCategories.length > 0 ? ( {accessibleCategories.map((category) => { - // 6. Obtenemos los reportes de esta categoría de la lista ACCESIBLE. const reportsInCategory = accessibleReportModules.filter(r => r.category === category); - - // Ya no es necesario el `if (reportsInCategory.length === 0) return null;` porque `accessibleCategories` ya está filtrado. - const isExpanded = expandedCategory === category; return ( @@ -147,15 +125,10 @@ const ReportesIndexPage: React.FC = () => { handleCategoryClick(category)} sx={{ - // py: 1.2, // Ajustar padding vertical de items de categoría - // backgroundColor: isExpanded ? 'action.selected' : 'transparent', borderLeft: isExpanded ? (theme) => `4px solid ${theme.palette.primary.main}` : '4px solid transparent', - pr: 1, // Menos padding a la derecha para dar espacio al ícono expander - '&:hover': { - backgroundColor: 'action.hover' - } - }} - > + pr: 1, + '&:hover': { backgroundColor: 'action.hover' } + }}> {isExpanded ? : } @@ -167,21 +140,14 @@ const ReportesIndexPage: React.FC = () => { selected={isReportActive(report.path)} onClick={() => handleReportClick(report.path)} sx={{ - pl: 3.5, // Indentación para los reportes (ajustar si se cambió el padding del título) - py: 0.8, // Padding vertical de items de reporte + pl: 3.5, py: 0.8, ...(isReportActive(report.path) && { - backgroundColor: (theme) => theme.palette.action.selected, // Un color de fondo sutil - borderLeft: (theme) => `4px solid ${theme.palette.primary.light}`, // Un borde para el activo - '& .MuiListItemText-primary': { - fontWeight: 'medium', // O 'bold' - // color: 'primary.main' - }, + backgroundColor: (theme) => theme.palette.action.selected, + borderLeft: (theme) => `4px solid ${theme.palette.primary.light}`, + '& .MuiListItemText-primary': { fontWeight: 'medium' }, }), - '&:hover': { - backgroundColor: (theme) => theme.palette.action.hover - } - }} - > + '&:hover': { backgroundColor: (theme) => theme.palette.action.hover } + }}> ))} @@ -196,26 +162,17 @@ const ReportesIndexPage: React.FC = () => { )} - {/* Área Principal para el Contenido del Reporte */} - - {/* El Outlet renderiza el componente del reporte específico */} - {(!location.pathname.startsWith('/reportes/') || !allReportModules.some(r => isReportActive(r.path))) && location.pathname !== '/reportes/' && location.pathname !== '/reportes' && !isLoadingInitialNavigation && ( - - El reporte solicitado no existe o la ruta no es válida. - - )} - {(location.pathname === '/reportes/' || location.pathname === '/reportes') && !allReportModules.some(r => isReportActive(r.path)) && !isLoadingInitialNavigation && ( + + {/* Lógica para mostrar el mensaje de bienvenida */} + {location.pathname === '/reportes' && !isLoadingInitialNavigation && ( - {allReportModules.length > 0 ? "Seleccione una categoría y un reporte del menú lateral." : "No hay reportes configurados."} + {accessibleReportModules.length > 0 + ? "Seleccione una categoría y un reporte del menú lateral." + : "No tiene acceso a ningún reporte." + } )} diff --git a/Frontend/src/routes/AppRoutes.tsx b/Frontend/src/routes/AppRoutes.tsx index 34c8dd9..f3d2c59 100644 --- a/Frontend/src/routes/AppRoutes.tsx +++ b/Frontend/src/routes/AppRoutes.tsx @@ -1,11 +1,9 @@ -// src/routes/AppRoutes.tsx import React, { type JSX } from 'react'; import { BrowserRouter, Routes, Route, Navigate, Outlet } from 'react-router-dom'; import LoginPage from '../pages/LoginPage'; import HomePage from '../pages/HomePage'; import { useAuth } from '../contexts/AuthContext'; import MainLayout from '../layouts/MainLayout'; -import { Typography } from '@mui/material'; import SectionProtectedRoute from './SectionProtectedRoute'; // Distribución @@ -267,7 +265,6 @@ const AppRoutes = () => { } > - Seleccione un reporte del menú lateral.} /> {/* Placeholder */} } /> } /> } />