| 
									
										
										
										
											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'; | 
					
						
							| 
									
										
										
										
											2025-10-28 13:19:24 -03:00
										 |  |  | import { Box, Button, Typography, Stack, Chip, CircularProgress } 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'; | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  | import EditarTitularModal from './EditarTitularModal'; | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | const Dashboard = () => { | 
					
						
							|  |  |  |   const [titulares, setTitulares] = useState<Titular[]>([]); | 
					
						
							|  |  |  |   const [modalOpen, setModalOpen] = useState(false); | 
					
						
							| 
									
										
										
										
											2025-10-28 13:19:24 -03:00
										 |  |  |   const [isGeneratingCsv, setIsGeneratingCsv] = useState(false); | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  |   const [titularAEditar, setTitularAEditar] = useState<Titular | null>(null); | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 13:19:24 -03:00
										 |  |  |   const handleGenerateCsv = async () => { | 
					
						
							|  |  |  |     setIsGeneratingCsv(true); | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       await api.generarCsvManual(); | 
					
						
							|  |  |  |       // Opcional: mostrar una notificación de éxito
 | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       console.error("Error al generar CSV manualmente", error); | 
					
						
							|  |  |  |       // Opcional: mostrar una notificación de error
 | 
					
						
							|  |  |  |     } finally { | 
					
						
							|  |  |  |       setIsGeneratingCsv(false); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  |   const handleSaveEdit = async (id: number, texto: string, viñeta: string) => { | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       await api.actualizarTitular(id, { texto, viñeta: viñeta || null }); | 
					
						
							|  |  |  |       // SignalR se encargará de actualizar la UI
 | 
					
						
							|  |  |  |     } catch (err) { | 
					
						
							|  |  |  |       console.error("Error al guardar cambios:", err); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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}> | 
					
						
							| 
									
										
										
										
											2025-10-28 13:19:24 -03:00
										 |  |  |           <Button | 
					
						
							|  |  |  |             variant="outlined" | 
					
						
							|  |  |  |             startIcon={isGeneratingCsv ? <CircularProgress size={20} /> : <SyncIcon />} | 
					
						
							|  |  |  |             onClick={handleGenerateCsv} | 
					
						
							|  |  |  |             disabled={isGeneratingCsv} | 
					
						
							|  |  |  |           > | 
					
						
							|  |  |  |             {isGeneratingCsv ? 'Generando...' : 'Generate CSV'} | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |           </Button> | 
					
						
							|  |  |  |           <Button variant="contained" startIcon={<AddIcon />} onClick={() => setModalOpen(true)}> | 
					
						
							|  |  |  |             Add Manual | 
					
						
							|  |  |  |           </Button> | 
					
						
							|  |  |  |         </Stack> | 
					
						
							|  |  |  |       </Box> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       <FormularioConfiguracion /> | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  |       <TablaTitulares | 
					
						
							|  |  |  |         titulares={titulares} | 
					
						
							|  |  |  |         onReorder={handleReorder} | 
					
						
							|  |  |  |         onDelete={handleDelete} | 
					
						
							|  |  |  |         onEdit={(titular) => setTitularAEditar(titular)} | 
					
						
							|  |  |  |       /> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       <AddTitularModal | 
					
						
							|  |  |  |         open={modalOpen} | 
					
						
							|  |  |  |         onClose={() => setModalOpen(false)} | 
					
						
							|  |  |  |         onAdd={handleAdd} | 
					
						
							|  |  |  |       /> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       <EditarTitularModal | 
					
						
							|  |  |  |         open={titularAEditar !== null} | 
					
						
							|  |  |  |         onClose={() => setTitularAEditar(null)} | 
					
						
							|  |  |  |         onSave={handleSaveEdit} | 
					
						
							|  |  |  |         titular={titularAEditar} | 
					
						
							|  |  |  |       /> | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |     </> | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default Dashboard; |