Finalización de Reportes y arreglos varios de controles y comportamientos...
This commit is contained in:
		| @@ -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> | ||||
|     ); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user