import { useData, usePublish, type PanelRef } from '@jaspers-ai/sdk' import { useEffect, useState } from 'react' import { fmt } from './format' import './styles.css' interface Order { id: string symbol: string | null side: string | null type: string | null time_in_force: string | null qty: number | null notional: number | null filled_qty: number | null limit_price: number | null stop_price: number | null filled_avg_price: number | null status: string | null submitted_at: string | null parent_id: string | null } interface State { status?: 'open' | 'closed' | 'all' limit?: number live_account?: boolean } const OPEN = new Set(['new', 'accepted', 'pending_new', 'partially_filled', 'accepted_for_bidding', 'held', 'calculated', 'pending_replace']) function statusClass(status: string | null): string { if (status === 'filled') return 'al-status-filled' if (status && OPEN.has(status)) return 'al-status-open' if (status === 'rejected' || status === 'suspended') return 'al-status-bad' return 'al-status-dead' } export function Blotter({ panel }: { panel: PanelRef }) { const state = useData(`workspaces/${panel.workspaceId}/panels/${panel.id}/state`) as State | undefined if (state === undefined) return

Loading…

return ( ) } function Table({ panel, status, limit, live }: { panel: PanelRef; status: string; limit: number; live: boolean }) { const { data, error, refetch } = useData('alpaca/orders', { status, limit, live_account: live }) const rows = (data ?? []) as Order[] // Per order: 'confirm' after the first click, 'cancelling' after the second, or a result message. const [phase, setPhase] = useState>({}) const set = (id: string, value: string | null) => setPhase((p) => { const next = { ...p } if (value === null) delete next[id] else next[id] = value return next }) usePublish(panel, { status, count: rows.length, open: rows.filter((r) => r.status && OPEN.has(r.status)).length }) return (

Alpaca orders · {status}

{live ? 'LIVE' : 'paper'}
{error ? (

{error}

) : !data ? (

Loading…

) : rows.length === 0 ? (

No {status === 'all' ? '' : `${status} `}orders.

) : (
{rows.map((o) => { const p = phase[o.id] const cancellable = o.status !== null && OPEN.has(o.status) return ( ) })}
Submitted Symbol Side Type Qty Filled Limit Stop Avg fill TIF Status
{o.submitted_at?.replace('T', ' ').slice(0, 19) ?? '–'} {o.parent_id ? `↳ ${o.symbol}` : o.symbol} {o.side} {o.type} {o.qty !== null ? fmt(o.qty, 0) : o.notional !== null ? `$${fmt(o.notional)}` : '–'} {fmt(o.filled_qty, 0)} {fmt(o.limit_price)} {fmt(o.stop_price)} {fmt(o.filled_avg_price)} {o.time_in_force} {o.status} {!cancellable && !p ? null : p === undefined ? ( ) : p === 'confirm' ? ( <> {' '} ) : p === 'cancelling' ? ( { set(o.id, message) refetch?.() }} /> ) : ( {p} )}
)} ) } /** Mounted only after the user clicked Cancel and then Confirm: this is the one place cancel-order runs. */ function Canceller({ id, live, onDone }: { id: string; live: boolean; onDone: (message: string) => void }) { const { data, error } = useData('alpaca/cancel-order', { order_id: id, live_account: live }) useEffect(() => { if (error) onDone(`Not cancelled: ${error}`) else if (data) onDone('Cancel requested') // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, error]) return Cancelling… }