diff --git a/HISTORY.md b/HISTORY.md index 20cb5d4..e92e468 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -28,6 +28,12 @@ - **Pin Due — urgent bills float to top of tracker** — A "Pin Due" toggle button in the TrackerPage header sorts overdue and due-soon bills to the top of each bucket when enabled. Priority order: `missed` → `late` → `due_soon` → `upcoming` → everything else; ties broken by `due_day`. The sort runs after filtering but before the bucket split, so each half-month bucket is sorted independently. The button uses `variant="default"` (solid) when active and `variant="outline"` when off so the current mode is always visible. Preference persists across sessions via `localStorage` under `tracker_pin_upcoming`. Drag reorder is automatically disabled while the toggle is on (`reorderEnabled` now also requires `!pinUpcoming`) since the two modes conflict. +- **Bank tracking pending deduction corrected — no double-counting** — The pending payments window was subtracting all recent payments regardless of source, including bank-synced ones (`payment_source = 'provider_sync'`). Since the live bank balance already reflects those outgoing transactions, subtracting them again produced a balance ~$2k lower than reality. Fixed in both `trackerService.js` and `summary.js` by adding `AND (p.payment_source IS NULL OR p.payment_source != 'provider_sync')` to the pending query. Only manually-entered payments and transaction-matched payments are now counted as pending — those are payments the user recorded in the tracker before the bank has seen them. The `pending_cleared` badge on tracker rows was given the same fix. Additionally, `b.name` was missing from the `SELECT` in `getOverdueCount`, causing the tooltip to show `null` names — corrected. + +- **Bank tracking: projected month-end balance added** — The CalendarPage Monthly Money Map (bank mode) now shows a dedicated "Projected Month-End Balance" row below the four metrics. It displays the full equation inline — `$5,461 bank − $1,737 pending − $3,696 remaining bills` — so the source of the number is always visible. The row uses a green/red border depending on whether the projection is positive or negative. The TrackerPage Starting card hint also updated from `account_name · live balance` to `account_name · projected $X after bills` so the projection is visible without navigating to Calendar. + +- **Bank tracking settings and account picker fixed** — The `loadBankTracking` function in `BankSyncSection` called `api.getSettings()` but the API method is `api.settings()`. The wrong name threw `TypeError: api.getSettings is not a function`, silently caught by the outer `catch {}`, so neither the toggle state nor the account list ever loaded. Renamed to `api.settings()`. All 19 financial accounts now appear in the picker and the enabled/account/pending-days settings persist correctly across page loads. + - **Overdue badge: "due today" no longer counts as overdue + tooltip added** — `getOverdueCount` changed `dueDate > todayStr` to `dueDate >= todayStr` so bills due today are not counted as past due — only bills strictly in the past trigger the badge. The function now also returns `names: [...]` alongside the count. Both the sidebar `TrackerMenu` (desktop) and `NavPill` (mobile) wrap the badge in a `` that shows "2 past due · Discord Nitro · Camry" on hover, so the number is immediately explained without navigating anywhere. Up to 5 names are shown with a "+N more" overflow line. - **Column labels larger and lighter in tracker table** — `TableHead` elements in `TrackerBucket` changed from `text-[10px] font-semibold tracking-widest` to `text-xs font-medium tracking-wider`. Size increased from 10 px to 12 px for readability; weight dropped from semibold to medium so the labels don't visually compete with the bold bill name text in each row. diff --git a/client/pages/CalendarPage.jsx b/client/pages/CalendarPage.jsx index 3df0e0f..93982ad 100644 --- a/client/pages/CalendarPage.jsx +++ b/client/pages/CalendarPage.jsx @@ -168,6 +168,32 @@ function MoneyMap({ summaryData, loading }) { )} + {bankMode && ( +
= 0 + ? 'border-emerald-500/25 bg-emerald-500/5' + : 'border-destructive/25 bg-destructive/5', + )}> +
+

+ Projected Month-End Balance +

+

+ {fmt(bt.balance || 0)} bank + {Number(bt.pending_payments || 0) > 0 && ` − ${fmt(bt.pending_payments)} pending`} + {` − ${fmt(bt.unpaid_this_month || 0)} remaining bills`} +

+
+

= 0 ? 'text-emerald-600 dark:text-emerald-400' : 'text-destructive', + )}> + {Number(bt.remaining || 0) < 0 ? '−' : ''}{fmt(Math.abs(Number(bt.remaining || 0)))} +

+
+ )} + {bankMode && bt.last_updated && (

Balance last updated: {new Date(bt.last_updated).toLocaleString()} diff --git a/client/pages/TrackerPage.jsx b/client/pages/TrackerPage.jsx index 54dea2e..3854c65 100644 --- a/client/pages/TrackerPage.jsx +++ b/client/pages/TrackerPage.jsx @@ -489,7 +489,11 @@ export default function TrackerPage() { type="starting" value={summary.total_starting} hint={(() => { - if (bankTracking?.enabled) return `${bankTracking.account_name} · live balance`; + if (bankTracking?.enabled) { + const proj = Number(bankTracking.remaining ?? 0); + const sign = proj < 0 ? '−' : ''; + return `${bankTracking.account_name} · projected ${sign}${fmt(Math.abs(proj))} after bills`; + } if (!summary.has_starting_amounts) return 'Set monthly starting cash'; if (cashflow?.has_data && cashflow.period_projected !== undefined) { const proj = Number(cashflow.period_projected);