- Transform /reports into a hub: highlights panel + 4 nav cards - New service: reportService.getHighlights (parameterised SQL, deterministic via referenceDate argument for tests, computes current-month balance, YTD, 12-month sparkline series, top expense movers vs previous month, top recent transactions within configurable 30/60/90 day window) - Extended types: HighlightsData, HighlightMover, MonthBalance - Wired useHighlights hook with reducer + window-days state - Hub tiles (flat naming under src/components/reports): HubNetBalanceTile, HubTopMoversTile, HubTopTransactionsTile, HubHighlightsPanel, HubReportNavCard - Detailed ReportsHighlightsPage: balance tiles, sortable top movers table, diverging bar chart (Recharts + patterns SVG), top transactions list with 30/60/90 window toggle; ViewModeToggle persistence keyed as reports-viewmode-highlights - New i18n keys: reports.hub.*, reports.highlights.* - 5 new vitest cases: empty profile, parameterised queries, window sizing, delta computation, zero-previous divisor handling Fixes #71 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
35 lines
1.1 KiB
TypeScript
35 lines
1.1 KiB
TypeScript
import { useTranslation } from "react-i18next";
|
|
import Sparkline from "./Sparkline";
|
|
|
|
export interface HubNetBalanceTileProps {
|
|
label: string;
|
|
amount: number;
|
|
series: number[];
|
|
}
|
|
|
|
function formatSigned(amount: number, language: string): string {
|
|
const formatted = new Intl.NumberFormat(language === "fr" ? "fr-CA" : "en-CA", {
|
|
style: "currency",
|
|
currency: "CAD",
|
|
signDisplay: "always",
|
|
}).format(amount);
|
|
return formatted;
|
|
}
|
|
|
|
export default function HubNetBalanceTile({ label, amount, series }: HubNetBalanceTileProps) {
|
|
const { i18n } = useTranslation();
|
|
const positive = amount >= 0;
|
|
const color = positive ? "var(--positive, #10b981)" : "var(--negative, #ef4444)";
|
|
|
|
return (
|
|
<div className="bg-[var(--card)] border border-[var(--border)] rounded-xl p-4 flex flex-col gap-2">
|
|
<span className="text-xs font-medium text-[var(--muted-foreground)] uppercase tracking-wide">
|
|
{label}
|
|
</span>
|
|
<span className="text-2xl font-bold" style={{ color }}>
|
|
{formatSigned(amount, i18n.language)}
|
|
</span>
|
|
<Sparkline data={series} color={color} height={28} />
|
|
</div>
|
|
);
|
|
}
|