Feat: Currency 2 Decimal Format - Fix: Charts Scale
This commit is contained in:
		| @@ -2,7 +2,7 @@ import { Box, CircularProgress, Alert } from '@mui/material'; | |||||||
| import type { CotizacionBolsa } from '../models/mercadoModels'; | import type { CotizacionBolsa } from '../models/mercadoModels'; | ||||||
| import { useApiData } from '../hooks/useApiData'; | import { useApiData } from '../hooks/useApiData'; | ||||||
| import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; | import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; | ||||||
| import { formatFullDateTime } from '../utils/formatters'; | import { formatFullDateTime, formatCurrency2Decimal } from '../utils/formatters'; | ||||||
|  |  | ||||||
| interface HistoricalChartWidgetProps { | interface HistoricalChartWidgetProps { | ||||||
|     ticker: string; |     ticker: string; | ||||||
| @@ -10,6 +10,11 @@ interface HistoricalChartWidgetProps { | |||||||
|     dias: number; |     dias: number; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Formateador para el eje X que solo muestra día/mes | ||||||
|  | const formatXAxis = (tickItem: string) => { | ||||||
|  |     return new Date(tickItem).toLocaleDateString('es-AR', { day: '2-digit', month: '2-digit' }); | ||||||
|  | }; | ||||||
|  |  | ||||||
| const formatTooltipLabel = (label: string) => { | const formatTooltipLabel = (label: string) => { | ||||||
|     return formatFullDateTime(label); |     return formatFullDateTime(label); | ||||||
| }; | }; | ||||||
| @@ -23,16 +28,25 @@ export const HistoricalChartWidget = ({ ticker, mercado, dias }: HistoricalChart | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (error) { |     if (error) { | ||||||
|         return <Alert severity="error" sx={{height: 300}}>{error}</Alert>; |         return <Alert severity="error" sx={{ height: 300 }}>{error}</Alert>; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (!data || data.length < 2) { |     if (!data || data.length < 2) { | ||||||
|         return <Alert severity="info" sx={{height: 300}}>No hay suficientes datos históricos para graficar.</Alert>; |         return <Alert severity="info" sx={{ height: 300 }}>No hay suficientes datos históricos para graficar.</Alert>; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Formateador para el eje X que solo muestra día/mes |     // 1. Calcular el dominio del eje Y con un margen | ||||||
|     const formatXAxis = (tickItem: string) => { |     const prices = data.map(p => p.precioActual); | ||||||
|         return new Date(tickItem).toLocaleDateString('es-AR', { day: '2-digit', month: '2-digit' }); |     const dataMin = Math.min(...prices); | ||||||
|  |     const dataMax = Math.max(...prices); | ||||||
|  |     const padding = (dataMax - dataMin) * 0.05; // 5% de padding | ||||||
|  |     const domainMin = Math.floor(dataMin - padding); | ||||||
|  |     const domainMax = Math.ceil(dataMax + padding); | ||||||
|  |  | ||||||
|  |     // 2. Formateador de ticks para el eje Y más robusto | ||||||
|  |     const yAxisTickFormatter = (tick: number) => { | ||||||
|  |         // Usamos el formateador de moneda | ||||||
|  |         return `$${formatCurrency2Decimal(tick)}`; | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     return ( |     return ( | ||||||
| @@ -40,13 +54,17 @@ export const HistoricalChartWidget = ({ ticker, mercado, dias }: HistoricalChart | |||||||
|             <LineChart data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}> |             <LineChart data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}> | ||||||
|                 <CartesianGrid strokeDasharray="3 3" /> |                 <CartesianGrid strokeDasharray="3 3" /> | ||||||
|                 <XAxis dataKey="fechaRegistro" tickFormatter={formatXAxis} /> |                 <XAxis dataKey="fechaRegistro" tickFormatter={formatXAxis} /> | ||||||
|                 <YAxis domain={['dataMin - dataMin * 0.02', 'dataMax + dataMax * 0.02']} tickFormatter={(tick) => `$${tick.toLocaleString('es-AR')}`} /> |                 <YAxis | ||||||
|  |                     domain={[domainMin, domainMax]} | ||||||
|  |                     tickFormatter={yAxisTickFormatter} | ||||||
|  |                     width={80} // Damos un poco más de espacio para números grandes | ||||||
|  |                 /> | ||||||
|                 <Tooltip |                 <Tooltip | ||||||
|                     formatter={(value: number) => [`$${value.toFixed(2)}`, 'Precio']}  |                     formatter={(value: number) => [`$${formatCurrency2Decimal(value)}`, 'Precio']} | ||||||
|                     labelFormatter={formatTooltipLabel} |                     labelFormatter={formatTooltipLabel} | ||||||
|                 /> |                 /> | ||||||
|                 <Legend /> |                 <Legend /> | ||||||
|                 <Line type="monotone" dataKey="precioActual" name="Precio de Cierre" stroke="#028fbe" strokeWidth={2} dot={false} /> |                 <Line type="monotone" dataKey="precioActual" name="Precio de Cierre" stroke="#8884d8" strokeWidth={2} dot={false} /> | ||||||
|             </LineChart> |             </LineChart> | ||||||
|         </ResponsiveContainer> |         </ResponsiveContainer> | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; | |||||||
| import RemoveIcon from '@mui/icons-material/Remove'; | import RemoveIcon from '@mui/icons-material/Remove'; | ||||||
|  |  | ||||||
| import type { CotizacionBolsa } from '../models/mercadoModels'; | import type { CotizacionBolsa } from '../models/mercadoModels'; | ||||||
| import { formatInteger, formatCurrency } from '../utils/formatters'; | import { formatInteger, formatCurrency, formatCurrency2Decimal } from '../utils/formatters'; | ||||||
| import { HistoricalChartWidget } from './HistoricalChartWidget'; | import { HistoricalChartWidget } from './HistoricalChartWidget'; | ||||||
| import { useApiData } from '../hooks/useApiData'; | import { useApiData } from '../hooks/useApiData'; | ||||||
|  |  | ||||||
| @@ -53,7 +53,7 @@ export const MervalHeroCard = () => { | |||||||
|             <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}> |             <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}> | ||||||
|                 <Box> |                 <Box> | ||||||
|                     <Typography variant="h5" component="h2" sx={{ fontWeight: 'bold' }}>Índice S&P MERVAL</Typography> |                     <Typography variant="h5" component="h2" sx={{ fontWeight: 'bold' }}>Índice S&P MERVAL</Typography> | ||||||
|                     <Typography variant="h3" component="p" sx={{ fontWeight: 'bold', mt:1 }}>{formatInteger(mervalData.precioActual)}</Typography> |                     <Typography variant="h3" component="p" sx={{ fontWeight: 'bold', mt:1 }}>{formatCurrency2Decimal(mervalData.precioActual)}</Typography> | ||||||
|                 </Box> |                 </Box> | ||||||
|                 <Box sx={{ pt: 2 }}> |                 <Box sx={{ pt: 2 }}> | ||||||
|                     <VariacionMerval actual={mervalData.precioActual} anterior={mervalData.cierreAnterior} /> |                     <VariacionMerval actual={mervalData.precioActual} anterior={mervalData.cierreAnterior} /> | ||||||
|   | |||||||
| @@ -11,6 +11,18 @@ export const formatCurrency = (num: number, currency = 'ARS') => { | |||||||
|   }).format(num); |   }).format(num); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | export const formatCurrency2Decimal = (num: number, currency = 'ARS') => { | ||||||
|  |   const style = currency === 'USD' ? 'currency' : 'decimal'; | ||||||
|  |   const locale = currency === 'USD' ? 'en-US' : 'es-AR'; | ||||||
|  |    | ||||||
|  |   return new Intl.NumberFormat(locale, { | ||||||
|  |     style: style, | ||||||
|  |     currency: currency, | ||||||
|  |     minimumFractionDigits: 2, | ||||||
|  |     maximumFractionDigits: 2, | ||||||
|  |   }).format(num); | ||||||
|  | }; | ||||||
|  |  | ||||||
| export const formatInteger = (num: number) => { | export const formatInteger = (num: number) => { | ||||||
|   return new Intl.NumberFormat('es-AR').format(num); |   return new Intl.NumberFormat('es-AR').format(num); | ||||||
| }; | }; | ||||||
| @@ -23,7 +35,7 @@ export const formatFullDateTime = (dateString: string) => { | |||||||
|     year: 'numeric', |     year: 'numeric', | ||||||
|     hour: '2-digit', |     hour: '2-digit', | ||||||
|     minute: '2-digit', |     minute: '2-digit', | ||||||
|     hourCycle: 'h23', // <--- LA CLAVE PARA EL FORMATO 24HS |     hourCycle: 'h23', | ||||||
|     timeZone: 'America/Argentina/Buenos_Aires', |     timeZone: 'America/Argentina/Buenos_Aires', | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user