Add a wipe & recalculate button to the account detail page
Cached daily P&L rows outlive what the Cash History report still produces (stale pre-roll dates being the recent example), and nothing deletes them any more. POST /api/accounts/[id]/wipe drops the account's cached P&L, fund transactions and meta, clears the in-memory view, and rebuilds from a fresh report — fetchDaysTraded now takes an optional account filter so one account doesn't cost a report round-trip on every other. The button confirms inline first: rows older than the report's 28-day window cannot be refetched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
bd72c4b1f6
commit
5a69ea2f64
@@ -80,6 +80,69 @@ function fmt(value: number) {
|
||||
return value.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
/** Wipes the account's cached P&L / fund / meta rows and refetches from
|
||||
* Tradovate. Confirmed inline because rows older than the report's 28-day
|
||||
* window cannot come back. */
|
||||
function WipeDataButton({ accountId, onDone }: { accountId: number; onDone: () => void }) {
|
||||
const [confirming, setConfirming] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
async function wipe() {
|
||||
setBusy(true);
|
||||
setError('');
|
||||
try {
|
||||
const res = await fetch(`/api/accounts/${accountId}/wipe`, { method: 'POST' });
|
||||
const body = await res.json().catch(() => ({}));
|
||||
if (!res.ok) throw new Error(body.error ?? 'Wipe failed');
|
||||
if (body.fetchError) setError(`Wiped, but refetch: ${body.fetchError}`);
|
||||
setConfirming(false);
|
||||
onDone();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Wipe failed');
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (confirming) {
|
||||
return (
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<span className="text-xs text-slate-500">
|
||||
Wipe cached data and refetch? Days older than the 28-day report window are lost for good.
|
||||
</span>
|
||||
<button
|
||||
onClick={wipe}
|
||||
disabled={busy}
|
||||
className="px-2.5 py-1 bg-red-100 hover:bg-red-200 disabled:opacity-50 text-red-700 text-xs font-semibold rounded-md transition-colors"
|
||||
>
|
||||
{busy ? 'Refetching…' : 'Wipe & recalculate'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setConfirming(false); setError(''); }}
|
||||
disabled={busy}
|
||||
className="px-2.5 py-1 bg-slate-100 hover:bg-slate-200 disabled:opacity-50 text-slate-600 text-xs font-semibold rounded-md transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
{error && <span className="text-xs text-red-500">{error}</span>}
|
||||
<button
|
||||
onClick={() => setConfirming(true)}
|
||||
className="px-2.5 py-1 text-xs font-semibold text-slate-400 hover:text-red-600 hover:bg-red-50 rounded-md transition-colors"
|
||||
title="Delete this account's cached daily P&L, fund transactions and meta, then rebuild them from a fresh Tradovate report"
|
||||
>
|
||||
Wipe & recalculate
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function fmtDate(iso: string) {
|
||||
const [, m, d] = iso.split('-');
|
||||
return `${parseInt(m)}/${parseInt(d)}`;
|
||||
@@ -410,6 +473,7 @@ export default function AccountPage() {
|
||||
☠ DEAD
|
||||
</span>
|
||||
)}
|
||||
<WipeDataButton accountId={account.id} onDone={() => { void load(); }} />
|
||||
</div>
|
||||
|
||||
{isDead && (
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getClients } from '@/lib/clients';
|
||||
import { wipeAccountData } from '@/lib/db';
|
||||
|
||||
/**
|
||||
* POST /api/accounts/[id]/wipe — drop every cached row for the account and
|
||||
* rebuild it from a fresh Tradovate report.
|
||||
*
|
||||
* Destructive on purpose: cached rows older than the report's 28-day window
|
||||
* cannot be refetched. The UI warns and confirms before calling this.
|
||||
*/
|
||||
export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const accountId = Number(id);
|
||||
if (!Number.isInteger(accountId)) {
|
||||
return NextResponse.json({ error: 'Bad account id' }, { status: 400 });
|
||||
}
|
||||
|
||||
for (const client of getClients().values()) {
|
||||
const account = client.accountList.find((a) => a.id === accountId);
|
||||
if (!account) continue;
|
||||
|
||||
wipeAccountData(accountId);
|
||||
|
||||
// Clear the in-memory view too — a failed refetch must leave the
|
||||
// account visibly empty, not showing the data that was just wiped.
|
||||
delete client.dailyPnL[accountId];
|
||||
delete client.fullDailyPnL[accountId];
|
||||
delete client.daysTraded[accountId];
|
||||
delete client.priorProfit[accountId];
|
||||
delete client.lastFundDates[accountId];
|
||||
delete client.fundTransactions[accountId];
|
||||
delete client.lastFetchErrors[account.name];
|
||||
delete client.lastFetchRaw[account.name];
|
||||
|
||||
await client.fetchDaysTraded([accountId]);
|
||||
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
entries: (client.dailyPnL[accountId] ?? []).length,
|
||||
daysTraded: client.daysTraded[accountId] ?? 0,
|
||||
fetchError: client.lastFetchErrors[account.name] ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: 'Account not found' }, { status: 404 });
|
||||
}
|
||||
Reference in New Issue
Block a user