feat(simple): add setup timeline and id copy
This commit is contained in:
parent
beb75c1d89
commit
d7516c440a
@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user