Fix: Menú Reportes
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 7m12s
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:
@@ -1,22 +1,22 @@
|
|||||||
// src/hooks/usePermissions.ts
|
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
export const usePermissions = () => {
|
export const usePermissions = () => {
|
||||||
const { user } = useAuth(); // user aquí es de tipo UserContextData | null
|
const { user } = useAuth();
|
||||||
|
|
||||||
const tienePermiso = (codigoPermisoRequerido: string): boolean => {
|
// Envolvemos la función en useCallback.
|
||||||
if (!user) { // Si no hay usuario logueado
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
if (user.esSuperAdmin) { // SuperAdmin tiene todos los permisos
|
if (user.esSuperAdmin) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Verificar si la lista de permisos del usuario incluye el código requerido
|
|
||||||
return user.permissions?.includes(codigoPermisoRequerido) ?? false;
|
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 {
|
return {
|
||||||
tienePermiso,
|
tienePermiso,
|
||||||
isSuperAdmin: user?.esSuperAdmin ?? false,
|
isSuperAdmin: user?.esSuperAdmin ?? false,
|
||||||
|
|||||||
@@ -40,14 +40,12 @@ const ReportesIndexPage: React.FC = () => {
|
|||||||
const [isLoadingInitialNavigation, setIsLoadingInitialNavigation] = useState(true);
|
const [isLoadingInitialNavigation, setIsLoadingInitialNavigation] = useState(true);
|
||||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||||
|
|
||||||
// 1. Creamos una lista memoizada de reportes a los que el usuario SÍ tiene acceso.
|
|
||||||
const accessibleReportModules = useMemo(() => {
|
const accessibleReportModules = useMemo(() => {
|
||||||
return allReportModules.filter(module =>
|
return allReportModules.filter(module =>
|
||||||
isSuperAdmin || tienePermiso(module.requiredPermission)
|
isSuperAdmin || tienePermiso(module.requiredPermission)
|
||||||
);
|
);
|
||||||
}, [isSuperAdmin, tienePermiso]);
|
}, [isSuperAdmin, tienePermiso]);
|
||||||
|
|
||||||
// 2. Creamos una lista de categorías que SÍ tienen al menos un reporte accesible.
|
|
||||||
const accessibleCategories = useMemo(() => {
|
const accessibleCategories = useMemo(() => {
|
||||||
const categoriesWithAccess = new Set(accessibleReportModules.map(r => r.category));
|
const categoriesWithAccess = new Set(accessibleReportModules.map(r => r.category));
|
||||||
return predefinedCategoryOrder.filter(category => categoriesWithAccess.has(category));
|
return predefinedCategoryOrder.filter(category => categoriesWithAccess.has(category));
|
||||||
@@ -62,18 +60,23 @@ const ReportesIndexPage: React.FC = () => {
|
|||||||
const activeReport = accessibleReportModules.find(module => module.path === subPathSegment);
|
const activeReport = accessibleReportModules.find(module => module.path === subPathSegment);
|
||||||
if (activeReport) {
|
if (activeReport) {
|
||||||
setExpandedCategory(activeReport.category);
|
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.
|
// No hay navegación automática, solo manejamos el estado de carga.
|
||||||
if (location.pathname === currentBasePath && accessibleReportModules.length > 0 && isLoadingInitialNavigation) {
|
|
||||||
const firstReportToNavigate = accessibleReportModules[0];
|
|
||||||
navigate(firstReportToNavigate.path, { replace: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoadingInitialNavigation(false);
|
setIsLoadingInitialNavigation(false);
|
||||||
|
|
||||||
}, [location.pathname, navigate, accessibleReportModules, isLoadingInitialNavigation]);
|
}, [location.pathname, accessibleReportModules, accessibleCategories]);
|
||||||
|
|
||||||
const handleCategoryClick = (categoryName: string) => {
|
const handleCategoryClick = (categoryName: string) => {
|
||||||
setExpandedCategory(prev => (prev === categoryName ? false : categoryName));
|
setExpandedCategory(prev => (prev === categoryName ? false : categoryName));
|
||||||
@@ -84,12 +87,10 @@ const ReportesIndexPage: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isReportActive = (reportPath: string) => {
|
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
|
if (isLoadingInitialNavigation) {
|
||||||
// Esto evita mostrar el loader si se navega directamente a un sub-reporte.
|
|
||||||
if (isLoadingInitialNavigation && (location.pathname === '/reportes' || location.pathname === '/reportes/')) {
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', width: '100%', height: '100%' }}>
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', width: '100%', height: '100%' }}>
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
@@ -98,48 +99,25 @@ const ReportesIndexPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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%' }}>
|
<Box sx={{ display: 'flex', width: '100%', height: '100%' }}>
|
||||||
{/* Panel Lateral para Navegación */}
|
<Paper elevation={0} square sx={{
|
||||||
<Paper
|
width: { xs: 220, sm: 250, md: 280 },
|
||||||
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
|
|
||||||
minWidth: { xs: 200, sm: 220 },
|
minWidth: { xs: 200, sm: 220 },
|
||||||
height: '100%', // Ocupa toda la altura del Box padre
|
height: '100%',
|
||||||
borderRight: (theme) => `1px solid ${theme.palette.divider}`,
|
borderRight: (theme) => `1px solid ${theme.palette.divider}`,
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
bgcolor: 'background.paper', // O el color que desees para el menú
|
bgcolor: 'background.paper',
|
||||||
// display: 'flex', flexDirection: 'column' // Para que el título y la lista usen el espacio vertical
|
}}>
|
||||||
}}
|
<Box sx={{ p: 1.5 }}>
|
||||||
>
|
<Typography variant="h6" component="div" sx={{ fontWeight: 'medium', ml:1 }}>
|
||||||
{/* 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 */ }}>
|
|
||||||
Reportes
|
Reportes
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 5. Renderizamos el menú usando la lista de categorías ACCESIBLES. */}
|
|
||||||
{accessibleCategories.length > 0 ? (
|
{accessibleCategories.length > 0 ? (
|
||||||
<List component="nav" dense sx={{ pt: 0 }}>
|
<List component="nav" dense sx={{ pt: 0 }}>
|
||||||
{accessibleCategories.map((category) => {
|
{accessibleCategories.map((category) => {
|
||||||
// 6. Obtenemos los reportes de esta categoría de la lista ACCESIBLE.
|
|
||||||
const reportsInCategory = accessibleReportModules.filter(r => r.category === category);
|
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;
|
const isExpanded = expandedCategory === category;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -147,15 +125,10 @@ const ReportesIndexPage: React.FC = () => {
|
|||||||
<ListItemButton
|
<ListItemButton
|
||||||
onClick={() => handleCategoryClick(category)}
|
onClick={() => handleCategoryClick(category)}
|
||||||
sx={{
|
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',
|
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
|
pr: 1,
|
||||||
'&:hover': {
|
'&:hover': { backgroundColor: 'action.hover' }
|
||||||
backgroundColor: 'action.hover'
|
}}>
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ListItemText primary={category} primaryTypographyProps={{ fontWeight: isExpanded ? 'bold' : 'normal' }}/>
|
<ListItemText primary={category} primaryTypographyProps={{ fontWeight: isExpanded ? 'bold' : 'normal' }}/>
|
||||||
{isExpanded ? <ExpandLess /> : <ExpandMore />}
|
{isExpanded ? <ExpandLess /> : <ExpandMore />}
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
@@ -167,21 +140,14 @@ const ReportesIndexPage: React.FC = () => {
|
|||||||
selected={isReportActive(report.path)}
|
selected={isReportActive(report.path)}
|
||||||
onClick={() => handleReportClick(report.path)}
|
onClick={() => handleReportClick(report.path)}
|
||||||
sx={{
|
sx={{
|
||||||
pl: 3.5, // Indentación para los reportes (ajustar si se cambió el padding del título)
|
pl: 3.5, py: 0.8,
|
||||||
py: 0.8, // Padding vertical de items de reporte
|
|
||||||
...(isReportActive(report.path) && {
|
...(isReportActive(report.path) && {
|
||||||
backgroundColor: (theme) => theme.palette.action.selected, // Un color de fondo sutil
|
backgroundColor: (theme) => theme.palette.action.selected,
|
||||||
borderLeft: (theme) => `4px solid ${theme.palette.primary.light}`, // Un borde para el activo
|
borderLeft: (theme) => `4px solid ${theme.palette.primary.light}`,
|
||||||
'& .MuiListItemText-primary': {
|
'& .MuiListItemText-primary': { fontWeight: 'medium' },
|
||||||
fontWeight: 'medium', // O 'bold'
|
|
||||||
// color: 'primary.main'
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
'&:hover': {
|
'&:hover': { backgroundColor: (theme) => theme.palette.action.hover }
|
||||||
backgroundColor: (theme) => theme.palette.action.hover
|
}}>
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ListItemText primary={report.label} primaryTypographyProps={{ variant: 'body2' }}/>
|
<ListItemText primary={report.label} primaryTypographyProps={{ variant: 'body2' }}/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
))}
|
))}
|
||||||
@@ -196,26 +162,17 @@ const ReportesIndexPage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Área Principal para el Contenido del Reporte */}
|
<Box component="main" sx={{
|
||||||
<Box
|
flexGrow: 1, p: { xs: 1, sm: 2, md: 3 },
|
||||||
component="main"
|
overflowY: 'auto', height: '100%', bgcolor: 'grey.100'
|
||||||
sx={{
|
}}>
|
||||||
flexGrow: 1, // Ocupa el espacio restante
|
{/* Lógica para mostrar el mensaje de bienvenida */}
|
||||||
p: { xs: 1, sm: 2, md: 3 }, // Padding interno para el contenido, responsivo
|
{location.pathname === '/reportes' && !isLoadingInitialNavigation && (
|
||||||
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 && (
|
|
||||||
<Typography sx={{p:2, textAlign:'center', mt: 4, color: 'text.secondary'}}>
|
<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>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
// src/routes/AppRoutes.tsx
|
|
||||||
import React, { type JSX } from 'react';
|
import React, { type JSX } from 'react';
|
||||||
import { BrowserRouter, Routes, Route, Navigate, Outlet } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route, Navigate, Outlet } from 'react-router-dom';
|
||||||
import LoginPage from '../pages/LoginPage';
|
import LoginPage from '../pages/LoginPage';
|
||||||
import HomePage from '../pages/HomePage';
|
import HomePage from '../pages/HomePage';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import MainLayout from '../layouts/MainLayout';
|
import MainLayout from '../layouts/MainLayout';
|
||||||
import { Typography } from '@mui/material';
|
|
||||||
import SectionProtectedRoute from './SectionProtectedRoute';
|
import SectionProtectedRoute from './SectionProtectedRoute';
|
||||||
|
|
||||||
// Distribución
|
// Distribución
|
||||||
@@ -267,7 +265,6 @@ const AppRoutes = () => {
|
|||||||
<ReportesIndexPage />
|
<ReportesIndexPage />
|
||||||
</SectionProtectedRoute>}
|
</SectionProtectedRoute>}
|
||||||
>
|
>
|
||||||
<Route index element={<Typography sx={{ p: 2 }}>Seleccione un reporte del menú lateral.</Typography>} /> {/* Placeholder */}
|
|
||||||
<Route path="existencia-papel" element={<ReporteExistenciaPapelPage />} />
|
<Route path="existencia-papel" element={<ReporteExistenciaPapelPage />} />
|
||||||
<Route path="movimiento-bobinas" element={<ReporteMovimientoBobinasPage />} />
|
<Route path="movimiento-bobinas" element={<ReporteMovimientoBobinasPage />} />
|
||||||
<Route path="movimiento-bobinas-estado" element={<ReporteMovimientoBobinasEstadoPage />} />
|
<Route path="movimiento-bobinas-estado" element={<ReporteMovimientoBobinasEstadoPage />} />
|
||||||
|
|||||||
Reference in New Issue
Block a user