feat(analysis): add 12-month summary row with total variation and accumulated inflation

This commit is contained in:
Jose Selesan
2026-08-27 11:56:02 -03:00
parent b78fcbc0e9
commit d24f3ce872

View File

@@ -54,6 +54,43 @@ function formatPrice(value: number): string {
return `$${priceFormatter.format(value)}`;
}
function TotalVariation({
variation,
inflation,
}: {
variation: number | null;
inflation: number | null;
}) {
if (variation === null) {
return (
<span className="text-xs text-muted-foreground inline-flex items-center gap-1">
--
</span>
);
}
return (
<span
className={`text-xs font-medium inline-flex items-center gap-1 tabular-nums ${
inflation !== null
? variation > inflation
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400 px-1.5 py-0.5 rounded"
: "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400 px-1.5 py-0.5 rounded"
: variation >= 0
? "text-emerald-500"
: "text-red-500"
}`}
>
{variation >= 0 ? (
<ArrowUp className="size-3" />
) : (
<ArrowDown className="size-3" />
)}
{formatPct(variation)}
</span>
);
}
function VariationIndicator({
variation,
inflation,
@@ -205,6 +242,34 @@ export function MonthlyVariationTable() {
.reverse();
}, [monthlyData, inflationData]);
const summary = useMemo(() => {
if (rows.length < 2) return null;
const oldest = rows[rows.length - 1];
const newest = rows[0];
const totalVariation = (
first: number | null,
last: number | null,
): number | null => {
if (first === null || last === null || first === 0) return null;
return ((last - first) / first) * 100;
};
const accumulatedInflation = rows.reduce<number | null>((acc, row) => {
if (row.inflation === null) return acc;
return (acc ?? 0) + row.inflation;
}, null);
return {
BELO: totalVariation(oldest.BELO.current, newest.BELO.current),
BLUE: totalVariation(oldest.BLUE.current, newest.BLUE.current),
BNA: totalVariation(oldest.BNA.current, newest.BNA.current),
inflation: accumulatedInflation,
period: `${oldest.label} – ${newest.label}`,
};
}, [rows]);
const isLoading = monthlyLoading || inflationLoading;
if (isLoading) {
@@ -279,6 +344,48 @@ export function MonthlyVariationTable() {
</div>
</div>
))}
{summary && (
<div className="rounded-lg border bg-muted/50 p-3 text-sm">
<div className="flex items-center justify-between mb-2">
<span className="font-semibold">Total 12 meses</span>
{summary.inflation !== null ? (
<span className="text-xs text-muted-foreground tabular-nums inline-flex items-center gap-1">
<TrendingUp className="size-3" />
{formatPct(summary.inflation)} inflación
</span>
) : (
<span className="text-xs text-muted-foreground">
Sin inflación
</span>
)}
</div>
<div className="grid grid-cols-3 gap-2">
<div>
<p className="text-[10px] text-muted-foreground mb-0.5">BELO</p>
<TotalVariation
variation={summary.BELO}
inflation={summary.inflation}
/>
</div>
<div>
<p className="text-[10px] text-muted-foreground mb-0.5">Blue</p>
<TotalVariation
variation={summary.BLUE}
inflation={summary.inflation}
/>
</div>
<div>
<p className="text-[10px] text-muted-foreground mb-0.5">
Oficial
</p>
<TotalVariation
variation={summary.BNA}
inflation={summary.inflation}
/>
</div>
</div>
</div>
)}
</div>
<div className="hidden overflow-x-auto rounded-lg border sm:block">
@@ -348,6 +455,38 @@ export function MonthlyVariationTable() {
</td>
</tr>
))}
{summary && (
<tr className="border-t-2 bg-muted/50 font-medium">
<td className="px-3 py-2.5 text-xs">Total 12 meses</td>
<td className="px-3 py-2.5 text-right">
<TotalVariation
variation={summary.BELO}
inflation={summary.inflation}
/>
</td>
<td className="px-3 py-2.5 text-right">
<TotalVariation
variation={summary.BLUE}
inflation={summary.inflation}
/>
</td>
<td className="px-3 py-2.5 text-right">
<TotalVariation
variation={summary.BNA}
inflation={summary.inflation}
/>
</td>
<td className="px-3 py-2.5 text-right">
{summary.inflation !== null ? (
<span className="text-xs font-medium tabular-nums">
{formatPct(summary.inflation)}
</span>
) : (
<span className="text-xs text-muted-foreground">--</span>
)}
</td>
</tr>
)}
</tbody>
</table>
</div>