whisper-money/resources/js/components
Víctor Falcón 7281b610b5
Render the Cashflow Sankey with recharts (responsive + expense drill-down) (#670)
## What & why

The Cashflow "Money Flow" diagram was a bespoke SVG Sankey that laid out
nodes and links by hand. It didn't hold up on small screens (fixed 400px
min-width, cramped flows) and was ~950 lines of custom geometry to
maintain.

This replaces it with recharts' `<Sankey>` (already a dependency) inside
a `ResponsiveContainer`, letting recharts own the layout. Net **−950 /
+515** lines across the component.

## What's kept

- Income → **Net** hub → expense flow.
- Small-category **"Other"** grouping (`groupSmallCategories`).
- Privacy-mode masking on every amount (hub + categories +
subcategories).
- Click-through to a category's transactions.
- Category colors, resolved via `categoryBarColor` (same as the
breakdown cards).

## What's new

- **Drill-down (expense side):** click an expense category with children
to expand its subcategories into their own column; recharts re-lays-out
around it. One open at a time, a chevron marks expandable parents, and
the expanded parent's label moves onto its bar. Subcategories and
childless categories still click through to their transactions. Data
comes from the existing `GET /api/cashflow/sankey?...&parent=<id>`
endpoint, fetched lazily and reset when the period changes.
- **Mobile:** the chart scrolls horizontally with a comfortable
min-width (same pattern as the Trend chart) instead of crushing the
flows; canvas height grows with the busiest column so labels don't
collide.
- `align="left"` so an expanded parent's subcategories line up beside it
instead of collapsing into the last column and crossing every flow.

## What's dropped (intentional)

- The "Other" popover breakdown — the per-category detail already lives
in the breakdown cards below the chart.
- Income-side drill-down — with the central hub, expanding an income
source would split the income column across two depths (visually
broken); income categories rarely have children. Income keeps
click-through.

## Review

Two parallel reviews (technical + bugs/side-effects) ran over the diff.
Applied:
- **Category colors:** resolve the `CategoryColor` keyword via
`categoryBarColor` instead of using it raw as an SVG `fill` — non-CSS
keywords (`slate`, `neutral`, `amber`, `rose`) were rendering **black**.
(Pre-existing bug carried over from the old chart.)
- Skip top-level categories with `amount <= 0` so they don't become
stray unlinked nodes.
- Keep an expanding parent's label beside its bar until subcategories
load (no flicker); collapse again if the fetch fails.
- Cap right-side label width so a parent left of an expanded column
can't stretch its label across it.
- Memoize the cashflow `period` so the drill-down fetch effect doesn't
re-run on unrelated re-renders.

Dropped (with reason): re-adding the "Other" popover and income
drill-down (intentional, above); wrapping aria-labels in `__()` (would
add i18n keys, and matches the prior behavior); the recharts node/link
prop casts (version is pinned; params are hand-typed to document the
fields used).

## Testing

- 6 Vitest unit tests (render, "Other" grouping, empty state, drill-down
with a mocked fetch, expand affordance, privacy masking) — green.
- `tsc --noEmit` clean for the touched files; ESLint + Prettier clean.

> **Note on browser QA:** I did not record an automated demo video. The
local DB holds real user financial data (won't log in / record), and
creating a synthetic test/demo account is currently blocked by pending
Spaces migrations in the local DB (`current_space_id` column missing —
unrelated to this change). The chart was verified interactively during
development (desktop render, mobile layout, drill-down, and the
black-color fix). Happy to record a walkthrough if we seed a safe demo
account.
2026-07-12 16:26:17 +00:00
..
accounts fix(balances): allow saving a zero balance (#664) 2026-07-10 15:02:38 +02:00
automation-rules fix(security): scope job-status endpoints to owner + feature-area fixes (#627) 2026-07-03 14:49:32 +02:00
budgets feat: add catch-all budgets (#527) 2026-06-15 16:07:19 +00:00
cashflow feat(analysis): per-category 12-month spending drawer (#519) 2026-06-11 09:52:53 +02:00
categories feat(analysis): per-category 12-month spending drawer (#519) 2026-06-11 09:52:53 +02:00
charts Render the Cashflow Sankey with recharts (responsive + expense drill-down) (#670) 2026-07-12 16:26:17 +00:00
dashboard fix(dashboard): stop negative asset balances inflating net worth (#660) 2026-07-08 15:22:16 +00:00
icons Subscriptions (#15) 2025-12-06 19:09:56 +01:00
integration-requests feat(integration-requests): add done status and fix review command crash on orphaned author (#601) 2026-06-27 14:42:09 +00:00
labels refactor(js): use InputError for inline form errors (#483) 2026-06-03 19:01:21 +02:00
landing feat(landing): add go now button to redirect modal (#506) 2026-06-08 09:51:35 +02:00
onboarding fix(balances): allow saving a zero balance (#664) 2026-07-10 15:02:38 +02:00
open-banking feat(banking): enable Interactive Brokers for all users (#593) 2026-06-26 11:03:21 +02:00
partials fix(header): keep mobile logo on one line, compact auth buttons (#512) 2026-06-09 14:44:35 +02:00
shared feat(ai): auto-categorize transactions with AI (behind flag) (#535) 2026-06-15 16:35:20 +02:00
transactions fix(balances): allow saving a zero balance (#664) 2026-07-10 15:02:38 +02:00
ui fix(chart): stop tooltip render loop crashing the dashboard (PHP-LARAVEL-3B) (#657) 2026-07-07 18:39:09 +00:00
alert-error.tsx Set up a fresh Laravel app 2025-11-07 12:01:36 +00:00
app-content.tsx Set up a fresh Laravel app 2025-11-07 12:01:36 +00:00
app-logo-icon.tsx refactor(encryption): strip client-side transaction encryption (#514) 2026-06-20 16:13:26 +00:00
app-logo.tsx Add animated logo icon that transitions based on encryption key state 2025-12-12 11:24:42 +01:00
app-shell.tsx Set up a fresh Laravel app 2025-11-07 12:01:36 +00:00
app-sidebar-header.tsx fix: keep iOS content below the notch (#280) 2026-04-13 13:59:05 +01:00
app-sidebar.tsx fix: move community link to user menu (#442) 2026-05-27 17:39:26 +02:00
appearance-tabs.tsx feat: Spanish localization (#74) 2026-02-08 11:58:08 +01:00
bank-logo.tsx feat: Integrate EnableBanking as open banking provider (#106) 2026-02-12 09:09:28 +01:00
breadcrumbs.tsx feat: Spanish localization (#74) 2026-02-08 11:58:08 +01:00
delete-user.tsx fix(account): block deletion while subscription or trial is active (#531) 2026-06-14 20:46:44 +02:00
encryption-key-button.tsx feat: Spanish localization (#74) 2026-02-08 11:58:08 +01:00
heading-small.tsx Add Budgeting Feature to Track and Manage Spending (#36) 2026-01-21 15:25:50 +01:00
heading.tsx Set up a fresh Laravel app 2025-11-07 12:01:36 +00:00
icon.tsx Set up a fresh Laravel app 2025-11-07 12:01:36 +00:00
input-error.tsx Set up a fresh Laravel app 2025-11-07 12:01:36 +00:00
mobile-back-button.tsx fix(haptics): use a local WebHaptics wrapper (#225) 2026-03-16 11:26:04 +00:00
nav-footer.tsx feat: Spanish localization (#74) 2026-02-08 11:58:08 +01:00
nav-main.tsx fix(haptics): use a local WebHaptics wrapper (#225) 2026-03-16 11:26:04 +00:00
nav-user.tsx feat(integration-requests): community board to request & vote bank integrations (#550) 2026-06-17 12:50:51 +00:00
sortable-grid.test.tsx fix(accounts): stop second long-press haptic on drag handle (#578) 2026-06-22 11:20:57 +02:00
sortable-grid.tsx fix(accounts): stop second long-press haptic on drag handle (#578) 2026-06-22 11:20:57 +02:00
support-dialog.tsx feat(support): add support link with community-first help modal (#542) 2026-06-16 13:16:04 +02:00
text-link.tsx Set up a fresh Laravel app 2025-11-07 12:01:36 +00:00
two-factor-recovery-codes.tsx feat: Spanish localization (#74) 2026-02-08 11:58:08 +01:00
two-factor-setup-modal.tsx feat: Spanish localization (#74) 2026-02-08 11:58:08 +01:00
unlock-message-dialog.tsx feat(auth): show/hide toggle on password fields (#499) 2026-06-06 11:42:20 +02:00
user-info.tsx feat: Update facehash and enable blink 2026-02-24 15:54:49 +01:00
user-menu-content.test.tsx feat(integration-requests): community board to request & vote bank integrations (#550) 2026-06-17 12:50:51 +00:00
user-menu-content.tsx feat(integration-requests): community board to request & vote bank integrations (#550) 2026-06-17 12:50:51 +00:00