feat(UI): Implement holiday awareness across all data widgets

- All data widgets (tables and cards) now use the useIsHoliday hook.
- An informational alert is displayed on holidays, without hiding the last available data.
- Unifies loading state logic to wait for both data and holiday status, preventing race conditions.
- Ensures a consistent and robust user experience across all components."
This commit is contained in:
2025-07-15 15:20:36 -03:00
parent e3339fff99
commit 8878ec632e
12 changed files with 533 additions and 243 deletions

View File

@@ -1,11 +1,17 @@
import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button } from '@mui/material';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
// Importaciones de nuestro proyecto
import { useApiData } from '../../hooks/useApiData';
import { useIsHoliday } from '../../hooks/useIsHoliday';
import type { CotizacionBolsa } from '../../models/mercadoModels';
import { formatCurrency, formatFullDateTime } from '../../utils/formatters';
import { copyToClipboard } from '../../utils/clipboardUtils';
import { HolidayAlert } from '../common/HolidayAlert';
// Función para convertir datos a formato CSV
/**
* Función para convertir los datos de la tabla a formato CSV.
*/
const toCSV = (headers: string[], data: CotizacionBolsa[]) => {
const headerRow = headers.join(';');
const dataRows = data.map(row =>
@@ -14,36 +20,57 @@ const toCSV = (headers: string[], data: CotizacionBolsa[]) => {
row.nombreEmpresa,
formatCurrency(row.precioActual),
formatCurrency(row.cierreAnterior),
`${row.porcentajeCambio.toFixed(2)}%`
`${row.porcentajeCambio.toFixed(2)}%`,
formatFullDateTime(row.fechaRegistro)
].join(';')
);
return [headerRow, ...dataRows].join('\n');
};
/**
* Componente de tabla de datos crudos para la Bolsa Local (MERVAL y acciones),
* diseñado para la página de redacción.
*/
export const RawBolsaLocalTable = () => {
const { data, loading, error } = useApiData<CotizacionBolsa[]>('/mercados/bolsa/local');
// Hooks para obtener los datos y el estado de feriado.
const { data, loading: dataLoading, error: dataError } = useApiData<CotizacionBolsa[]>('/mercados/bolsa/local');
const isHoliday = useIsHoliday('BA');
const handleCopy = () => {
if (!data) return;
const headers = ["Ticker", "Nombre", "Último Precio", "Cierre Anterior", "Variación %"];
const headers = ["Ticker", "Nombre", "Último Precio", "Cierre Anterior", "Variación %", "Fecha de Registro"];
const csvData = toCSV(headers, data);
copyToClipboard(csvData)
.then(() => {
alert('¡Tabla copiada al portapapeles!');
})
.then(() => alert('¡Tabla copiada al portapapeles!'))
.catch(err => {
console.error('Error al copiar:', err);
alert('Error: No se pudo copiar la tabla.');
});
};
if (loading) return <CircularProgress />;
if (error) return <Alert severity="error">{error}</Alert>;
if (!data) return null;
// Estado de carga unificado.
const isLoading = dataLoading || isHoliday === null;
if (isLoading) return <CircularProgress />;
if (dataError) return <Alert severity="error">{dataError}</Alert>;
if (!data || data.length === 0) {
if (isHoliday) {
return <HolidayAlert />;
}
return <Alert severity="info">No hay datos disponibles para el mercado local.</Alert>;
}
return (
<Box>
{/* Si es feriado, mostramos una alerta informativa encima de la tabla. */}
{isHoliday && (
<Box sx={{ mb: 2 }}>
<HolidayAlert />
</Box>
)}
<Button startIcon={<ContentCopyIcon />} onClick={handleCopy} sx={{ mb: 1 }}>
Copiar como CSV
</Button>