Insert a new "Bilan" / "Balance sheet" entry in NAV_ITEMS pointing
at /balance with the Wallet lucide-react icon. Position: between
Reports and Settings, matching the autopilot prompt instruction
and the spec-plan-bilan v2 ordering.
Sidebar.tsx imports Wallet and registers it in iconMap.
Refs: #141
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three new components composed under a new BalancePage at /balance:
- BalanceOverviewCard — latest aggregate net worth, Δ% vs the
previous chronological snapshot (rendered as "—" when only
one snapshot exists), 60-day staleness warning, and a
"+ Nouveau snapshot" CTA pointing at /balance/snapshot.
- BalanceEvolutionChart — Recharts-based line / stacked-area
toggle. Line mode plots SUM(value) per snapshot_date with a
single primary-coloured stroke. Stacked mode transposes the
byCategory series into one Area per category_key with a
fixed 10-color palette indexed deterministically. Tooltip
formats CAD via Intl.NumberFormat.
- BalanceAccountsTable — one row per active account with name,
category label, latest value, and Δ% over the active period
(latest_value vs the period anchor). Returns columns
(3M / 1Y / since-creation / unadjusted) reserved for #142
with a TODO marker. Action menu includes a disabled "Detail"
placeholder + functional "Archive" wired through reload().
BalancePage composes the three with an inline period selector
(3M / 6M / 1A / 3A / Tout) and chart-mode toggle, both styled
as segmented controls. State flows through useBalanceOverview.
Route /balance registered before /balance/accounts in App.tsx.
Refs: #141
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scoped useReducer hook backing BalancePage. Tracks:
- period (3M / 6M / 1A / 3A / all) — defaults to 1A
- chartMode (line / stacked) — defaults to line
- evolutionTotals + evolutionByCategory + accountsLatest +
accountsPeriodAnchor (parallel-fetched on mount and on every
period change via Promise.all)
- isLoading + error
Exposes computeBalanceDateRange(period, today) as a pure helper
so the date math is unit-testable without mocking time. Anchors
on `today` rather than the latest snapshot — keeps the chart's
right edge stable as the user enters new snapshots.
Refs: #141
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add four service helpers used by the upcoming `/balance` overview:
- getSnapshotTotalsByDate(range?) — SUM(value) GROUP BY snapshot_date
with an optional inclusive [from, to] range. LEFT JOIN preserves
empty snapshots as zero rows so the chart shows continuity.
- getSnapshotTotalsByCategoryAndDate(range?) — same aggregation broken
down by balance_categories.key, returned as one row per snapshot
date with a `byCategory` map. Powers the stacked-area variant.
- getAccountsLatestSnapshot() — one row per active account with the
value of its most-recent snapshot line (NULL when none exists).
Filters archived accounts via WHERE is_active = 1 AND archived_at
IS NULL, matches the listBalanceAccounts default.
- getAccountsPeriodAnchor(range) — earliest snapshot_date >= from
per account, with the value at that date — the anchor used to
compute the per-account Δ% column on the accounts table.
Tests cover empty DB, single/multi snapshot, archived exclusion via
SQL inspection, date-range params (from-only, both bounds, open).
Refs: #141
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>