feat(simple): add setup timeline and id copy

This commit is contained in:
root 2026-05-06 16:08:23 +00:00
parent beb75c1d89
commit d7516c440a

View File

@ -350,6 +350,41 @@ function statusToneClasses(tone: SimpleRuntimeSnapshot['tone']): string {
}
}
const SIMPLE_TIMELINE_STEPS: Array<SimpleRuntimeSnapshot['stage']> = [
'armed',
'entry_submitted',
'filled',
'exit_submitted',
'closed',
];
function isTimelineStepComplete(
current: SimpleRuntimeSnapshot['stage'] | undefined,
step: SimpleRuntimeSnapshot['stage'],
): boolean {
if (!current) return false;
const currentIndex = SIMPLE_TIMELINE_STEPS.indexOf(current);
const stepIndex = SIMPLE_TIMELINE_STEPS.indexOf(step);
return currentIndex >= 0 && stepIndex >= 0 && stepIndex <= currentIndex;
}
function formatTimelineStepLabel(step: SimpleRuntimeSnapshot['stage']) {
switch (step) {
case 'armed':
return 'Armed';
case 'entry_submitted':
return 'Entry sent';
case 'filled':
return 'Filled';
case 'exit_submitted':
return 'Exit sent';
case 'closed':
return 'Closed';
default:
return step;
}
}
function formatSetupUpdatedAt(entry: ManualEntryPayload): string | null {
const raw = String(entry.sell_time || entry.buy_time || '').trim();
if (!raw) return null;
@ -449,6 +484,7 @@ export function SimpleView() {
const [submitting, setSubmitting] = useState(false);
const [loadingPrice, setLoadingPrice] = useState(false);
const [marketPriceSource, setMarketPriceSource] = useState<MarketPriceSource>(null);
const [copiedKey, setCopiedKey] = useState<string | null>(null);
const [message, setMessage] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const marketPriceRequestSymbolRef = useRef<string>('');
@ -572,6 +608,20 @@ export function SimpleView() {
setDraft((prev) => ({ ...prev, [key]: value }));
}
async function copyIdentifier(kind: 'trade' | 'order', value: string | null | undefined) {
if (!value) return;
try {
await navigator.clipboard.writeText(value);
const key = `${kind}:${value}`;
setCopiedKey(key);
window.setTimeout(() => {
setCopiedKey((prev) => (prev === key ? null : prev));
}, 1200);
} catch {
setError(`Failed to copy ${kind} ID`);
}
}
async function refreshSetupList() {
const [profileRows, entryRows] = await Promise.all([
fetchTradeProfiles(),
@ -1077,14 +1127,26 @@ export function SimpleView() {
</span>
) : null}
{(runtimeSnapshot?.tradeId || entry.linked_trade_id) ? (
<span className="rounded-full border border-[var(--border)] px-3 py-1">
Trade {String(runtimeSnapshot?.tradeId || entry.linked_trade_id).slice(0, 18)}
</span>
<button
type="button"
onClick={() => void copyIdentifier('trade', String(runtimeSnapshot?.tradeId || entry.linked_trade_id))}
className="rounded-full border border-[var(--border)] px-3 py-1 transition hover:border-[var(--primary)] hover:text-[var(--foreground)]"
>
{copiedKey === `trade:${String(runtimeSnapshot?.tradeId || entry.linked_trade_id)}`
? 'Trade copied'
: `Trade ${String(runtimeSnapshot?.tradeId || entry.linked_trade_id).slice(0, 18)}`}
</button>
) : null}
{runtimeSnapshot?.orderId ? (
<span className="rounded-full border border-[var(--border)] px-3 py-1">
Order {runtimeSnapshot.orderId.slice(0, 12)}
</span>
<button
type="button"
onClick={() => void copyIdentifier('order', runtimeSnapshot.orderId)}
className="rounded-full border border-[var(--border)] px-3 py-1 transition hover:border-[var(--primary)] hover:text-[var(--foreground)]"
>
{copiedKey === `order:${runtimeSnapshot.orderId}`
? 'Order copied'
: `Order ${runtimeSnapshot.orderId.slice(0, 12)}`}
</button>
) : null}
{updatedAt ? (
<span className="rounded-full border border-[var(--border)] px-3 py-1 normal-case tracking-normal">
@ -1092,6 +1154,27 @@ export function SimpleView() {
</span>
) : null}
</div>
<div className="mt-4 grid gap-2 md:grid-cols-5">
{SIMPLE_TIMELINE_STEPS.map((step) => {
const complete = isTimelineStepComplete(runtimeSnapshot?.stage, step);
const isCurrent = runtimeSnapshot?.stage === step;
return (
<div
key={step}
className={`rounded-2xl border px-3 py-2 text-[11px] font-semibold ${
isCurrent
? 'border-[var(--primary)] bg-[var(--accent-soft)] text-[var(--primary)]'
: complete
? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300'
: 'border-[var(--border)] bg-[var(--background)] text-[var(--muted-foreground)]'
}`}
>
{formatTimelineStepLabel(step)}
</div>
);
})}
</div>
</div>
);
})}