'use client'; import { useMemo, useState } from 'react'; import { Badge, Box, Button, Flex, Menu, Portal, SimpleGrid, Stat, Text, } from '@chakra-ui/react'; import { getFormatted } from '@tradejs/core/backtest'; import type { RuntimeStrategyView, TestThresholdsKey } from '@tradejs/types'; import { formatDateTime, OrdersDrawerPanel, } from '#components/Shared/OrdersDrawer'; import { RuntimeStrategyChart } from './RuntimeStrategyChart'; import { RuntimeStrategyConfigDrawer } from './RuntimeStrategyConfigDrawer'; import { RuntimeStrategyRevisionsDrawer } from './RuntimeStrategyRevisionsDrawer'; import { RuntimeStrategyStatsDrawer } from './RuntimeStrategyStatsDrawer'; import { buildRuntimeStrategyCardViewModel, getColorByLevel, RUNTIME_ORDER_ROW_HEIGHT, } from './RuntimeStrategyCard.presenter'; import { toaster } from '#ui'; const DEPLOYMENT_PRESENTATIONS = [ { colorPalette: 'blue' }, { colorPalette: 'purple' }, { colorPalette: 'pink' }, { colorPalette: 'cyan' }, ] as const; const getDeploymentPresentation = (deploymentId: string) => { const hash = [...deploymentId].reduce( (value, character) => (value * 31 + character.charCodeAt(0)) >>> 0, 0, ); return DEPLOYMENT_PRESENTATIONS[hash % DEPLOYMENT_PRESENTATIONS.length]; }; const StatItem = ({ stat, id, title, }: { stat: RuntimeStrategyView['stat']; id: TestThresholdsKey; title: string; }) => { const { formatted, level } = getFormatted(stat, id); return ( {title} {formatted} ); }; export const RuntimeStrategyCard = ({ strategy, provider, startTimestamp, endTimestamp, onUpdated, }: { strategy: RuntimeStrategyView; provider: string; startTimestamp: number; endTimestamp: number; onUpdated: () => Promise | void; }) => { const [configOpen, setConfigOpen] = useState(false); const [ordersOpen, setOrdersOpen] = useState(false); const [statsOpen, setStatsOpen] = useState(false); const [revisionsOpen, setRevisionsOpen] = useState(false); const [controlSaving, setControlSaving] = useState(false); const viewModel = useMemo( () => buildRuntimeStrategyCardViewModel(strategy), [strategy], ); const { lastTrade, runtimeOrders } = viewModel; const deploymentPresentation = getDeploymentPresentation( strategy.deploymentId, ); const strategyInstanceLabel = `${strategy.strategyName}, ${strategy.deploymentLabel}`; const setControlState = async (controlState: 'active' | 'entries_paused') => { setControlSaving(true); try { const response = await fetch( `/api/user/runtime-deployments/${encodeURIComponent(strategy.deploymentId)}/strategies/${encodeURIComponent(strategy.strategyName)}/control`, { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ controlState }), }, ); const payload = (await response.json()) as { error?: string }; if (!response.ok) throw new Error(payload.error || 'Update failed'); toaster.success({ title: controlState === 'entries_paused' ? 'New entries paused' : 'Strategy resumed', }); await onUpdated(); } catch (error) { toaster.error({ title: 'Could not update strategy state', description: error instanceof Error ? error.message : String(error), }); } finally { setControlSaving(false); } }; return ( {strategy.strategyName} {strategy.deploymentLabel} {strategy.controlState === 'entries_paused' ? 'entries paused' : strategy.enabled ? 'enabled' : 'disabled'} {strategy.universe} TF: {strategy.interval}m connector: {provider} trades: {strategy.summary.totalTrades} active: 0 ? 'orange.300' : 'gray.200' } > {strategy.summary.activeTrades} {lastTrade ? `last trade: ${lastTrade.symbol} ${formatDateTime(lastTrade.entryTimestamp)}` : strategy.connected ? 'connected, no runtime trades yet' : 'runtime trades only'} setConfigOpen(true)}> View config void setControlState( strategy.controlState === 'entries_paused' ? 'active' : 'entries_paused', ) } > {strategy.controlState === 'entries_paused' ? 'Resume entries' : 'Pause new entries'} setOrdersOpen(true)}> Orders setStatsOpen(true)}> Stat setRevisionsOpen(true)} > Revisions ); };