DashboardPage now reuses the Cartes report's presentational widgets (KpiCard with MoM/YoY deltas, top movers, budget adherence) sourced from getCartesSnapshot against a Dashboard-owned reference month (defaults to the last complete month). The expense-only pie chart is replaced by a ranked bar chart of top expense categories (resurrecting the previously unused CategoryBarChart), and the account (import-source) filter introduced on Trends/Compare/Budget now also applies to the Dashboard's own transactional widgets via a Dashboard-local accountIds state (kept separate from useReportsPeriod, since the Dashboard owns its own two temporal axes). A new net-worth tile surfaces the Balance sheet's latest total (getSnapshotTotalsByDate) — a distinct metric from every transactional card here, so it stays hidden (never a misleading "$0") until at least one balance account has a recorded snapshot, reusing deriveLandingState rather than re-inferring emptiness from nulls. It is not scoped by the account filter (balance_accounts is a disjoint concept from import_sources) and is fetched independently on mount. The category-over-time trend chart now passes typeFilter "expense", fixing a latent mismatch where a revenue category could silently show up in a chart titled "expenses over time". Resolves #279
62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
// NetWorthTile — Dashboard KPI-row tile showing the Bilan's latest net worth.
|
|
//
|
|
// Issue #279. Deliberately NOT `HubNetBalanceTile` (that one is the reports
|
|
// hub's transaction-P&L net balance — a different metric). This tile mirrors
|
|
// `BalanceOverviewCard`'s "latest total" figure and reuses its exact i18n
|
|
// copy (`balance.overview.latestTotal` / `asOf`) so the label is unambiguous
|
|
// next to the transactional KPI cards and the "import sources" account
|
|
// filter — same words as the Bilan page itself, never "balance"/"solde".
|
|
//
|
|
// Renders nothing when `data.visible` is false (no balance account yet, or
|
|
// accounts but no snapshot yet) — never a misleading "$0" tile.
|
|
|
|
import { Link } from "react-router-dom";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Wallet } from "lucide-react";
|
|
import type { NetWorthTileData } from "../../services/dashboardService";
|
|
|
|
export interface NetWorthTileProps {
|
|
data: NetWorthTileData;
|
|
}
|
|
|
|
function formatCurrency(amount: number, language: string): string {
|
|
return new Intl.NumberFormat(language === "fr" ? "fr-CA" : "en-CA", {
|
|
style: "currency",
|
|
currency: "CAD",
|
|
maximumFractionDigits: 0,
|
|
}).format(amount);
|
|
}
|
|
|
|
export default function NetWorthTile({ data }: NetWorthTileProps) {
|
|
const { t, i18n } = useTranslation();
|
|
|
|
if (!data.visible || data.total === null || data.asOfDate === null) {
|
|
return null;
|
|
}
|
|
|
|
const dateLocale = i18n.language === "fr" ? "fr-CA" : "en-CA";
|
|
const formattedDate = new Date(data.asOfDate).toLocaleDateString(dateLocale, {
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
});
|
|
|
|
return (
|
|
<Link
|
|
to="/balance"
|
|
data-kpi="net-worth"
|
|
className="flex-1 min-w-[180px] bg-[var(--card)] border border-[var(--border)] rounded-xl p-4 flex flex-col gap-3 hover:border-[var(--primary)] transition-colors"
|
|
>
|
|
<div className="text-sm text-[var(--muted-foreground)] flex items-center gap-1.5">
|
|
<Wallet size={14} aria-hidden="true" />
|
|
<span>{t("balance.overview.latestTotal")}</span>
|
|
</div>
|
|
<div className="text-2xl font-bold tabular-nums text-[var(--foreground)]">
|
|
{formatCurrency(data.total, i18n.language)}
|
|
</div>
|
|
<div className="text-xs text-[var(--muted-foreground)] pt-1 border-t border-[var(--border)]">
|
|
{t("balance.overview.asOf", { date: formattedDate })}
|
|
</div>
|
|
</Link>
|
|
);
|
|
}
|