|
|
|
|
@@ -3,92 +3,77 @@ import { Box, Paper, Typography, List, ListItemButton, ListItemText, Collapse, C
|
|
|
|
|
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
|
|
|
|
import ExpandLess from '@mui/icons-material/ExpandLess';
|
|
|
|
|
import ExpandMore from '@mui/icons-material/ExpandMore';
|
|
|
|
|
import { usePermissions } from '../../hooks/usePermissions';
|
|
|
|
|
|
|
|
|
|
// Definición de los módulos de reporte con sus categorías, etiquetas y rutas
|
|
|
|
|
const allReportModules: { category: string; label: string; path: string }[] = [
|
|
|
|
|
{ category: 'Existencia Papel', label: 'Existencia de Papel', path: 'existencia-papel' },
|
|
|
|
|
{ category: 'Movimientos Bobinas', label: 'Movimiento de Bobinas', path: 'movimiento-bobinas' },
|
|
|
|
|
{ category: 'Movimientos Bobinas', label: 'Mov. Bobinas por Estado', path: 'movimiento-bobinas-estado' },
|
|
|
|
|
{ category: 'Listados Distribución', label: 'Distribución Distribuidores', path: 'listado-distribucion-distribuidores' },
|
|
|
|
|
{ category: 'Listados Distribución', label: 'Distribución Canillas', path: 'listado-distribucion-canillas' },
|
|
|
|
|
{ category: 'Listados Distribución', label: 'Distribución General', path: 'listado-distribucion-general' },
|
|
|
|
|
{ category: 'Listados Distribución', label: 'Distrib. Canillas (Importe)', path: 'listado-distribucion-canillas-importe' },
|
|
|
|
|
{ category: 'Listados Distribución', label: 'Detalle Distribución Canillas', path: 'detalle-distribucion-canillas' },
|
|
|
|
|
{ category: 'Secretaría', label: 'Venta Mensual Secretaría', path: 'venta-mensual-secretaria' },
|
|
|
|
|
{ category: 'Tiradas por Publicación', label: 'Tiradas Publicación/Sección', path: 'tiradas-publicaciones-secciones' },
|
|
|
|
|
{ category: 'Consumos Bobinas', label: 'Consumo Bobinas/Sección', path: 'consumo-bobinas-seccion' },
|
|
|
|
|
{ category: 'Consumos Bobinas', label: 'Consumo Bobinas/PubPublicación', path: 'consumo-bobinas-publicacion' },
|
|
|
|
|
{ category: 'Consumos Bobinas', label: 'Comparativa Consumo Bobinas', path: 'comparativa-consumo-bobinas' },
|
|
|
|
|
{ category: 'Balance de Cuentas', label: 'Cuentas Distribuidores', path: 'cuentas-distribuidores' },
|
|
|
|
|
{ category: 'Ctrl. Devoluciones', label: 'Control de Devoluciones', path: 'control-devoluciones' },
|
|
|
|
|
{ category: 'Novedades de Canillitas', label: 'Novedades de Canillitas', path: 'novedades-canillas' },
|
|
|
|
|
{ category: 'Listados Distribución', label: 'Dist. Mensual Can/Acc', path: 'listado-distribucion-mensual' },
|
|
|
|
|
{ category: 'Suscripciones', label: 'Facturas para Publicidad', path: 'suscripciones-facturas-publicidad' },
|
|
|
|
|
{ category: 'Suscripciones', label: 'Distribución de Suscripciones', path: 'suscripciones-distribucion' },
|
|
|
|
|
const allReportModules: { category: string; label: string; path: string; requiredPermission: string; }[] = [
|
|
|
|
|
{ category: 'Existencia Papel', label: 'Existencia de Papel', path: 'existencia-papel', requiredPermission: 'RR005' },
|
|
|
|
|
{ category: 'Movimientos Bobinas', label: 'Movimiento de Bobinas', path: 'movimiento-bobinas', requiredPermission: 'RR006' },
|
|
|
|
|
{ category: 'Movimientos Bobinas', label: 'Mov. Bobinas por Estado', path: 'movimiento-bobinas-estado', requiredPermission: 'RR006' },
|
|
|
|
|
{ category: 'Listados Distribución', label: 'Distribución Distribuidores', path: 'listado-distribucion-distribuidores', requiredPermission: 'RR002' },
|
|
|
|
|
{ category: 'Listados Distribución', label: 'Distribución Canillas', path: 'listado-distribucion-canillas', requiredPermission: 'RR002' },
|
|
|
|
|
{ category: 'Listados Distribución', label: 'Distribución General', path: 'listado-distribucion-general', requiredPermission: 'RR002' },
|
|
|
|
|
{ category: 'Listados Distribución', label: 'Distrib. Canillas (Importe)', path: 'listado-distribucion-canillas-importe', requiredPermission: 'RR002' },
|
|
|
|
|
{ category: 'Listados Distribución', label: 'Detalle Distribución Canillas', path: 'detalle-distribucion-canillas', requiredPermission: 'MC005' },
|
|
|
|
|
{ category: 'Secretaría', label: 'Venta Mensual Secretaría', path: 'venta-mensual-secretaria', requiredPermission: 'RR002' },
|
|
|
|
|
{ category: 'Tiradas por Publicación', label: 'Tiradas Publicación/Sección', path: 'tiradas-publicaciones-secciones', requiredPermission: 'RR008' },
|
|
|
|
|
{ category: 'Consumos Bobinas', label: 'Consumo Bobinas/Sección', path: 'consumo-bobinas-seccion', requiredPermission: 'RR007' },
|
|
|
|
|
{ category: 'Consumos Bobinas', label: 'Consumo Bobinas/Publicación', path: 'consumo-bobinas-publicacion', requiredPermission: 'RR007' },
|
|
|
|
|
{ category: 'Consumos Bobinas', label: 'Comparativa Consumo Bobinas', path: 'comparativa-consumo-bobinas', requiredPermission: 'RR007' },
|
|
|
|
|
{ category: 'Balance de Cuentas', label: 'Cuentas Distribuidores', path: 'cuentas-distribuidores', requiredPermission: 'RR001' },
|
|
|
|
|
{ category: 'Ctrl. Devoluciones', label: 'Control de Devoluciones', path: 'control-devoluciones', requiredPermission: 'RR003' },
|
|
|
|
|
{ category: 'Novedades de Canillitas', label: 'Novedades de Canillitas', path: 'novedades-canillas', requiredPermission: 'RR004' },
|
|
|
|
|
{ category: 'Listados Distribución', label: 'Dist. Mensual Can/Acc', path: 'listado-distribucion-mensual', requiredPermission: 'RR009' },
|
|
|
|
|
{ category: 'Suscripciones', label: 'Facturas para Publicidad', path: 'suscripciones-facturas-publicidad', requiredPermission: 'RR010' },
|
|
|
|
|
{ category: 'Suscripciones', label: 'Distribución de Suscripciones', path: 'suscripciones-distribucion', requiredPermission: 'RR011' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const predefinedCategoryOrder = [
|
|
|
|
|
'Balance de Cuentas',
|
|
|
|
|
'Listados Distribución',
|
|
|
|
|
'Ctrl. Devoluciones',
|
|
|
|
|
'Novedades de Canillitas',
|
|
|
|
|
'Suscripciones',
|
|
|
|
|
'Existencia Papel',
|
|
|
|
|
'Movimientos Bobinas',
|
|
|
|
|
'Consumos Bobinas',
|
|
|
|
|
'Tiradas por Publicación',
|
|
|
|
|
'Secretaría',
|
|
|
|
|
'Balance de Cuentas', 'Listados Distribución', 'Ctrl. Devoluciones',
|
|
|
|
|
'Novedades de Canillitas', 'Suscripciones', 'Existencia Papel',
|
|
|
|
|
'Movimientos Bobinas', 'Consumos Bobinas', 'Tiradas por Publicación', 'Secretaría',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const ReportesIndexPage: React.FC = () => {
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
const location = useLocation();
|
|
|
|
|
|
|
|
|
|
const [expandedCategory, setExpandedCategory] = useState<string | false>(false);
|
|
|
|
|
const [isLoadingInitialNavigation, setIsLoadingInitialNavigation] = useState(true);
|
|
|
|
|
const { tienePermiso, isSuperAdmin } = usePermissions();
|
|
|
|
|
|
|
|
|
|
const uniqueCategories = useMemo(() => predefinedCategoryOrder, []);
|
|
|
|
|
// 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));
|
|
|
|
|
}, [accessibleReportModules]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const currentBasePath = '/reportes';
|
|
|
|
|
const pathParts = location.pathname.substring(currentBasePath.length + 1).split('/');
|
|
|
|
|
const subPathSegment = pathParts[0];
|
|
|
|
|
|
|
|
|
|
let activeReportFoundInEffect = false;
|
|
|
|
|
|
|
|
|
|
if (subPathSegment && subPathSegment !== "") { // Asegurarse que subPathSegment no esté vacío
|
|
|
|
|
const activeReport = allReportModules.find(module => module.path === subPathSegment);
|
|
|
|
|
if (subPathSegment) {
|
|
|
|
|
const activeReport = accessibleReportModules.find(module => module.path === subPathSegment);
|
|
|
|
|
if (activeReport) {
|
|
|
|
|
setExpandedCategory(activeReport.category);
|
|
|
|
|
activeReportFoundInEffect = true;
|
|
|
|
|
} else {
|
|
|
|
|
setExpandedCategory(false);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
setExpandedCategory(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (location.pathname === currentBasePath && allReportModules.length > 0 && isLoadingInitialNavigation) {
|
|
|
|
|
let firstReportToNavigate: { category: string; label: string; path: string } | null = null;
|
|
|
|
|
for (const category of uniqueCategories) {
|
|
|
|
|
const reportsInCat = allReportModules.filter(r => r.category === category);
|
|
|
|
|
if (reportsInCat.length > 0) {
|
|
|
|
|
firstReportToNavigate = reportsInCat[0];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (firstReportToNavigate) {
|
|
|
|
|
navigate(firstReportToNavigate.path, { replace: true });
|
|
|
|
|
activeReportFoundInEffect = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Solo se establece a false si no estamos en el proceso de navegación inicial O si no se encontró reporte
|
|
|
|
|
if (!activeReportFoundInEffect || location.pathname !== currentBasePath) {
|
|
|
|
|
setIsLoadingInitialNavigation(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 });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setIsLoadingInitialNavigation(false);
|
|
|
|
|
|
|
|
|
|
}, [location.pathname, navigate, uniqueCategories, isLoadingInitialNavigation]);
|
|
|
|
|
}, [location.pathname, navigate, accessibleReportModules, isLoadingInitialNavigation]);
|
|
|
|
|
|
|
|
|
|
const handleCategoryClick = (categoryName: string) => {
|
|
|
|
|
setExpandedCategory(prev => (prev === categoryName ? false : categoryName));
|
|
|
|
|
@@ -146,11 +131,15 @@ const ReportesIndexPage: React.FC = () => {
|
|
|
|
|
</Typography>
|
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
{/* Lista de Categorías y Reportes */}
|
|
|
|
|
{uniqueCategories.length > 0 ? (
|
|
|
|
|
<List component="nav" dense sx={{ pt: 0 }} /* Quitar padding superior de la lista si el título ya lo tiene */ >
|
|
|
|
|
{uniqueCategories.map((category) => {
|
|
|
|
|
const reportsInCategory = allReportModules.filter(r => r.category === category);
|
|
|
|
|
{/* 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 (
|
|
|
|
|
@@ -167,17 +156,10 @@ const ReportesIndexPage: React.FC = () => {
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<ListItemText
|
|
|
|
|
primary={category}
|
|
|
|
|
primaryTypographyProps={{
|
|
|
|
|
fontWeight: isExpanded ? 'bold' : 'normal',
|
|
|
|
|
// color: isExpanded ? 'primary.main' : 'text.primary'
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
{reportsInCategory.length > 0 && (isExpanded ? <ExpandLess /> : <ExpandMore />)}
|
|
|
|
|
<ListItemText primary={category} primaryTypographyProps={{ fontWeight: isExpanded ? 'bold' : 'normal' }}/>
|
|
|
|
|
{isExpanded ? <ExpandLess /> : <ExpandMore />}
|
|
|
|
|
</ListItemButton>
|
|
|
|
|
{reportsInCategory.length > 0 && (
|
|
|
|
|
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
|
|
|
|
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
|
|
|
|
<List component="div" disablePadding dense>
|
|
|
|
|
{reportsInCategory.map((report) => (
|
|
|
|
|
<ListItemButton
|
|
|
|
|
@@ -204,20 +186,13 @@ const ReportesIndexPage: React.FC = () => {
|
|
|
|
|
</ListItemButton>
|
|
|
|
|
))}
|
|
|
|
|
</List>
|
|
|
|
|
</Collapse>
|
|
|
|
|
)}
|
|
|
|
|
{reportsInCategory.length === 0 && isExpanded && (
|
|
|
|
|
<ListItemText
|
|
|
|
|
primary="No hay reportes en esta categoría."
|
|
|
|
|
sx={{ pl: 3.5, fontStyle: 'italic', color: 'text.secondary', py:1, typography: 'body2' }}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</Collapse>
|
|
|
|
|
</React.Fragment>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</List>
|
|
|
|
|
) : (
|
|
|
|
|
<Typography sx={{p:2, fontStyle: 'italic'}}>No hay categorías configuradas.</Typography>
|
|
|
|
|
<Typography sx={{p:2, fontStyle: 'italic'}}>No tiene acceso a ningún reporte.</Typography>
|
|
|
|
|
)}
|
|
|
|
|
</Paper>
|
|
|
|
|
|
|
|
|
|
|