Add a bulk wipe & recalculate button to the dashboard

The per-account wipe works one account at a time; when every account is
carrying stale cached rows (the pre-roll dates again), that's a lot of
clicking. POST /api/accounts/wipe-all drops the cached rows for every
account on every firm and rebuilds them all from fresh reports — firms
in parallel, accounts within a firm at fetchDaysTraded's own pacing.
The toolbar button confirms inline with the same 28-day-window warning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-09-08 21:36:36 -05:00
co-authored by Claude Fable 5
parent ef476ccbb7
commit 3516e6c288
2 changed files with 106 additions and 0 deletions
+67
View File
@@ -244,6 +244,72 @@ function CredentialForm({ firm, onSaved }: { firm: FirmConfig; onSaved: () => vo
);
}
/** Bulk form of the detail page's wipe: drops every account's cached rows on
* every firm and refetches them all. One report per account, so the busy state
* can run for minutes on a big roster. Same 28-day-window caveat, so the
* confirm step spells it out. */
function WipeAllButton() {
const [confirming, setConfirming] = useState(false);
const [busy, setBusy] = useState(false);
const [result, setResult] = useState('');
async function wipeAll() {
setBusy(true);
setResult('');
try {
const res = await fetch('/api/accounts/wipe-all', { method: 'POST' });
const body = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(body.error ?? 'Wipe failed');
setResult(body.failedAccounts > 0
? `Rebuilt ${body.wiped}, ${body.failedAccounts} refetch failure(s)`
: `Rebuilt ${body.wiped} account(s)`);
// No refresh hook needed — the dashboard polls /api/state every 5s.
setConfirming(false);
} catch (err) {
setResult(err instanceof Error ? err.message : 'Wipe failed');
} finally {
setBusy(false);
}
}
if (confirming) {
return (
<div className="flex items-center gap-2">
<span className="text-xs text-slate-500 max-w-64 text-right">
Wipe every account&apos;s cached data and refetch? Days older than the 28-day report window are lost for good.
</span>
<button
onClick={wipeAll}
disabled={busy}
className="px-4 py-2 bg-red-100 hover:bg-red-200 disabled:opacity-50 text-red-700 text-sm font-semibold rounded-lg transition-colors whitespace-nowrap"
>
{busy ? 'Refetching…' : 'Wipe all'}
</button>
<button
onClick={() => setConfirming(false)}
disabled={busy}
className="px-4 py-2 bg-white hover:bg-slate-100 disabled:opacity-50 text-slate-600 text-sm font-semibold rounded-lg border border-slate-200 transition-colors"
>
Cancel
</button>
</div>
);
}
return (
<div className="flex items-center gap-2">
{result && <span className="text-xs text-slate-400">{result}</span>}
<button
onClick={() => setConfirming(true)}
className="px-4 py-2 bg-white text-slate-600 border border-slate-200 hover:text-red-600 hover:bg-red-50 hover:border-red-200 text-sm font-semibold rounded-lg transition-colors whitespace-nowrap"
title="Delete every account's cached daily P&L, fund transactions and meta, then rebuild them from fresh Tradovate reports"
>
Wipe & recalc all
</button>
</div>
);
}
function AccountRow({ account, firm, hideDead, privacy, onAssigned }: {
account: AccountState;
firm: FirmConfig;
@@ -623,6 +689,7 @@ export default function Home() {
<div className="flex items-center justify-between mb-6">
<h1 className="text-2xl font-bold text-slate-900">AutoTrader</h1>
<div className="flex items-center gap-2">
<WipeAllButton />
{deadCount > 0 && (
<button
onClick={() => setHideDead((h) => !h)}