Finalización de Reportes y arreglos varios de controles y comportamientos...

This commit is contained in:
2025-06-03 13:45:20 -03:00
parent 99532b03f1
commit 062cc05fd0
67 changed files with 4523 additions and 993 deletions

View File

@@ -1,8 +1,14 @@
import React, { type ReactNode, useState, useEffect } from 'react';
import { Box, AppBar, Toolbar, Typography, Button, Tabs, Tab, Paper } from '@mui/material';
import {
Box, AppBar, Toolbar, Typography, Tabs, Tab, Paper,
IconButton, Menu, MenuItem, ListItemIcon, ListItemText, Divider // Nuevas importaciones
} from '@mui/material';
import AccountCircle from '@mui/icons-material/AccountCircle'; // Icono de usuario
import LockResetIcon from '@mui/icons-material/LockReset'; // Icono para cambiar contraseña
import LogoutIcon from '@mui/icons-material/Logout'; // Icono para cerrar sesión
import { useAuth } from '../contexts/AuthContext';
import ChangePasswordModal from '../components/Modals/Usuarios/ChangePasswordModal';
import { useNavigate, useLocation } from 'react-router-dom'; // Para manejar la navegación y la ruta actual
import { useNavigate, useLocation } from 'react-router-dom';
interface MainLayoutProps {
children: ReactNode;
@@ -18,12 +24,10 @@ const modules = [
{ label: 'Usuarios', path: '/usuarios' },
];
const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
const {
user,
logout,
// ... (resto de las props de useAuth) ...
isAuthenticated,
isPasswordChangeForced,
showForcedPasswordChangeModal,
@@ -32,9 +36,10 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
} = useAuth();
const navigate = useNavigate();
const location = useLocation(); // Para obtener la ruta actual
const location = useLocation();
const [selectedTab, setSelectedTab] = useState<number | false>(false);
const [anchorElUserMenu, setAnchorElUserMenu] = useState<null | HTMLElement>(null); // Estado para el menú de usuario
useEffect(() => {
const currentModulePath = modules.findIndex(module =>
@@ -43,12 +48,30 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
if (currentModulePath !== -1) {
setSelectedTab(currentModulePath);
} else if (location.pathname === '/') {
setSelectedTab(0);
setSelectedTab(0); // Asegurar que la pestaña de Inicio se seleccione para la ruta raíz
} else {
setSelectedTab(false);
setSelectedTab(false); // Ninguna pestaña seleccionada si no coincide
}
}, [location.pathname]);
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElUserMenu(event.currentTarget);
};
const handleCloseUserMenu = () => {
setAnchorElUserMenu(null);
};
const handleChangePasswordClick = () => {
setShowForcedPasswordChangeModal(true);
handleCloseUserMenu();
};
const handleLogoutClick = () => {
logout();
handleCloseUserMenu(); // Cierra el menú antes de desloguear completamente
};
const handleModalClose = (passwordChangedSuccessfully: boolean) => {
if (passwordChangedSuccessfully) {
passwordChangeCompleted();
@@ -70,7 +93,6 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
const isReportesModule = location.pathname.startsWith('/reportes');
if (showForcedPasswordChangeModal && isPasswordChangeForced) {
// ... (lógica del modal forzado sin cambios) ...
return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }}>
<ChangePasswordModal
@@ -84,33 +106,94 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
return (
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
<AppBar position="static">
{/* ... (Toolbar y Tabs sin cambios) ... */}
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
<AppBar position="sticky" elevation={1} /* Elevation sutil para AppBar */>
<Toolbar sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="h6" component="div" noWrap sx={{ cursor: 'pointer' }} onClick={() => navigate('/')}>
Sistema de Gestión - El Día
</Typography>
{user && <Typography sx={{ mr: 2 }}>Hola, {user.nombreCompleto}</Typography>}
{isAuthenticated && !isPasswordChangeForced && (
<Button
color="inherit"
onClick={() => setShowForcedPasswordChangeModal(true)}
>
Cambiar Contraseña
</Button>
)}
<Button color="inherit" onClick={logout}>Cerrar Sesión</Button>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{user && (
<Typography sx={{ mr: 2, display: { xs: 'none', sm: 'block' } }} /* Ocultar en pantallas muy pequeñas */>
Hola, {user.nombreCompleto}
</Typography>
)}
{isAuthenticated && (
<>
<IconButton
size="large"
aria-label="Cuenta del usuario"
aria-controls="menu-appbar"
aria-haspopup="true"
sx={{
padding: '15px',
}}
onClick={handleOpenUserMenu}
color="inherit"
>
<AccountCircle sx={{ fontSize: 36 }} />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorElUserMenu}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
keepMounted
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
open={Boolean(anchorElUserMenu)}
onClose={handleCloseUserMenu}
sx={{ '& .MuiPaper-root': { minWidth: 220, marginTop: '8px' } }}
>
{user && ( // Mostrar info del usuario en el menú
<Box sx={{ px: 2, py: 1.5, pointerEvents: 'none' /* Para que no sea clickeable */ }}>
<Typography variant="subtitle1" sx={{ fontWeight: 'medium' }}>{user.nombreCompleto}</Typography>
<Typography variant="body2" color="text.secondary">{user.username}</Typography>
</Box>
)}
{user && <Divider sx={{ mb: 1 }} />}
{!isPasswordChangeForced && ( // No mostrar si ya está forzado a cambiarla
<MenuItem onClick={handleChangePasswordClick}>
<ListItemIcon><LockResetIcon fontSize="small" /></ListItemIcon>
<ListItemText>Cambiar Contraseña</ListItemText>
</MenuItem>
)}
<MenuItem onClick={handleLogoutClick}>
<ListItemIcon><LogoutIcon fontSize="small" /></ListItemIcon>
<ListItemText>Cerrar Sesión</ListItemText>
</MenuItem>
</Menu>
</>
)}
</Box>
</Toolbar>
<Paper square elevation={0} >
<Tabs
value={selectedTab}
onChange={handleTabChange}
indicatorColor="secondary"
textColor="inherit"
indicatorColor="secondary" // O 'primary' si prefieres el mismo color que el fondo
textColor="inherit" // El texto de la pestaña hereda el color (blanco sobre fondo oscuro)
variant="scrollable"
scrollButtons="auto"
allowScrollButtonsMobile
aria-label="módulos principales"
sx={{ backgroundColor: 'primary.main', color: 'white' }}
sx={{
backgroundColor: 'primary.main', // Color de fondo de las pestañas
color: 'white', // Color del texto de las pestañas
'& .MuiTabs-indicator': {
height: 3, // Un indicador un poco más grueso
},
'& .MuiTab-root': { // Estilo para cada pestaña
minWidth: 100, // Ancho mínimo para cada pestaña
textTransform: 'none', // Evitar MAYÚSCULAS por defecto
fontWeight: 'normal',
opacity: 0.85, // Ligeramente transparentes si no están seleccionadas
'&.Mui-selected': {
fontWeight: 'bold',
opacity: 1,
// color: 'secondary.main' // Opcional: color diferente para la pestaña seleccionada
},
}
}}
>
{modules.map((module) => (
<Tab key={module.path} label={module.label} />
@@ -119,30 +202,30 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
</Paper>
</AppBar>
{/* Contenido del Módulo */}
<Box
component="main"
sx={{
flexGrow: 1,
py: isReportesModule ? 0 : 3, // Padding vertical condicional. Si es el módulo de Reportes, px es 0 si no 3
px: isReportesModule ? 0 : 3, // Padding horizontal condicional. Si es el módulo de Reportes, px es 0 si no 3
display: 'flex', // IMPORTANTE: Para que el hijo (ReportesIndexPage) pueda usar height: '100%'
flexDirection: 'column' // IMPORTANTE
py: isReportesModule ? 0 : { xs: 1.5, sm: 2, md: 2.5 }, // Padding vertical responsivo
px: isReportesModule ? 0 : { xs: 1.5, sm: 2, md: 2.5 }, // Padding horizontal responsivo
display: 'flex',
flexDirection: 'column'
}}
>
{children}
</Box>
<Box component="footer" sx={{ p: 1, mt: 'auto', backgroundColor: 'primary.dark', color: 'white', textAlign: 'center' }}>
<Typography variant="body2">
Usuario: {user?.username} | Acceso: {user?.esSuperAdmin ? 'Super Admin' : `Perfil ID ${user?.userId}`} | Versión: {/* TODO: Obtener versión */}
<Box component="footer" sx={{ p: 1, backgroundColor: 'grey.200' /* Un gris más claro */, color: 'text.secondary', textAlign: 'left', borderTop: (theme) => `1px solid ${theme.palette.divider}` }}>
<Typography variant="caption">
{/* Puedes usar caption para un texto más pequeño en el footer */}
Usuario: {user?.username} | Acceso: {user?.esSuperAdmin ? 'Super Administrador' : (user?.perfil || `ID ${user?.idPerfil}`)}
</Typography>
</Box>
<ChangePasswordModal
open={showForcedPasswordChangeModal}
onClose={handleModalClose}
isFirstLogin={isPasswordChangeForced}
open={showForcedPasswordChangeModal && !isPasswordChangeForced} // Solo mostrar si no es el forzado inicial
onClose={() => handleModalClose(false)} // Asumir que si se cierra sin cambiar, no fue exitoso
isFirstLogin={false} // Este modal no es para el primer login forzado
/>
</Box>
);