feat(analysis): add 12-month summary row with total variation and accumulated inflation
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user