| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | // frontend/src/components/Dashboard.tsx
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  | import { useEffect, useState, useCallback } from 'react'; | 
					
						
							|  |  |  | import { Box, Button, Typography, Stack, Chip } from '@mui/material'; | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | import AddIcon from '@mui/icons-material/Add'; | 
					
						
							|  |  |  | import SyncIcon from '@mui/icons-material/Sync'; | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | import type { Titular } from '../types'; | 
					
						
							|  |  |  | import * as api from '../services/apiService'; | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  | import { useSignalR } from '../hooks/useSignalR'; | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | import FormularioConfiguracion from './FormularioConfiguracion'; | 
					
						
							|  |  |  | import TablaTitulares from './TablaTitulares'; | 
					
						
							|  |  |  | import AddTitularModal from './AddTitularModal'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const Dashboard = () => { | 
					
						
							|  |  |  |   const [titulares, setTitulares] = useState<Titular[]>([]); | 
					
						
							|  |  |  |   const [modalOpen, setModalOpen] = useState(false); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |   // Usamos useCallback para que la función de callback no se recree en cada render,
 | 
					
						
							|  |  |  |   // evitando que el useEffect del hook se ejecute innecesariamente.
 | 
					
						
							|  |  |  |   const onTitularesActualizados = useCallback((titularesActualizados: Titular[]) => { | 
					
						
							|  |  |  |     console.log("Datos recibidos desde SignalR:", titularesActualizados); | 
					
						
							|  |  |  |     setTitulares(titularesActualizados); | 
					
						
							|  |  |  |   }, []); // El array vacío significa que esta función nunca cambiará
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Usamos nuestro hook y le pasamos el evento que nos interesa escuchar
 | 
					
						
							|  |  |  |   const { connectionStatus } = useSignalR([ | 
					
						
							|  |  |  |     { eventName: 'TitularesActualizados', callback: onTitularesActualizados } | 
					
						
							|  |  |  |   ]); | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |   // La carga inicial de datos sigue siendo necesaria por si el componente se monta
 | 
					
						
							|  |  |  |   // antes de que llegue la primera notificación de SignalR.
 | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |   useEffect(() => { | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |     api.obtenerTitulares() | 
					
						
							|  |  |  |       .then(setTitulares) | 
					
						
							|  |  |  |       .catch(error => console.error("Error al cargar titulares:", error)); | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |   }, []); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const handleReorder = async (titularesReordenados: Titular[]) => { | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |     setTitulares(titularesReordenados); | 
					
						
							|  |  |  |     const payload = titularesReordenados.map((item, index) => ({ id: item.id, nuevoOrden: index })); | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |     try { | 
					
						
							|  |  |  |       await api.actualizarOrdenTitulares(payload); | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |       // Ya no necesitamos hacer nada más, SignalR notificará a todos los clientes.
 | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |     } catch (err) { | 
					
						
							|  |  |  |       console.error("Error al reordenar:", err); | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |       // En caso de error, volvemos a pedir los datos para no tener un estado inconsistente.
 | 
					
						
							|  |  |  |       api.obtenerTitulares().then(setTitulares); | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const handleDelete = async (id: number) => { | 
					
						
							|  |  |  |     if (window.confirm('¿Estás seguro de que quieres eliminar este titular?')) { | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         await api.eliminarTitular(id); | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |         // SignalR se encargará de actualizar el estado.
 | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |       } catch (err) { | 
					
						
							|  |  |  |         console.error("Error al eliminar:", err); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const handleAdd = async (texto: string) => { | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       await api.crearTitularManual(texto); | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |       // SignalR se encargará de actualizar el estado.
 | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |     } catch (err) { | 
					
						
							|  |  |  |       console.error("Error al añadir titular:", err); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |   const getStatusChip = () => { | 
					
						
							|  |  |  |     switch (connectionStatus) { | 
					
						
							|  |  |  |       case 'Connected': | 
					
						
							|  |  |  |         return <Chip label="Conectado" color="success" size="small" />; | 
					
						
							|  |  |  |       case 'Reconnecting': | 
					
						
							|  |  |  |       case 'Connecting': | 
					
						
							|  |  |  |         return <Chip label="Conectando..." color="warning" size="small" />; | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |         return <Chip label="Desconectado" color="error" size="small" />; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |   return ( | 
					
						
							|  |  |  |     <> | 
					
						
							|  |  |  |       <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}> | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |         <Stack direction="row" spacing={2} alignItems="center"> | 
					
						
							|  |  |  |           <Typography variant="h4" component="h1"> | 
					
						
							|  |  |  |             Titulares Dashboard | 
					
						
							|  |  |  |           </Typography> | 
					
						
							|  |  |  |           {getStatusChip()} | 
					
						
							|  |  |  |         </Stack> | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |         <Stack direction="row" spacing={2}> | 
					
						
							|  |  |  |           <Button variant="outlined" startIcon={<SyncIcon />}> | 
					
						
							|  |  |  |             Generate CSV | 
					
						
							|  |  |  |           </Button> | 
					
						
							|  |  |  |           <Button variant="contained" startIcon={<AddIcon />} onClick={() => setModalOpen(true)}> | 
					
						
							|  |  |  |             Add Manual | 
					
						
							|  |  |  |           </Button> | 
					
						
							|  |  |  |         </Stack> | 
					
						
							|  |  |  |       </Box> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       <FormularioConfiguracion /> | 
					
						
							|  |  |  |       <TablaTitulares titulares={titulares} onReorder={handleReorder} onDelete={handleDelete} /> | 
					
						
							|  |  |  |       <AddTitularModal open={modalOpen} onClose={() => setModalOpen(false)} onAdd={handleAdd} /> | 
					
						
							|  |  |  |     </> | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default Dashboard; |