feat: Add visual summary cards for Agro/Grains and implement 24h time format

This commit is contained in:
2025-07-01 16:05:26 -03:00
parent c4eead1033
commit ab9e77fa81
22 changed files with 908 additions and 125 deletions

View File

@@ -0,0 +1,79 @@
import { Box, CircularProgress, Alert, Paper, Typography } from '@mui/material';
import { PiCow } from "react-icons/pi"; // Un icono divertido para "cabezas"
import ScaleIcon from '@mui/icons-material/Scale'; // Para kilos
import type { CotizacionGanado } from '../models/mercadoModels';
import { useApiData } from '../hooks/useApiData';
import { formatCurrency, formatInteger } from '../utils/formatters';
const AgroCard = ({ categoria }: { categoria: CotizacionGanado }) => {
return (
<Paper elevation={2} sx={{ p: 2, flex: '1 1 250px', minWidth: '250px', maxWidth: '300px' }}>
<Typography variant="h6" component="h3" sx={{ fontWeight: 'bold', borderBottom: 1, borderColor: 'divider', pb: 1, mb: 2 }}>
{categoria.categoria}
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="body2" color="text.secondary">Precio Máximo:</Typography>
<Typography variant="body2" sx={{ fontWeight: 'bold', color: 'success.main' }}>${formatCurrency(categoria.maximo)}</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="body2" color="text.secondary">Precio Mínimo:</Typography>
<Typography variant="body2" sx={{ fontWeight: 'bold', color: 'error.main' }}>${formatCurrency(categoria.minimo)}</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="body2" color="text.secondary">Precio Mediano:</Typography>
<Typography variant="body2" sx={{ fontWeight: 'bold' }}>${formatCurrency(categoria.mediano)}</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 3, pt: 1, borderTop: 1, borderColor: 'divider' }}>
<Box sx={{ textAlign: 'center' }}>
<PiCow size={28}/>
<Typography variant="body1" sx={{ fontWeight: 'bold' }}>{formatInteger(categoria.cabezas)}</Typography>
<Typography variant="caption" color="text.secondary">Cabezas</Typography>
</Box>
<Box sx={{ textAlign: 'center' }}>
<ScaleIcon color="action" />
<Typography variant="body1" sx={{ fontWeight: 'bold' }}>{formatInteger(categoria.kilosTotales)}</Typography>
<Typography variant="caption" color="text.secondary">Kilos</Typography>
</Box>
</Box>
</Paper>
);
};
// Este widget agrupa los datos por categoría para un resumen más limpio.
export const MercadoAgroCardWidget = () => {
const { data, loading, error } = useApiData<CotizacionGanado[]>('/mercados/agroganadero');
if (loading) {
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}><CircularProgress /></Box>;
}
if (error) {
return <Alert severity="error">{error}</Alert>;
}
if (!data || data.length === 0) {
return <Alert severity="info">No hay datos del mercado agroganadero disponibles.</Alert>;
}
// Agrupamos y sumamos los datos por categoría principal
const resumenPorCategoria = data.reduce((acc, item) => {
if (!acc[item.categoria]) {
acc[item.categoria] = { ...item };
} else {
acc[item.categoria].cabezas += item.cabezas;
acc[item.categoria].kilosTotales += item.kilosTotales;
acc[item.categoria].importeTotal += item.importeTotal;
acc[item.categoria].maximo = Math.max(acc[item.categoria].maximo, item.maximo);
acc[item.categoria].minimo = Math.min(acc[item.categoria].minimo, item.minimo);
}
return acc;
}, {} as Record<string, CotizacionGanado>);
return (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, justifyContent: 'center' }}>
{Object.values(resumenPorCategoria).map(categoria => (
<AgroCard key={categoria.categoria} categoria={categoria} />
))}
</Box>
);
};