// BalanceOverviewCard — top summary tile of /balance. // // Issue #141 (Bilan #3). Displays: // - The latest aggregate snapshot total (sum across all accounts on the // most recent snapshot date). // - Δ% versus the previous chronological snapshot (null when only one // snapshot exists; rendered as "—"). // - A staleness warning when the latest snapshot is older than 60 days. // - "+ Nouveau snapshot" CTA → `/balance/snapshot`. import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { Plus, TrendingUp, TrendingDown, AlertTriangle } from "lucide-react"; import { Link } from "react-router-dom"; import type { SnapshotTotalPoint } from "../../services/balance.service"; const STALENESS_DAYS = 60; const cadFormatter = (value: number) => new Intl.NumberFormat("en-CA", { style: "currency", currency: "CAD", maximumFractionDigits: 2, }).format(value); interface BalanceOverviewCardProps { /** The full evolution series for the active period (latest at the end). */ totals: SnapshotTotalPoint[]; } export default function BalanceOverviewCard({ totals }: BalanceOverviewCardProps) { const { t, i18n } = useTranslation(); const summary = useMemo(() => { if (totals.length === 0) { return null; } const last = totals[totals.length - 1]; const prev = totals.length >= 2 ? totals[totals.length - 2] : null; const deltaPct = prev && prev.total !== 0 ? ((last.total - prev.total) / Math.abs(prev.total)) * 100 : null; const ageMs = Date.now() - new Date(last.snapshot_date).getTime(); const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24)); return { latest: last, deltaPct, isStale: ageDays > STALENESS_DAYS, ageDays, }; }, [totals]); const dateLocale = i18n.language === "fr" ? "fr-CA" : "en-CA"; const formatDate = (iso: string) => new Date(iso).toLocaleDateString(dateLocale, { year: "numeric", month: "long", day: "numeric", }); return (
{t("balance.overview.latestTotal")}
{summary ? ( <>{cadFormatter(summary.latest.total)}
{t("balance.overview.asOf", { date: formatDate(summary.latest.snapshot_date), })}
> ) : ({t("balance.overview.noSnapshots")}
)}