Fase 3: Refactorizado SignalR a un hook reutilizable (useSignalR) y conectado al Dashboard.

This commit is contained in:
2025-10-28 12:26:49 -03:00
parent 7eee798c99
commit 9be62937bd
8 changed files with 347 additions and 54 deletions

View File

@@ -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} />
</>
);