From 2b710c459b71d97bb1353d7ab8406780f171cba8 Mon Sep 17 00:00:00 2001 From: null Date: Fri, 3 Jul 2026 18:49:35 -0500 Subject: [PATCH] =?UTF-8?q?feat(tracker):=20summary-card=20redesign=20?= =?UTF-8?q?=E2=80=94=20de-dup=20Paid,=20surface=20Remaining=20+=20progress?= =?UTF-8?q?=20(T3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The summary row showed 'Paid' twice (this month + last month), indistinguishable, while the actionable remaining only appeared in the CashFlow card below. Now: - previous-month becomes a muted 'Last month: $X' hint under Total Paid (not a second green box); - a Remaining card (existing unused blue type) surfaces summary.remaining when there's no bank hero (bank mode already shows projected remaining on the hero); - a compact '$X of $Y paid · N bills left' progress line under the cards. Respects tracker_show_summary_cards. Visual baseline for / may need a refresh. Co-Authored-By: Claude Opus 4.8 --- HISTORY.md | 4 ++++ client/pages/TrackerPage.jsx | 30 ++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index fbae9ba..7e065a7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -9,6 +9,10 @@ - **[Tracker] Killed the getTracker N+1 (was ~2–3 DB round-trips × N bills every home-page load)** — inside `bills.map`, `getTracker` ran a payments query per bill (`fetchPaymentsForBillCycle`) plus `computeAmountSuggestion` per bill, and the suggestion alone fired up to 12 queries per bill (6 months × 2) — roughly 70–450 queries for a 35-bill account on every Tracker load. Now one query fetches all bills' cycle payments (grouped in JS by each bill's own range) and two queries compute all amount suggestions (`computeAmountSuggestionsBatch`), replacing the per-bill loops. Behavior-preserving — `tests/amountSuggestionService.test.js` pins the batched suggestion to be byte-identical to the per-bill function, and the `trackerService` tests still pass unchanged. (Tracker P1) +### 🎨 Tracker summary cards + +- **[Tracker] Fixed the two identical "Paid" boxes + surfaced Remaining + month-progress** — the summary row rendered `SummaryCard type="paid"` **twice** (this month and last month), visually indistinguishable, while the actionable *remaining* number only lived in the CashFlow card below. Now the previous-month figure is a muted **"Last month: $X" hint** under Total Paid (not a second green box), and a **Remaining** card (the existing-but-unused blue card type) surfaces `summary.remaining` when there's no bank hero (in bank mode the hero already shows the projected remaining, so it isn't duplicated). Added a compact **"$X of $Y paid · N bills left"** progress line under the cards. Respects the `tracker_show_summary_cards` setting; light/dark + a11y unchanged. *(The Playwright visual baseline for `/` may need a refresh via `npm run test:e2e:update` since the card row changed intentionally.)* (Tracker T3) + ### 🚀 Tracker modern UX - **[Tracker] Optimistic pay/skip + `toast.promise` on long syncs** — marking a bill paid/unpaid now flips the row **instantly** (optimistic local state) and rolls back on error, instead of waiting for the server round-trip before updating — on both the desktop and mobile rows. And the bank sync (Tracker) and the bill-modal "Sync" now use sonner's `toast.promise` — one toast that transitions loading → done → error, replacing the manual spinner-flag + separate success/error toasts. (Tracker M1/M2) diff --git a/client/pages/TrackerPage.jsx b/client/pages/TrackerPage.jsx index 5cf23fc..119ccae 100644 --- a/client/pages/TrackerPage.jsx +++ b/client/pages/TrackerPage.jsx @@ -846,13 +846,39 @@ export default function TrackerPage() { onEdit={() => setEditStartingOpen(true)} /> )} - + 0 ? `Last month: ${fmt(summary.previous_month_total)}` : undefined} + /> + {/* In bank mode the hero card already surfaces the projected remaining, + so only show the Remaining card when there's no bank hero. */} + {!bankTracking?.enabled && ( + + )} - {summary.trend && } ) : null} + {/* Compact month-progress line — quick "how far through the month am I?" */} + {showSummaryCards && !loading && !isError && summary?.total_expected > 0 && (() => { + const billsLeft = (summary.count_upcoming ?? 0) + (summary.count_late ?? 0); + return ( +

+ {fmt(summary.paid_toward_due)} + {' of '} + {fmt(summary.total_expected)} + {' paid'} + {billsLeft > 0 && <> · {billsLeft} bill{billsLeft === 1 ? '' : 's'} left} +

+ ); + })()} + {/* ── Safe to Spend ── */} {!isError && !loading && showSafeToSpend && isCurrentMonth && cashflow && (