Feat: RawData Table Format
This commit is contained in:
@@ -1,33 +1,29 @@
|
||||
import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button } from '@mui/material';
|
||||
import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button, Typography } 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 { CotizacionGanado } from '../../models/mercadoModels';
|
||||
import { formatInteger, formatCurrency, formatFullDateTime } from '../../utils/formatters';
|
||||
import { formatInteger, formatFullDateTime } from '../../utils/formatters';
|
||||
import { copyToClipboard } from '../../utils/clipboardUtils';
|
||||
import { HolidayAlert } from '../common/HolidayAlert';
|
||||
|
||||
/**
|
||||
* Función para convertir los datos de la tabla a un formato CSV para el portapapeles.
|
||||
* Función para convertir los datos a formato TSV (Tab-Separated Values)
|
||||
* con el formato específico solicitado por redacción.
|
||||
*/
|
||||
const toCSV = (headers: string[], data: CotizacionGanado[]) => {
|
||||
const headerRow = headers.join(';');
|
||||
const dataRows = data.map(row =>
|
||||
[
|
||||
row.categoria,
|
||||
row.especificaciones,
|
||||
formatCurrency(row.maximo),
|
||||
formatCurrency(row.minimo),
|
||||
formatCurrency(row.mediano),
|
||||
formatInteger(row.cabezas),
|
||||
formatInteger(row.kilosTotales),
|
||||
formatInteger(row.importeTotal),
|
||||
formatFullDateTime(row.fechaRegistro)
|
||||
].join(';')
|
||||
);
|
||||
return [headerRow, ...dataRows].join('\n');
|
||||
const toTSV = (data: CotizacionGanado[]) => {
|
||||
const dataRows = data.map(row => {
|
||||
// Unimos Categoría y Especificaciones en una sola columna para el copiado
|
||||
const categoriaCompleta = `${row.categoria}/${row.especificaciones}`;
|
||||
const cabezas = formatInteger(row.cabezas);
|
||||
const importeTotal = formatInteger(row.importeTotal);
|
||||
|
||||
return [categoriaCompleta, cabezas, importeTotal].join('\t');
|
||||
});
|
||||
|
||||
return dataRows.join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -35,24 +31,21 @@ const toCSV = (headers: string[], data: CotizacionGanado[]) => {
|
||||
* diseñado para la página de redacción.
|
||||
*/
|
||||
export const RawAgroTable = () => {
|
||||
// Hooks para obtener los datos y el estado de feriado.
|
||||
const { data, loading: dataLoading, error: dataError } = useApiData<CotizacionGanado[]>('/mercados/agroganadero');
|
||||
const isHoliday = useIsHoliday('BA');
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!data) return;
|
||||
const headers = ["Categoría", "Especificaciones", "Máximo", "Mínimo", "Mediano", "Cabezas", "Kg Total", "Importe Total", "Fecha de Registro"];
|
||||
const csvData = toCSV(headers, data);
|
||||
const tsvData = toTSV(data);
|
||||
|
||||
copyToClipboard(csvData)
|
||||
.then(() => alert('¡Tabla copiada al portapapeles!'))
|
||||
copyToClipboard(tsvData)
|
||||
.then(() => alert('Datos del Mercado Agroganadero copiados al portapapeles!'))
|
||||
.catch(err => {
|
||||
console.error('Error al copiar:', err);
|
||||
alert('Error: No se pudo copiar la tabla.');
|
||||
});
|
||||
};
|
||||
|
||||
// Estado de carga unificado.
|
||||
const isLoading = dataLoading || isHoliday === null;
|
||||
|
||||
if (isLoading) return <CircularProgress />;
|
||||
@@ -67,43 +60,37 @@ export const RawAgroTable = () => {
|
||||
|
||||
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>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
||||
<Button startIcon={<ContentCopyIcon />} onClick={handleCopy}>
|
||||
Copiar Datos para Redacción
|
||||
</Button>
|
||||
<Typography variant="caption" sx={{ fontStyle: 'italic', color: 'text.secondary' }}>
|
||||
Última actualización: {formatFullDateTime(data[0].fechaRegistro)}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* La tabla ahora muestra solo las columnas requeridas para facilitar la visualización */}
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Categoría</TableCell>
|
||||
<TableCell>Especificaciones</TableCell>
|
||||
<TableCell align="right">Máximo</TableCell>
|
||||
<TableCell align="right">Mínimo</TableCell>
|
||||
<TableCell align="right">Mediano</TableCell>
|
||||
<TableCell>Categoría / Especificaciones</TableCell>
|
||||
<TableCell align="right">Cabezas</TableCell>
|
||||
<TableCell align="right">Kg Total</TableCell>
|
||||
<TableCell align="right">Importe Total</TableCell>
|
||||
<TableCell>Última Act.</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.map(row => (
|
||||
<TableRow key={row.id}>
|
||||
<TableCell>{row.categoria}</TableCell>
|
||||
<TableCell>{row.especificaciones}</TableCell>
|
||||
<TableCell align="right">${formatCurrency(row.maximo)}</TableCell>
|
||||
<TableCell align="right">${formatCurrency(row.minimo)}</TableCell>
|
||||
<TableCell align="right">${formatCurrency(row.mediano)}</TableCell>
|
||||
<TableCell>{row.categoria} / {row.especificaciones}</TableCell>
|
||||
<TableCell align="right">{formatInteger(row.cabezas)}</TableCell>
|
||||
<TableCell align="right">{formatInteger(row.kilosTotales)}</TableCell>
|
||||
<TableCell align="right">${formatInteger(row.importeTotal)}</TableCell>
|
||||
<TableCell>{formatFullDateTime(row.fechaRegistro)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
@@ -1,105 +1,121 @@
|
||||
import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button } from '@mui/material';
|
||||
import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button, Typography, Divider } 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';
|
||||
import { TICKERS_PRIORITARIOS_LOCAL } from '../../config/priorityTickers';
|
||||
|
||||
/**
|
||||
* Función para convertir los datos de la tabla a formato CSV.
|
||||
* Función para convertir los datos prioritarios a formato TSV (Tab-Separated Values).
|
||||
*/
|
||||
const toCSV = (headers: string[], data: CotizacionBolsa[]) => {
|
||||
const headerRow = headers.join(';');
|
||||
const dataRows = data.map(row =>
|
||||
[
|
||||
row.ticker,
|
||||
row.nombreEmpresa,
|
||||
formatCurrency(row.precioActual),
|
||||
formatCurrency(row.cierreAnterior),
|
||||
`${row.porcentajeCambio.toFixed(2)}%`,
|
||||
formatFullDateTime(row.fechaRegistro)
|
||||
].join(';')
|
||||
);
|
||||
return [headerRow, ...dataRows].join('\n');
|
||||
const toTSV = (data: CotizacionBolsa[]) => {
|
||||
const dataRows = data.map(row => {
|
||||
// Formateamos el nombre para que quede como "GGAL.BA (GRUPO FINANCIERO GALICIA)"
|
||||
const nombreCompleto = `${row.ticker} (${row.nombreEmpresa || ''})`;
|
||||
const precio = `$${formatCurrency(row.precioActual)}`
|
||||
const cambio = `${row.porcentajeCambio.toFixed(2)}%`
|
||||
|
||||
// Unimos los campos con un carácter de tabulación '\t'
|
||||
return [nombreCompleto, precio, cambio].join('\t');
|
||||
});
|
||||
|
||||
// Unimos todas las filas con un salto de línea
|
||||
return 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.
|
||||
* Componente de tabla de datos crudos para la Bolsa Local, adaptado para redacción.
|
||||
*/
|
||||
export const RawBolsaLocalTable = () => {
|
||||
// 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 { data, loading, error } = useApiData<CotizacionBolsa[]>('/mercados/bolsa/local');
|
||||
|
||||
// Separamos los datos en prioritarios y el resto
|
||||
const priorityData = data?.filter(d => TICKERS_PRIORITARIOS_LOCAL.includes(d.ticker))
|
||||
.sort((a, b) => TICKERS_PRIORITARIOS_LOCAL.indexOf(a.ticker) - TICKERS_PRIORITARIOS_LOCAL.indexOf(b.ticker)); // Mantenemos el orden
|
||||
|
||||
const otherData = data?.filter(d => !TICKERS_PRIORITARIOS_LOCAL.includes(d.ticker));
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!data) return;
|
||||
const headers = ["Ticker", "Nombre", "Último Precio", "Cierre Anterior", "Variación %", "Fecha de Registro"];
|
||||
const csvData = toCSV(headers, data);
|
||||
if (!priorityData) return;
|
||||
const tsvData = toTSV(priorityData);
|
||||
|
||||
copyToClipboard(csvData)
|
||||
.then(() => alert('¡Tabla copiada al portapapeles!'))
|
||||
copyToClipboard(tsvData)
|
||||
.then(() => alert('Datos prioritarios copiados al portapapeles.'))
|
||||
.catch(err => {
|
||||
console.error('Error al copiar:', err);
|
||||
alert('Error: No se pudo copiar la tabla.');
|
||||
});
|
||||
};
|
||||
|
||||
// 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>;
|
||||
}
|
||||
if (loading) return <CircularProgress />;
|
||||
if (error) return <Alert severity="error">{error}</Alert>;
|
||||
if (!data || data.length === 0) 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>
|
||||
)}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
||||
<Button startIcon={<ContentCopyIcon />} onClick={handleCopy}>
|
||||
Copiar Datos Principales
|
||||
</Button>
|
||||
<Typography variant="caption" sx={{ fontStyle: 'italic', color: 'text.secondary' }}>
|
||||
Última actualización: {formatFullDateTime(data[0].fechaRegistro)}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Button startIcon={<ContentCopyIcon />} onClick={handleCopy} sx={{ mb: 1 }}>
|
||||
Copiar como CSV
|
||||
</Button>
|
||||
{/* Tabla de Datos Prioritarios */}
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Ticker</TableCell>
|
||||
<TableCell>Nombre</TableCell>
|
||||
<TableCell align="right">Último Precio</TableCell>
|
||||
<TableCell align="right">Cierre Anterior</TableCell>
|
||||
<TableCell align="right">Variación %</TableCell>
|
||||
<TableCell>Última Act.</TableCell>
|
||||
<TableCell>Símbolo (Nombre)</TableCell>
|
||||
<TableCell align="right">Precio Actual</TableCell>
|
||||
<TableCell align="right">% Cambio</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.map(row => (
|
||||
{priorityData?.map(row => (
|
||||
<TableRow key={row.id}>
|
||||
<TableCell>{row.ticker}</TableCell>
|
||||
<TableCell>{row.nombreEmpresa}</TableCell>
|
||||
<TableCell>
|
||||
<Typography component="span" sx={{ fontWeight: 'bold' }}>{row.ticker}</Typography>
|
||||
<Typography component="span" sx={{ ml: 1, color: 'text.secondary' }}>({row.nombreEmpresa})</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">${formatCurrency(row.precioActual)}</TableCell>
|
||||
<TableCell align="right">${formatCurrency(row.cierreAnterior)}</TableCell>
|
||||
<TableCell align="right">{row.porcentajeCambio.toFixed(2)}%</TableCell>
|
||||
<TableCell>{formatFullDateTime(row.fechaRegistro)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{/* Sección para Otros Tickers (solo para consulta) */}
|
||||
{otherData && otherData.length > 0 && (
|
||||
<Box mt={4}>
|
||||
<Divider sx={{ mb: 2 }}>
|
||||
<Typography variant="overline">Otros Tickers (Solo Consulta)</Typography>
|
||||
</Divider>
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Ticker</TableCell>
|
||||
<TableCell>Nombre</TableCell>
|
||||
<TableCell align="right">Precio</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{otherData.map(row => (
|
||||
<TableRow key={row.id}>
|
||||
<TableCell>{row.ticker}</TableCell>
|
||||
<TableCell>{row.nombreEmpresa}</TableCell>
|
||||
<TableCell align="right">${formatCurrency(row.precioActual)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button } from '@mui/material';
|
||||
import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button, Typography, Divider } from '@mui/material';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
|
||||
// Importaciones de nuestro proyecto
|
||||
@@ -8,98 +8,125 @@ import type { CotizacionBolsa } from '../../models/mercadoModels';
|
||||
import { formatCurrency, formatFullDateTime } from '../../utils/formatters';
|
||||
import { copyToClipboard } from '../../utils/clipboardUtils';
|
||||
import { HolidayAlert } from '../common/HolidayAlert';
|
||||
import { TICKERS_PRIORITARIOS_USA } from '../../config/priorityTickers';
|
||||
|
||||
/**
|
||||
* Función para convertir los datos de la tabla a formato CSV.
|
||||
* Función para convertir los datos prioritarios a formato TSV (Tab-Separated Values).
|
||||
*/
|
||||
const toCSV = (headers: string[], data: CotizacionBolsa[]) => {
|
||||
const headerRow = headers.join(';');
|
||||
const dataRows = data.map(row =>
|
||||
[
|
||||
row.ticker,
|
||||
row.nombreEmpresa,
|
||||
formatCurrency(row.precioActual, 'USD'),
|
||||
formatCurrency(row.cierreAnterior, 'USD'),
|
||||
`${row.porcentajeCambio.toFixed(2)}%`,
|
||||
formatFullDateTime(row.fechaRegistro)
|
||||
].join(';')
|
||||
);
|
||||
return [headerRow, ...dataRows].join('\n');
|
||||
const toTSV = (data: CotizacionBolsa[]) => {
|
||||
const dataRows = data.map(row => {
|
||||
// Formateamos los datos según los requisitos de redacción
|
||||
const nombreCompleto = `${row.ticker} (${row.nombreEmpresa || ''})`;
|
||||
const precio = `$${formatCurrency(row.precioActual)}`;
|
||||
const cambio = `${row.porcentajeCambio.toFixed(2)}%`;
|
||||
|
||||
return [nombreCompleto, precio, cambio].join('\t');
|
||||
});
|
||||
return dataRows.join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* Componente de tabla de datos crudos para la Bolsa de EEUU y ADRs,
|
||||
* diseñado para la página de redacción.
|
||||
* adaptado para las necesidades de redacción.
|
||||
*/
|
||||
export const RawBolsaUsaTable = () => {
|
||||
// Hooks para obtener los datos y el estado de feriado para el mercado de EEUU.
|
||||
const { data, loading: dataLoading, error: dataError } = useApiData<CotizacionBolsa[]>('/mercados/bolsa/eeuu');
|
||||
const isHoliday = useIsHoliday('US');
|
||||
|
||||
// Separamos los datos en prioritarios y el resto, manteniendo el orden de la lista
|
||||
const priorityData = data?.filter(d => TICKERS_PRIORITARIOS_USA.includes(d.ticker))
|
||||
.sort((a, b) => TICKERS_PRIORITARIOS_USA.indexOf(a.ticker) - TICKERS_PRIORITARIOS_USA.indexOf(b.ticker));
|
||||
|
||||
const otherData = data?.filter(d => !TICKERS_PRIORITARIOS_USA.includes(d.ticker));
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!data) return;
|
||||
const headers = ["Ticker", "Nombre", "Último Precio (USD)", "Cierre Anterior (USD)", "Variación %", "Fecha de Registro"];
|
||||
const csvData = toCSV(headers, data);
|
||||
if (!priorityData) return;
|
||||
const tsvData = toTSV(priorityData);
|
||||
|
||||
copyToClipboard(csvData)
|
||||
.then(() => alert('¡Tabla copiada al portapapeles!'))
|
||||
copyToClipboard(tsvData)
|
||||
.then(() => alert('Datos prioritarios copiados al portapapeles!'))
|
||||
.catch(err => {
|
||||
console.error('Error al copiar:', err);
|
||||
alert('Error: No se pudo copiar la tabla.');
|
||||
});
|
||||
};
|
||||
|
||||
// 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 de EEUU (el fetcher puede estar desactivado).</Alert>;
|
||||
if (isHoliday) { return <HolidayAlert />; }
|
||||
return <Alert severity="info">No hay datos disponibles para el mercado de EEUU.</Alert>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* Si es feriado, mostramos una alerta informativa encima de la tabla. */}
|
||||
{isHoliday && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<HolidayAlert />
|
||||
</Box>
|
||||
)}
|
||||
{isHoliday && <Box sx={{ mb: 2 }}><HolidayAlert /></Box>}
|
||||
|
||||
<Button startIcon={<ContentCopyIcon />} onClick={handleCopy} sx={{ mb: 1 }}>
|
||||
Copiar como CSV
|
||||
</Button>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
||||
<Button startIcon={<ContentCopyIcon />} onClick={handleCopy}>
|
||||
Copiar Datos Principales
|
||||
</Button>
|
||||
<Typography variant="caption" sx={{ fontStyle: 'italic', color: 'text.secondary' }}>
|
||||
Última actualización: {formatFullDateTime(data[0].fechaRegistro)}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Tabla de Datos Prioritarios */}
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Ticker</TableCell>
|
||||
<TableCell>Nombre</TableCell>
|
||||
<TableCell align="right">Último Precio</TableCell>
|
||||
<TableCell align="right">Cierre Anterior</TableCell>
|
||||
<TableCell align="right">Variación %</TableCell>
|
||||
<TableCell>Última Act.</TableCell>
|
||||
<TableCell>Símbolo (Nombre)</TableCell>
|
||||
<TableCell align="right">Precio Actual</TableCell>
|
||||
<TableCell align="right">% Cambio</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.map(row => (
|
||||
{priorityData?.map(row => (
|
||||
<TableRow key={row.id}>
|
||||
<TableCell>{row.ticker}</TableCell>
|
||||
<TableCell>{row.nombreEmpresa}</TableCell>
|
||||
<TableCell>
|
||||
<Typography component="span" sx={{ fontWeight: 'bold' }}>{row.ticker}</Typography>
|
||||
<Typography component="span" sx={{ ml: 1, color: 'text.secondary' }}>({row.nombreEmpresa})</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">{formatCurrency(row.precioActual, 'USD')}</TableCell>
|
||||
<TableCell align="right">{formatCurrency(row.cierreAnterior, 'USD')}</TableCell>
|
||||
<TableCell align="right">{row.porcentajeCambio.toFixed(2)}%</TableCell>
|
||||
<TableCell>{formatFullDateTime(row.fechaRegistro)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{/* Sección para Otros Tickers (solo para consulta) */}
|
||||
{otherData && otherData.length > 0 && (
|
||||
<Box mt={4}>
|
||||
<Divider sx={{ mb: 2 }}>
|
||||
<Typography variant="overline">Otros Tickers (Solo Consulta)</Typography>
|
||||
</Divider>
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Ticker</TableCell>
|
||||
<TableCell>Nombre</TableCell>
|
||||
<TableCell align="right">Precio</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{otherData.map(row => (
|
||||
<TableRow key={row.id}>
|
||||
<TableCell>{row.ticker}</TableCell>
|
||||
<TableCell>{row.nombreEmpresa}</TableCell>
|
||||
<TableCell align="right">{formatCurrency(row.precioActual, 'USD')}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,29 +1,32 @@
|
||||
import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button } from '@mui/material';
|
||||
import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button, Typography } 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 { CotizacionGrano } from '../../models/mercadoModels';
|
||||
import { formatInteger, formatDateOnly, formatFullDateTime } from '../../utils/formatters';
|
||||
import { formatInteger, formatFullDateTime } from '../../utils/formatters';
|
||||
import { copyToClipboard } from '../../utils/clipboardUtils';
|
||||
import { HolidayAlert } from '../common/HolidayAlert';
|
||||
|
||||
/**
|
||||
* Función para convertir los datos de la tabla a formato CSV.
|
||||
* Función para convertir los datos a formato TSV (Tab-Separated Values)
|
||||
* con el formato específico solicitado por redacción.
|
||||
*/
|
||||
const toCSV = (headers: string[], data: CotizacionGrano[]) => {
|
||||
const headerRow = headers.join(';');
|
||||
const dataRows = data.map(row =>
|
||||
[
|
||||
row.nombre,
|
||||
formatInteger(row.precio),
|
||||
formatInteger(row.variacionPrecio),
|
||||
formatDateOnly(row.fechaOperacion),
|
||||
formatFullDateTime(row.fechaRegistro)
|
||||
].join(';')
|
||||
);
|
||||
return [headerRow, ...dataRows].join('\n');
|
||||
const toTSV = (data: CotizacionGrano[]) => {
|
||||
const dataRows = data.map(row => {
|
||||
// Formateamos la variación para que muestre "=" si es cero.
|
||||
const variacion = row.variacionPrecio === 0
|
||||
? '= 0'
|
||||
: formatInteger(row.variacionPrecio);
|
||||
|
||||
const precio = formatInteger(row.precio);
|
||||
|
||||
// Unimos los campos con un carácter de tabulación '\t'
|
||||
return [row.nombre, precio, variacion].join('\t');
|
||||
});
|
||||
|
||||
return dataRows.join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -31,24 +34,21 @@ const toCSV = (headers: string[], data: CotizacionGrano[]) => {
|
||||
* diseñado para la página de redacción.
|
||||
*/
|
||||
export const RawGranosTable = () => {
|
||||
// Hooks para obtener los datos y el estado de feriado para el mercado argentino.
|
||||
const { data, loading: dataLoading, error: dataError } = useApiData<CotizacionGrano[]>('/mercados/granos');
|
||||
const isHoliday = useIsHoliday('BA');
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!data) return;
|
||||
const headers = ["Grano", "Precio ($/Tn)", "Variación", "Fecha Op.", "Fecha de Registro"];
|
||||
const csvData = toCSV(headers, data);
|
||||
const tsvData = toTSV(data);
|
||||
|
||||
copyToClipboard(csvData)
|
||||
.then(() => alert('¡Tabla copiada al portapapeles!'))
|
||||
copyToClipboard(tsvData)
|
||||
.then(() => alert('Datos de Granos copiados al portapapeles!'))
|
||||
.catch(err => {
|
||||
console.error('Error al copiar:', err);
|
||||
alert('Error: No se pudo copiar la tabla.');
|
||||
});
|
||||
};
|
||||
|
||||
// Estado de carga unificado.
|
||||
const isLoading = dataLoading || isHoliday === null;
|
||||
|
||||
if (isLoading) return <CircularProgress />;
|
||||
@@ -63,16 +63,21 @@ export const RawGranosTable = () => {
|
||||
|
||||
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>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
||||
<Button startIcon={<ContentCopyIcon />} onClick={handleCopy}>
|
||||
Copiar Datos para Redacción
|
||||
</Button>
|
||||
<Typography variant="caption" sx={{ fontStyle: 'italic', color: 'text.secondary' }}>
|
||||
Última actualización: {formatFullDateTime(data[0].fechaRegistro)}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
@@ -80,8 +85,6 @@ export const RawGranosTable = () => {
|
||||
<TableCell>Grano</TableCell>
|
||||
<TableCell align="right">Precio ($/Tn)</TableCell>
|
||||
<TableCell align="right">Variación</TableCell>
|
||||
<TableCell>Fecha Op.</TableCell>
|
||||
<TableCell>Última Act.</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
@@ -89,9 +92,7 @@ export const RawGranosTable = () => {
|
||||
<TableRow key={row.id}>
|
||||
<TableCell>{row.nombre}</TableCell>
|
||||
<TableCell align="right">${formatInteger(row.precio)}</TableCell>
|
||||
<TableCell align="right">{formatInteger(row.variacionPrecio)}</TableCell>
|
||||
<TableCell>{formatDateOnly(row.fechaOperacion)}</TableCell>
|
||||
<TableCell>{formatFullDateTime(row.fechaRegistro)}</TableCell>
|
||||
<TableCell align="right">{row.variacionPrecio === 0 ? '= 0' : formatInteger(row.variacionPrecio)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
11
frontend/src/config/priorityTickers.ts
Normal file
11
frontend/src/config/priorityTickers.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const TICKERS_PRIORITARIOS_LOCAL = [
|
||||
'^MERV', 'GGAL.BA', 'YPFD.BA', 'PAMP.BA', 'BMA.BA',
|
||||
'COME.BA', 'TECO2.BA', 'EDN.BA', 'CRES.BA', 'TXAR.BA',
|
||||
'MIRG.BA', 'CEPU.BA', 'LOMA.BA', 'VALO.BA'
|
||||
];
|
||||
|
||||
// Dejaremos las otras listas aquí para los siguientes componentes
|
||||
export const TICKERS_PRIORITARIOS_USA = [
|
||||
'AAPL', 'AMD', 'AMZN', 'BRK-B', 'KO', 'MSFT', 'NVDA',
|
||||
'GLD', 'XLF', 'XLI', 'XLE', 'XLK', 'MELI'
|
||||
];
|
||||
Reference in New Issue
Block a user