Fase 3: Refactorizado SignalR a un hook reutilizable (useSignalR) y conectado al Dashboard.
This commit is contained in:
		| @@ -1,11 +1,13 @@ | ||||
| // frontend/src/components/Dashboard.tsx | ||||
|  | ||||
| import { useEffect, useState } from 'react'; | ||||
| import { Box, Button, Typography, Stack } from '@mui/material'; | ||||
| import { useEffect, useState, useCallback } from 'react'; | ||||
| import { Box, Button, Typography, Stack, Chip } from '@mui/material'; | ||||
| import AddIcon from '@mui/icons-material/Add'; | ||||
| import SyncIcon from '@mui/icons-material/Sync'; | ||||
|  | ||||
| import type { Titular } from '../types'; | ||||
| import * as api from '../services/apiService'; | ||||
| import { useSignalR } from '../hooks/useSignalR'; | ||||
| import FormularioConfiguracion from './FormularioConfiguracion'; | ||||
| import TablaTitulares from './TablaTitulares'; | ||||
| import AddTitularModal from './AddTitularModal'; | ||||
| @@ -14,30 +16,36 @@ const Dashboard = () => { | ||||
|   const [titulares, setTitulares] = useState<Titular[]>([]); | ||||
|   const [modalOpen, setModalOpen] = useState(false); | ||||
|  | ||||
|   const cargarTitulares = async () => { | ||||
|     try { | ||||
|       const data = await api.obtenerTitulares(); | ||||
|       setTitulares(data); | ||||
|     } catch (error) { | ||||
|       console.error("Error al cargar titulares:", error); | ||||
|     } | ||||
|   }; | ||||
|   // 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 } | ||||
|   ]); | ||||
|  | ||||
|   // La carga inicial de datos sigue siendo necesaria por si el componente se monta | ||||
|   // antes de que llegue la primera notificación de SignalR. | ||||
|   useEffect(() => { | ||||
|     cargarTitulares(); | ||||
|     api.obtenerTitulares() | ||||
|       .then(setTitulares) | ||||
|       .catch(error => console.error("Error al cargar titulares:", error)); | ||||
|   }, []); | ||||
|  | ||||
|   const handleReorder = async (titularesReordenados: Titular[]) => { | ||||
|     setTitulares(titularesReordenados); // Actualización optimista de la UI | ||||
|     const payload = titularesReordenados.map((item, index) => ({ | ||||
|       id: item.id, | ||||
|       nuevoOrden: index | ||||
|     })); | ||||
|     setTitulares(titularesReordenados); | ||||
|     const payload = titularesReordenados.map((item, index) => ({ id: item.id, nuevoOrden: index })); | ||||
|     try { | ||||
|       await api.actualizarOrdenTitulares(payload); | ||||
|       // Ya no necesitamos hacer nada más, SignalR notificará a todos los clientes. | ||||
|     } catch (err) { | ||||
|       console.error("Error al reordenar:", err); | ||||
|       cargarTitulares(); // Revertir en caso de error | ||||
|       // En caso de error, volvemos a pedir los datos para no tener un estado inconsistente. | ||||
|       api.obtenerTitulares().then(setTitulares); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
| @@ -45,7 +53,7 @@ const Dashboard = () => { | ||||
|     if (window.confirm('¿Estás seguro de que quieres eliminar este titular?')) { | ||||
|       try { | ||||
|         await api.eliminarTitular(id); | ||||
|         setTitulares(titulares.filter(t => t.id !== id)); // Actualizar UI | ||||
|         // SignalR se encargará de actualizar el estado. | ||||
|       } catch (err) { | ||||
|         console.error("Error al eliminar:", err); | ||||
|       } | ||||
| @@ -55,18 +63,33 @@ const Dashboard = () => { | ||||
|   const handleAdd = async (texto: string) => { | ||||
|     try { | ||||
|       await api.crearTitularManual(texto); | ||||
|       cargarTitulares(); // Recargar la lista para ver el nuevo titular | ||||
|       // SignalR se encargará de actualizar el estado. | ||||
|     } catch (err) { | ||||
|       console.error("Error al añadir titular:", err); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   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" />; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}> | ||||
|         <Typography variant="h4" component="h1"> | ||||
|           Titulares Dashboard | ||||
|         </Typography> | ||||
|         <Stack direction="row" spacing={2} alignItems="center"> | ||||
|           <Typography variant="h4" component="h1"> | ||||
|             Titulares Dashboard | ||||
|           </Typography> | ||||
|           {getStatusChip()} | ||||
|         </Stack> | ||||
|         <Stack direction="row" spacing={2}> | ||||
|           <Button variant="outlined" startIcon={<SyncIcon />}> | ||||
|             Generate CSV | ||||
| @@ -79,7 +102,6 @@ const Dashboard = () => { | ||||
|  | ||||
|       <FormularioConfiguracion /> | ||||
|       <TablaTitulares titulares={titulares} onReorder={handleReorder} onDelete={handleDelete} /> | ||||
|  | ||||
|       <AddTitularModal open={modalOpen} onClose={() => setModalOpen(false)} onAdd={handleAdd} /> | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user