Feat Skeleton Table de Carga - Fix Freno del Proceso al Cerrar Web
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
// frontend/src/components/Dashboard.tsx
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import {
|
||||
Box, Button, Stack, Chip, CircularProgress,
|
||||
Accordion, AccordionSummary, AccordionDetails, Typography
|
||||
} from '@mui/material';
|
||||
import { Box, Button, Stack, Chip, CircularProgress, Accordion, AccordionSummary, AccordionDetails, Typography } from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import SyncIcon from '@mui/icons-material/Sync';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
@@ -18,6 +17,7 @@ import EditarTitularModal from './EditarTitularModal';
|
||||
import { PowerSwitch } from './PowerSwitch';
|
||||
import ConfirmationModal from './ConfirmationModal';
|
||||
import type { ActualizarTitularPayload } from '../services/apiService';
|
||||
import { TableSkeleton } from './TableSkeleton';
|
||||
|
||||
const Dashboard = () => {
|
||||
const [titulares, setTitulares] = useState<Titular[]>([]);
|
||||
@@ -26,9 +26,10 @@ const Dashboard = () => {
|
||||
const [isGeneratingCsv, setIsGeneratingCsv] = useState(false);
|
||||
const [confirmState, setConfirmState] = useState<{ open: boolean; onConfirm: (() => void) | null }>({ open: false, onConfirm: null });
|
||||
const { showNotification } = useNotification();
|
||||
|
||||
const [titularAEditar, setTitularAEditar] = useState<Titular | null>(null);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const onTitularesActualizados = useCallback((titularesActualizados: Titular[]) => {
|
||||
setTitulares(titularesActualizados);
|
||||
}, []);
|
||||
@@ -38,11 +39,15 @@ const Dashboard = () => {
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
// 1. Cargamos la configuración persistente
|
||||
const fetchConfig = api.obtenerConfiguracion();
|
||||
|
||||
// 2. Preguntamos al servidor por el estado ACTUAL del proceso
|
||||
const fetchEstado = api.getEstadoProceso();
|
||||
|
||||
Promise.all([fetchConfig, fetchEstado])
|
||||
.then(([configData, estadoData]) => {
|
||||
// Construimos el estado de la UI para que REFLEJE el estado real del servidor
|
||||
setConfig({
|
||||
...configData,
|
||||
scrapingActivo: estadoData.activo
|
||||
@@ -50,8 +55,13 @@ const Dashboard = () => {
|
||||
})
|
||||
.catch(error => console.error("Error al cargar datos iniciales:", error));
|
||||
|
||||
api.obtenerTitulares().then(setTitulares);
|
||||
}, []);
|
||||
// La carga de titulares sigue igual
|
||||
api.obtenerTitulares()
|
||||
.then(setTitulares)
|
||||
.catch(error => console.error("Error al cargar titulares:", error))
|
||||
.finally(() => setIsLoading(false));
|
||||
|
||||
}, []); // El array vacío asegura que esto solo se ejecute una vez
|
||||
|
||||
const handleDelete = (id: number) => {
|
||||
const onConfirm = async () => {
|
||||
@@ -188,13 +198,17 @@ const Dashboard = () => {
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
<TablaTitulares
|
||||
titulares={titulares}
|
||||
onReorder={handleReorder}
|
||||
onDelete={handleDelete}
|
||||
onEdit={(titular) => setTitularAEditar(titular)}
|
||||
onSave={handleSaveEdit}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<TableSkeleton />
|
||||
) : (
|
||||
<TablaTitulares
|
||||
titulares={titulares}
|
||||
onReorder={handleReorder}
|
||||
onDelete={handleDelete}
|
||||
onEdit={(titular) => setTitularAEditar(titular)}
|
||||
onSave={handleSaveEdit}
|
||||
/>
|
||||
)}
|
||||
|
||||
<AddTitularModal
|
||||
open={addModalOpen}
|
||||
|
||||
51
frontend/src/components/TableSkeleton.tsx
Normal file
51
frontend/src/components/TableSkeleton.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
// frontend/src/components/TableSkeleton.tsx
|
||||
|
||||
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Skeleton } from '@mui/material';
|
||||
|
||||
// Un componente para una única fila de esqueleto
|
||||
const SkeletonRow = () => (
|
||||
<TableRow>
|
||||
<TableCell sx={{ padding: '8px 16px', width: 50 }}>
|
||||
<Skeleton variant="circular" width={24} height={24} />
|
||||
</TableCell>
|
||||
<TableCell sx={{ padding: '8px 16px' }}>
|
||||
<Skeleton variant="text" sx={{ fontSize: '1rem' }} />
|
||||
</TableCell>
|
||||
<TableCell sx={{ padding: '8px 16px' }}>
|
||||
<Skeleton variant="rounded" width={60} height={22} />
|
||||
</TableCell>
|
||||
<TableCell sx={{ padding: '8px 16px' }}>
|
||||
<Skeleton variant="text" width={80} />
|
||||
</TableCell>
|
||||
<TableCell sx={{ padding: '8px 16px' }} align="right">
|
||||
<Skeleton variant="text" width={60} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
// El componente principal que renderiza la tabla fantasma
|
||||
export const TableSkeleton = () => {
|
||||
return (
|
||||
<Paper elevation={0} sx={{ overflow: 'hidden' }}>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow sx={{ '& .MuiTableCell-root': { padding: '6px 16px', borderBottom: '1px solid rgba(255, 255, 255, 0.12)' } }}>
|
||||
<TableCell sx={{ width: 50 }} />
|
||||
<TableCell sx={{ textTransform: 'uppercase', color: 'text.secondary', letterSpacing: '0.05em' }}>Texto del Titular</TableCell>
|
||||
<TableCell sx={{ textTransform: 'uppercase', color: 'text.secondary', letterSpacing: '0.05em' }}>Tipo</TableCell>
|
||||
<TableCell sx={{ textTransform: 'uppercase', color: 'text.secondary', letterSpacing: '0.05em' }}>Fuente</TableCell>
|
||||
<TableCell sx={{ textTransform: 'uppercase', color: 'text.secondary', letterSpacing: '0.05em', textAlign: 'right' }}>Acciones</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{/* Creamos un array de 5 elementos para renderizar 5 filas de esqueleto */}
|
||||
{[...Array(5)].map((_, index) => (
|
||||
<SkeletonRow key={index} />
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user