whisper-money/resources/js/components
Víctor Falcón 50d0fccd17
fix(dashboard): render negative net worth as a downward bar (#722)
## Problem

On the dashboard **Net Worth Evolution** monthly bar chart, users with a
large negative net worth (lots of debt) saw an **empty chart** — only
the x-axis month labels rendered, no bars.

Root cause: the chart scales each asset segment so the stacked bar
height equals net worth. For negative net worth the scale factor was
clamped to zero:

```ts
const scaleFactor = hasLiabs && totalAssets > 0
    ? Math.max(0, netWorth / totalAssets) // negative net worth -> 0 -> every bar collapses
    : 1;
```

A stack of positive asset segments simply can't represent a negative
total, so everything collapsed to zero height.

## Fix

- Extract the scaling into a tested pure helper
`computeNetWorthBarScaling`.
- When net worth is **negative**, hide the (meaningless) scaled asset
segments and draw the deficit as a **single downward bar** from a **zero
baseline** (dashed reference line).
- **Positive** periods still stack assets upward. Both directions round
only at their far end and meet the zero line flush, so upward and
downward bars look symmetric.
- Extend the same handling to the **daily area chart**, which shared the
bug through the same scaled dataset.
- Share a single `NetWorthMode` type across the bar/area charts and
tooltip (was duplicated in three places).
- Translate the tooltip **Net Worth / Total** labels (were hardcoded
English; added `Net Worth` to `lang/es.json`).
- Render the chart for **liability-only** users (a debtor with just a
loan) instead of the "No account data available" empty state.

## Tests

- Unit tests for `computeNetWorthBarScaling`: no-liabilities, positive,
negative-with-assets, liabilities-only, net-worth-exactly-zero, and
no-assets cases.
- `bun run test`, `prettier --check`, `eslint` all green on the touched
files.

## QA

Verified in the running app (Playwright) with two seeded users:

- **Heavily indebted** (net worth −56.250,00 €): the chart now renders
red downward bars instead of an empty area.
- **Net worth crossing zero**: negative months draw downward, positive
months stack upward, sharing the zero baseline; tooltip shows
per-account balances + the (now translated) "Patrimonio Neto" total.

## Demo


https://github.com/user-attachments/assets/7a9b167f-c037-4c82-8b68-f32932be99fe

## Screenshots

**Before** — empty chart, only x-axis labels:

<!-- PLACEHOLDER: before screenshot -->
<img width="1440" height="900" alt="net-worth-before"
src="https://github.com/user-attachments/assets/3bf9b3e3-ae75-4230-bca7-e7246a9a35d9"
/>


**After** — negative net worth as downward bars:
<img width="1440" height="900" alt="net-worth-after-tooltip"
src="https://github.com/user-attachments/assets/85302f0a-e427-4cbf-baba-5b1414049e9f"
/>
<img width="1440" height="900" alt="net-worth-after-mixed"
src="https://github.com/user-attachments/assets/41f08331-f055-4245-beeb-8d071c0d5d6d"
/>
<img width="1440" height="900" alt="net-worth-after-negative"
src="https://github.com/user-attachments/assets/c8610b03-dcff-46de-8987-d47c477ddd85"
/>
2026-07-22 09:32:18 +00:00
..
accounts feat(import): persist per-account import configuration on the backend (#698) 2026-07-18 16:10:40 +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 feat(cashflow): expand income categories and fix Sankey label overlap (#672) 2026-07-13 07:28:14 +00:00
dashboard fix(dashboard): render negative net worth as a downward bar (#722) 2026-07-22 09:32:18 +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(onboarding): stop the AI rule-suggestion step from hanging forever (#705) 2026-07-20 18:10:51 +00:00
open-banking feat: reuse the upgrade modal at more upsell points and attribute revenue (#699) 2026-07-18 12:53:20 +00:00
partials refactor: remove HIDE_AUTH_BUTTONS launch gate and waitlist apparatus (#717) 2026-07-22 08:51:48 +02:00
shared feat(ai): auto-categorize transactions with AI (behind flag) (#535) 2026-06-15 16:35:20 +02:00
subscription feat: reuse the upgrade modal at more upsell points and attribute revenue (#699) 2026-07-18 12:53:20 +00:00
transactions feat(import): persist per-account import configuration on the backend (#698) 2026-07-18 16:10:40 +02:00
ui fix(dashboard): render negative net worth as a downward bar (#722) 2026-07-22 09:32:18 +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
pro-badge.tsx feat(mcp): read-only MCP server for Pro accounts (#689) 2026-07-17 16:54:15 +02: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