From 3951767ab1239b5444bd32f90e59dbd73bde0df2 Mon Sep 17 00:00:00 2001 From: Saravana Achu Mac Date: Sat, 9 May 2026 02:11:52 -0700 Subject: [PATCH] refactor(ui): standardize operations table badges --- web/src/tabs/HistoryTab.tsx | 17 ++-- web/src/tabs/PositionsTab.tsx | 162 ++++++++++++++++------------------ 2 files changed, 85 insertions(+), 94 deletions(-) diff --git a/web/src/tabs/HistoryTab.tsx b/web/src/tabs/HistoryTab.tsx index f550860..a6c976d 100644 --- a/web/src/tabs/HistoryTab.tsx +++ b/web/src/tabs/HistoryTab.tsx @@ -7,7 +7,7 @@ import { import { useCanonicalLifecycle } from '../hooks/useCanonicalLifecycle'; import { fetchTradeHistory } from '../lib/tradeHistoryApi'; import { fetchPositionsBootstrap } from '../lib/positionsApi'; -import { Button, Input, Select } from '../components/ui/Primitives'; +import { Badge, Button, Input, Select } from '../components/ui/Primitives'; interface TradeRecord { @@ -53,6 +53,9 @@ interface HistoryTabProps { botState?: any; } +const historySourceBadgeVariant = (source?: 'BOT' | 'MANUAL') => source === 'BOT' ? 'accent' : 'warning'; +const historySideBadgeVariant = (side: string) => side === 'BUY' ? 'success' : side === 'SELL' ? 'danger' : 'neutral'; + export interface HistoryDateBounds { from: number | null; to: number | null; @@ -587,11 +590,11 @@ export const HistoryTab = ({ botState }: HistoryTabProps) => { className={`${isLoss ? 'bg-red-500/[0.06] border-l-2 border-l-red-500/50' : ''} hover:bg-white/[0.02] transition-colors`} > - + {t.source === 'BOT' ? (t.profile_id ? (profiles.find(p => p.id === t.profile_id)?.name || 'BOT') : 'BOT') : 'MANUAL'} - + {(t.timestamp || t.created_at) ? new Date(t.timestamp || t.created_at!).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '-'} @@ -604,9 +607,9 @@ export const HistoryTab = ({ botState }: HistoryTabProps) => { {t.symbol} - + {t.side} - + {capitalUsed !== null @@ -630,9 +633,9 @@ export const HistoryTab = ({ botState }: HistoryTabProps) => { ${pnlValue.toFixed(2)} {isLoss && ( -
+ Loss Alert -
+ )} diff --git a/web/src/tabs/PositionsTab.tsx b/web/src/tabs/PositionsTab.tsx index 931b880..4d6e7c1 100644 --- a/web/src/tabs/PositionsTab.tsx +++ b/web/src/tabs/PositionsTab.tsx @@ -8,7 +8,7 @@ import { createRequestId } from '../../../shared/request-id.js'; import { Layers, ListFilter, Link2, GitBranch, AlertTriangle, Lock, RefreshCw, CheckCircle, XCircle } from 'lucide-react'; import { useCanonicalLifecycle } from '../hooks/useCanonicalLifecycle'; import { fetchPositionsBootstrap } from '../lib/positionsApi'; -import { Button, Input, Select } from '../components/ui/Primitives'; +import { Badge, Button, Input, Select } from '../components/ui/Primitives'; interface PositionsTabProps { botState: BotState; @@ -274,7 +274,7 @@ export const normalizeOrder = (record: RawOrderRecord, fallbackSource?: OrderSou }; }; -const SourceTruthVariants: Record<'exchange' | 'db' | 'reconciled' | 'unknown', { label: string; className: string }> = { +const SourceTruthVariants: Record<'exchange' | 'db' | 'reconciled' | 'unknown', { label: string; className: string }> = { exchange: { label: 'Exchange', className: 'truth-pill exchange' }, db: { label: 'DB', className: 'truth-pill db' }, reconciled: { label: 'Reconciled', className: 'truth-pill reconciled' }, @@ -293,6 +293,25 @@ const getTruthSourceForPosition = (entryOrder?: NormalizedOrder, canonicalAvaila return SourceTruthVariants.reconciled; }; +const sourceBadgeVariant = (source: 'BOT' | 'MANUAL') => source === 'BOT' ? 'accent' : 'warning'; +const sideBadgeVariant = (side: 'BUY' | 'SELL') => side === 'BUY' ? 'success' : 'danger'; +const actionBadgeVariant = (action: OrderAction) => action === 'ENTRY' ? 'info' : 'warning'; +const orderStatusBadgeVariant = (status: string, stale = false) => { + if (stale) return 'warning'; + if (status === 'filled') return 'success'; + if (status === 'expired') return 'warning'; + if (status === 'unknown') return 'neutral'; + if (status.includes('reject') || status.includes('fail') || status.includes('cancel')) return 'danger'; + if (status.includes('pending') || status.includes('new')) return 'info'; + return 'neutral'; +}; +const lifecycleStateBadgeVariant = (state: string) => { + if (state === 'CLOSED') return 'success'; + if (state === 'PARTIAL_EXIT' || state === 'EXIT_PENDING') return 'warning'; + if (state === 'ORPHAN_EXIT') return 'danger'; + return 'info'; +}; + const getTruthSourceForOrder = (order: NormalizedOrder, historyKeys: Set, canonicalAvailable: boolean = true) => { const status = order.status || ''; if (status.includes('pending') || status.includes('new') || status.includes('accepted')) { @@ -1436,11 +1455,11 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) = return ( - - - {pos.source === 'BOT' ? (pos.profileName || profiles.find(pr => pr.id === pos.profileId)?.name || 'BOT') : 'MANUAL'} - - + + + {pos.source === 'BOT' ? (pos.profileName || profiles.find(pr => pr.id === pos.profileId)?.name || 'BOT') : 'MANUAL'} + + {positionTruth.label} @@ -1458,16 +1477,12 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) = {pos.symbol} - {pos.side} + {pos.side} {pos.planMode ? (
- + {pos.planMode === 'long_term' ? 'Long-term hold' : 'Short-term managed'} - +
) : null} @@ -1479,14 +1494,14 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) = {currentPrice !== null ? `$${currentPrice.toLocaleString()}` : '-'}
{slBreached && ( - + SL breached - + )} {!slBreached && tpHit && ( - + TP hit - + )}
@@ -1704,9 +1719,9 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) = )} - + {order.profileId ? (profiles.find(pr => pr.id === order.profileId)?.name || 'BOT') : 'MANUAL'} - + {truthSource.label} @@ -1715,21 +1730,18 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) = {order.timestamp ? new Date(order.timestamp).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '-'} {order.symbol} - - {resolvedAction ? ( - - {isEntry ? 'ENTRY' : 'EXIT'} - - ) : ( - {order.type} - )} - - - {order.side} - + + {resolvedAction ? ( + + {isEntry ? 'ENTRY' : 'EXIT'} + + ) : ( + {order.type} + )} + + + {order.side} + {Number(order.qty || 0).toFixed(4)} ${Number(order.price).toLocaleString()} @@ -1746,30 +1758,21 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) = const orderAge = order.timestamp ? Date.now() - order.timestamp : 0; const isStale = isPendingNew && orderAge > 5 * 60 * 1000; - let badgeClass = 'bg-white/10 text-gray-400'; - let tooltip = ''; - - if (order.status === 'filled') { - badgeClass = 'bg-green-500/20 text-green-400 border border-green-500/20'; - } else if (isStale) { - badgeClass = 'bg-yellow-500/20 text-yellow-400 border border-yellow-500/20'; - tooltip = 'Order pending for >5 min - sync in progress'; - } else if (isExpired) { - badgeClass = 'bg-orange-500/20 text-orange-400 border border-orange-500/20'; - tooltip = 'Order not found on exchange - likely never executed'; - } else if (isUnknown) { - badgeClass = 'bg-gray-500/20 text-gray-400 border border-gray-500/20'; - tooltip = 'Order status could not be verified'; - } - - return ( -
- - {order.status} - + let tooltip = ''; + + if (isStale) { + tooltip = 'Order pending for >5 min - sync in progress'; + } else if (isExpired) { + tooltip = 'Order not found on exchange - likely never executed'; + } else if (isUnknown) { + tooltip = 'Order status could not be verified'; + } + + return ( +
+ + {order.status} + {isStale && ( ! )} @@ -1940,14 +1943,11 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) =
- - - {trace.profileName} - - + + + {trace.profileName} + + {trace.symbol} @@ -1955,14 +1955,11 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) =
{trace.orderedEvents.map((event) => { const actionLabel: OrderAction = event.action === 'EXIT' ? 'EXIT' : 'ENTRY'; - const actionClass = actionLabel === 'ENTRY' - ? 'bg-blue-500/10 text-blue-300 border border-blue-500/20' - : 'bg-amber-500/10 text-amber-300 border border-amber-500/20'; - return ( -
- - {actionLabel} - + return ( +
+ + {actionLabel} + {event.timestamp ? new Date(event.timestamp).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '-'} @@ -1986,18 +1983,9 @@ export const PositionsTab = ({ botState, onManageHolding }: PositionsTabProps) =
- - {trace.state} - + + {trace.state} +
{trace.stateReason}