Simpl-Resultat/src/components/reports/HubNetBalanceTile.tsx
le king fu ac9c8afc4a
All checks were successful
PR Check / rust (pull_request) Successful in 24m54s
PR Check / frontend (pull_request) Successful in 2m32s
PR Check / rust (push) Successful in 24m14s
PR Check / frontend (push) Successful in 2m26s
feat: reports hub with highlights panel and detailed highlights page (#71)
- 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>
2026-04-14 14:47:55 -04:00

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>
);
}