ui: explicit Save button for shadow book settings
The shadow book toggle saved on change and the numeric fields on blur, inconsistent with every other admin panel (which stages edits behind a Save button). Stage all four fields in local state and commit them together on Save, with an unsaved-changes hint. This also makes enabling the live-trade toggle a deliberate two-step action rather than an unguarded single click. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,15 @@ export function PerformanceSettings() {
|
||||
|
||||
if (window.isLoading || shadow.isLoading || !book) return <SkeletonCard />;
|
||||
|
||||
// Staged edits: nothing about the shadow book persists until Save, matching
|
||||
// the other admin panels — and making the live-trade toggle deliberate.
|
||||
const bookDirty =
|
||||
!!shadow.data &&
|
||||
(book.enabled !== shadow.data.enabled ||
|
||||
book.capacity !== shadow.data.capacity ||
|
||||
book.risk_pct !== shadow.data.risk_pct ||
|
||||
book.start_equity !== shadow.data.start_equity);
|
||||
|
||||
return (
|
||||
<div className="glass space-y-5 p-5">
|
||||
<div>
|
||||
@@ -106,14 +115,15 @@ export function PerformanceSettings() {
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={book.enabled}
|
||||
onChange={(e) => saveBook.mutate({ enabled: e.target.checked })}
|
||||
onChange={(e) => setBook({ ...book, enabled: e.target.checked })}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<span>
|
||||
<span className="text-sm text-gray-200">Shadow book enabled</span>
|
||||
<span className="block text-[11px] leading-relaxed text-gray-500">
|
||||
Starts opening real paper positions automatically on the next near-close scan. Verify its
|
||||
first selections match a backtest of that day's cross-section before trusting the curve.
|
||||
Starts opening real paper positions automatically on the next near-close scan — takes
|
||||
effect when you Save. Verify its first selections match the top-ranked qualified setups
|
||||
before trusting the curve.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
@@ -127,7 +137,6 @@ export function PerformanceSettings() {
|
||||
max={100}
|
||||
value={book.capacity}
|
||||
onChange={(e) => setBook({ ...book, capacity: Number(e.target.value) })}
|
||||
onBlur={() => saveBook.mutate({ capacity: book.capacity })}
|
||||
className="input-glass w-full px-3 py-2 text-sm"
|
||||
/>
|
||||
</label>
|
||||
@@ -140,7 +149,6 @@ export function PerformanceSettings() {
|
||||
max={10}
|
||||
value={book.risk_pct}
|
||||
onChange={(e) => setBook({ ...book, risk_pct: Number(e.target.value) })}
|
||||
onBlur={() => saveBook.mutate({ risk_pct: book.risk_pct })}
|
||||
className="input-glass w-full px-3 py-2 text-sm"
|
||||
/>
|
||||
</label>
|
||||
@@ -152,7 +160,6 @@ export function PerformanceSettings() {
|
||||
step={1000}
|
||||
value={book.start_equity}
|
||||
onChange={(e) => setBook({ ...book, start_equity: Number(e.target.value) })}
|
||||
onBlur={() => saveBook.mutate({ start_equity: book.start_equity })}
|
||||
className="input-glass w-full px-3 py-2 text-sm"
|
||||
/>
|
||||
</label>
|
||||
@@ -161,6 +168,20 @@ export function PerformanceSettings() {
|
||||
Defaults match the validated configuration: 10 positions, 1% fixed-fractional risk. Start
|
||||
equity is only a sizing base — the books are compared in R-multiples, not currency.
|
||||
</p>
|
||||
|
||||
<div className="mt-4 flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
className="btn-primary px-4 py-2 text-sm disabled:opacity-50"
|
||||
disabled={!bookDirty || saveBook.isPending}
|
||||
onClick={() => saveBook.mutate(book)}
|
||||
>
|
||||
{saveBook.isPending ? 'Saving…' : 'Save shadow book'}
|
||||
</button>
|
||||
{bookDirty && !saveBook.isPending && (
|
||||
<span className="text-[11px] text-amber-400/80">Unsaved changes</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user