import React, { useCallback, useEffect, useMemo, useState } from 'react'; import VirtualTargetCard, { type VirtualCardDisplayMode } from './VirtualTargetCard'; import VirtualTargetFocusView from './VirtualTargetFocusView'; import type { StrategyDto } from '../../utils/backendApi'; import type { Theme } from '../../types'; import type { ChartRealtimeSource } from '../../hooks/useChartRealtimeData'; import { type VirtualTargetItem, type VirtualCardViewMode, type VirtualSessionConfig, resolveTargetCandleType, } from '../../utils/virtualTradingStorage'; import type { VirtualLiveStatus } from '../../hooks/useVirtualTargetLiveStatus'; import type { VirtualIndicatorSnapshot } from '../../hooks/useVirtualIndicatorSnapshots'; import type { TickerData } from '../../hooks/useMarketTicker'; interface Props { targets: VirtualTargetItem[]; strategies: StrategyDto[]; snapshots: Record; session: Pick; onTargetStrategyChange: (market: string, strategyId: number | null) => void; onTargetCandleTypeChange: (market: string, candleType: string) => void; liveStatusByMarket?: Record; lastTickAtByMarket?: Record; selectedMarket?: string; onSelectMarket?: (market: string) => void; theme?: Theme; chartRealtimeSource?: ChartRealtimeSource; chartSeriesPriceLabels?: boolean; chartLiveReceiveHighlight?: boolean; tickers?: Map; viewMode: VirtualCardViewMode; globalDisplayMode: VirtualCardDisplayMode; onFocusMarketChange?: (market: string | null) => void; } const VirtualTargetGrid: React.FC = ({ targets, strategies, snapshots, session, onTargetStrategyChange, onTargetCandleTypeChange, liveStatusByMarket = {}, lastTickAtByMarket = {}, selectedMarket, onSelectMarket, theme = 'dark', chartRealtimeSource = 'BACKEND_STOMP', chartSeriesPriceLabels = true, chartLiveReceiveHighlight = true, tickers, viewMode, globalDisplayMode, onFocusMarketChange, }) => { const [cardDisplayOverrides, setCardDisplayOverrides] = useState>({}); const [focusMarket, setFocusMarket] = useState(null); const activeMarkets = useMemo(() => new Set(targets.map(t => t.market)), [targets]); useEffect(() => { if (focusMarket && !activeMarkets.has(focusMarket)) { setFocusMarket(null); onFocusMarketChange?.(null); } }, [activeMarkets, focusMarket, onFocusMarketChange]); useEffect(() => { onFocusMarketChange?.(focusMarket); }, [focusMarket, onFocusMarketChange]); useEffect(() => { setCardDisplayOverrides(prev => { const next: Record = {}; let changed = false; for (const [market, mode] of Object.entries(prev)) { if (activeMarkets.has(market)) next[market] = mode; else changed = true; } return changed ? next : prev; }); }, [activeMarkets]); useEffect(() => { setCardDisplayOverrides({}); }, [globalDisplayMode]); const handleCardDisplayMode = useCallback((market: string, mode: VirtualCardDisplayMode) => { setCardDisplayOverrides(prev => ({ ...prev, [market]: mode })); }, []); const resolveCardDisplayMode = useCallback( (market: string) => cardDisplayOverrides[market] ?? globalDisplayMode, [cardDisplayOverrides, globalDisplayMode], ); const focusTarget = useMemo( () => (focusMarket ? targets.find(t => t.market === focusMarket) : undefined), [focusMarket, targets], ); if (targets.length === 0) { return (

좌측에서 투자대상 종목을 추가하면 카드가 표시됩니다.

); } return (
{focusMarket && focusTarget ? ( s.id === (focusTarget.strategyId ?? session.globalStrategyId))} strategies={strategies} strategyId={focusTarget.strategyId} globalStrategyId={session.globalStrategyId} onStrategyChange={id => onTargetStrategyChange(focusTarget.market, id)} candleType={resolveTargetCandleType(focusTarget, snapshots[focusTarget.market]?.timeframe)} onCandleTypeChange={ct => onTargetCandleTypeChange(focusTarget.market, ct)} snapshot={snapshots[focusTarget.market]} running={session.running} liveStatus={liveStatusByMarket[focusTarget.market] ?? (session.running ? 'connecting' : 'idle')} lastTickAt={lastTickAtByMarket[focusTarget.market]} viewMode={viewMode} onExit={() => setFocusMarket(null)} theme={theme} chartRealtimeSource={chartRealtimeSource} chartSeriesPriceLabels={chartSeriesPriceLabels} chartLiveReceiveHighlight={chartLiveReceiveHighlight} ticker={tickers?.get(focusTarget.market)} /> ) : (
{targets.map(t => { const strat = strategies.find(s => s.id === (t.strategyId ?? session.globalStrategyId)); return ( onTargetStrategyChange(t.market, id)} candleType={resolveTargetCandleType(t, snapshots[t.market]?.timeframe)} onCandleTypeChange={ct => onTargetCandleTypeChange(t.market, ct)} snapshot={snapshots[t.market]} running={session.running} liveStatus={liveStatusByMarket[t.market] ?? (session.running ? 'connecting' : 'idle')} lastTickAt={lastTickAtByMarket[t.market]} viewMode={viewMode} displayMode={resolveCardDisplayMode(t.market)} onDisplayModeChange={mode => handleCardDisplayMode(t.market, mode)} onEnterFocus={() => setFocusMarket(t.market)} selected={t.market === selectedMarket} onSelect={onSelectMarket ? () => onSelectMarket(t.market) : undefined} theme={theme} chartRealtimeSource={chartRealtimeSource} chartSeriesPriceLabels={chartSeriesPriceLabels} chartLiveReceiveHighlight={chartLiveReceiveHighlight} ticker={tickers?.get(t.market)} /> ); })}
)}
); }; export default VirtualTargetGrid;