feat(tracker): summary-card redesign — de-dup Paid, surface Remaining + progress (T3)
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 <noreply@anthropic.com>
This commit is contained in:
parent
4a76eb9b92
commit
2b710c459b
|
|
@ -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] 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 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)
|
- **[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)
|
||||||
|
|
|
||||||
|
|
@ -846,13 +846,39 @@ export default function TrackerPage() {
|
||||||
onEdit={() => setEditStartingOpen(true)}
|
onEdit={() => setEditStartingOpen(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<SummaryCard type="paid" value={summary.total_paid} />
|
<SummaryCard
|
||||||
|
type="paid"
|
||||||
|
value={summary.total_paid}
|
||||||
|
hint={summary.previous_month_total > 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 && (
|
||||||
|
<SummaryCard
|
||||||
|
type="remaining"
|
||||||
|
value={summary.remaining}
|
||||||
|
hint={summary.remaining_label || undefined}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<SummaryCard type="overdue" value={summary.overdue} />
|
<SummaryCard type="overdue" value={summary.overdue} />
|
||||||
<SummaryCard type="paid" value={summary.previous_month_total} hint="Previous month"/>
|
|
||||||
{summary.trend && <TrendCard trend={summary.trend} />}
|
{summary.trend && <TrendCard trend={summary.trend} />}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : 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 (
|
||||||
|
<p className="px-1 -mt-1 text-[11px] text-muted-foreground">
|
||||||
|
<span className="font-mono font-semibold text-foreground">{fmt(summary.paid_toward_due)}</span>
|
||||||
|
{' of '}
|
||||||
|
<span className="font-mono">{fmt(summary.total_expected)}</span>
|
||||||
|
{' paid'}
|
||||||
|
{billsLeft > 0 && <> · {billsLeft} bill{billsLeft === 1 ? '' : 's'} left</>}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
|
||||||
{/* ── Safe to Spend ── */}
|
{/* ── Safe to Spend ── */}
|
||||||
{!isError && !loading && showSafeToSpend && isCurrentMonth && cashflow && (
|
{!isError && !loading && showSafeToSpend && isCurrentMonth && cashflow && (
|
||||||
<CashFlowCard
|
<CashFlowCard
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue