Fix: Menú Reportes
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 7m12s

- 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.
This commit is contained in:
2025-08-12 10:33:36 -03:00
parent 9f8d577265
commit 5781713b13
3 changed files with 49 additions and 95 deletions

View File

@@ -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,

View File

@@ -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 (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', width: '100%', height: '100%' }}>
<CircularProgress />
@@ -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
<Box sx={{ display: 'flex', width: '100%', height: '100%' }}>
{/* Panel Lateral para Navegación */}
<Paper
elevation={0} // Sin elevación para que se sienta más integrado si el fondo es el mismo
square // Bordes rectos
sx={{
width: { xs: 220, sm: 250, md: 280 }, // Ancho responsivo del panel lateral
<Paper elevation={0} square sx={{
width: { xs: 220, sm: 250, md: 280 },
minWidth: { xs: 200, sm: 220 },
height: '100%', // Ocupa toda la altura del Box padre
height: '100%',
borderRight: (theme) => `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 */}
<Box
sx={{
p: 1.5, // Padding interno para el título
// borderBottom: (theme) => `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
}}
>
<Typography variant="h6" component="div" sx={{ fontWeight: 'medium', ml:1 /* Pequeño margen para alinear con items */ }}>
bgcolor: 'background.paper',
}}>
<Box sx={{ p: 1.5 }}>
<Typography variant="h6" component="div" sx={{ fontWeight: 'medium', ml:1 }}>
Reportes
</Typography>
</Box>
{/* 5. Renderizamos el menú usando la lista de categorías ACCESIBLES. */}
{accessibleCategories.length > 0 ? (
<List component="nav" dense sx={{ pt: 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 = () => {
<ListItemButton
onClick={() => 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' }
}}>
<ListItemText primary={category} primaryTypographyProps={{ fontWeight: isExpanded ? 'bold' : 'normal' }}/>
{isExpanded ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
@@ -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 }
}}>
<ListItemText primary={report.label} primaryTypographyProps={{ variant: 'body2' }}/>
</ListItemButton>
))}
@@ -196,26 +162,17 @@ const ReportesIndexPage: React.FC = () => {
)}
</Paper>
{/* Área Principal para el Contenido del Reporte */}
<Box
component="main"
sx={{
flexGrow: 1, // Ocupa el espacio restante
p: { xs: 1, sm: 2, md: 3 }, // Padding interno para el contenido, responsivo
overflowY: 'auto',
height: '100%', // Ocupa toda la altura del Box padre
bgcolor: 'grey.100' // Un color de fondo diferente para distinguir el área de contenido
}}
>
{/* 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 && (
<Typography sx={{p:2, textAlign:'center', mt: 4, color: 'text.secondary'}}>
El reporte solicitado no existe o la ruta no es válida.
</Typography>
)}
{(location.pathname === '/reportes/' || location.pathname === '/reportes') && !allReportModules.some(r => isReportActive(r.path)) && !isLoadingInitialNavigation && (
<Box component="main" sx={{
flexGrow: 1, p: { xs: 1, sm: 2, md: 3 },
overflowY: 'auto', height: '100%', bgcolor: 'grey.100'
}}>
{/* Lógica para mostrar el mensaje de bienvenida */}
{location.pathname === '/reportes' && !isLoadingInitialNavigation && (
<Typography sx={{p:2, textAlign:'center', mt: 4, color: 'text.secondary'}}>
{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."
}
</Typography>
)}
<Outlet />

View File

@@ -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 = () => {
<ReportesIndexPage />
</SectionProtectedRoute>}
>
<Route index element={<Typography sx={{ p: 2 }}>Seleccione un reporte del menú lateral.</Typography>} /> {/* Placeholder */}
<Route path="existencia-papel" element={<ReporteExistenciaPapelPage />} />
<Route path="movimiento-bobinas" element={<ReporteMovimientoBobinasPage />} />
<Route path="movimiento-bobinas-estado" element={<ReporteMovimientoBobinasEstadoPage />} />