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:
co-authored by
Claude Fable 5
parent
ef476ccbb7
commit
3516e6c288
@@ -0,0 +1,39 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { getClients } from '@/lib/clients';
|
||||||
|
import { wipeAccountData } from '@/lib/db';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/accounts/wipe-all — the bulk form of /api/accounts/[id]/wipe:
|
||||||
|
* drop every account's cached rows on every firm and rebuild them all from
|
||||||
|
* fresh Tradovate reports. One report per account, so this takes a while on
|
||||||
|
* big rosters; firms run in parallel, accounts within a firm sequentially
|
||||||
|
* (that's fetchDaysTraded's own pacing against the flaky report server).
|
||||||
|
*
|
||||||
|
* Same destructive caveat as the single wipe: cached rows older than the
|
||||||
|
* report's 28-day window cannot be refetched. The UI confirms first.
|
||||||
|
*/
|
||||||
|
export async function POST() {
|
||||||
|
const clients = [...getClients().values()];
|
||||||
|
|
||||||
|
let wiped = 0;
|
||||||
|
let failedAccounts = 0;
|
||||||
|
|
||||||
|
await Promise.all(clients.map(async (client) => {
|
||||||
|
for (const account of client.accountList) {
|
||||||
|
wipeAccountData(account.id);
|
||||||
|
delete client.dailyPnL[account.id];
|
||||||
|
delete client.fullDailyPnL[account.id];
|
||||||
|
delete client.daysTraded[account.id];
|
||||||
|
delete client.priorProfit[account.id];
|
||||||
|
delete client.lastFundDates[account.id];
|
||||||
|
delete client.fundTransactions[account.id];
|
||||||
|
delete client.lastFetchErrors[account.name];
|
||||||
|
delete client.lastFetchRaw[account.name];
|
||||||
|
wiped++;
|
||||||
|
}
|
||||||
|
const result = await client.fetchDaysTraded();
|
||||||
|
failedAccounts += result.failedAccounts;
|
||||||
|
}));
|
||||||
|
|
||||||
|
return NextResponse.json({ ok: true, wiped, failedAccounts });
|
||||||
|
}
|
||||||
@@ -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'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 }: {
|
function AccountRow({ account, firm, hideDead, privacy, onAssigned }: {
|
||||||
account: AccountState;
|
account: AccountState;
|
||||||
firm: FirmConfig;
|
firm: FirmConfig;
|
||||||
@@ -623,6 +689,7 @@ export default function Home() {
|
|||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h1 className="text-2xl font-bold text-slate-900">AutoTrader</h1>
|
<h1 className="text-2xl font-bold text-slate-900">AutoTrader</h1>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<WipeAllButton />
|
||||||
{deadCount > 0 && (
|
{deadCount > 0 && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setHideDead((h) => !h)}
|
onClick={() => setHideDead((h) => !h)}
|
||||||
|
|||||||
Reference in New Issue
Block a user