/** * 위젯 대시보드 — 종목·매매 상태 공유 */ import React, { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react'; import type { PaperSummaryDto, PaperTradeDto, StrategyDto } from '../utils/backendApi'; import type { TickerData, MarketInfo } from '../hooks/useMarketTicker'; import type { TradeOrderFillRequest } from '../types'; import type { Theme } from '../types'; import type { ChartRealtimeSource } from '../hooks/useChartRealtimeData'; export interface WidgetDashboardContextValue { theme: Theme; selectedMarket: string; setSelectedMarket: (m: string) => void; tickers: Map; marketInfos: MarketInfo[]; marketLoading: boolean; usdRate: number; strategies: StrategyDto[]; selectedStrategyId: number | null; setSelectedStrategyId: (id: number | null) => void; summary: PaperSummaryDto | null; trades: PaperTradeDto[]; refreshPaperData: () => void; tradePrice: number | null; fillBuy: TradeOrderFillRequest | null; fillSell: TradeOrderFillRequest | null; setFillBuy: (r: TradeOrderFillRequest | null) => void; setFillSell: (r: TradeOrderFillRequest | null) => void; paperTradingEnabled: boolean; paperAutoTradeEnabled: boolean; onPaperOrderFilled?: () => void; chartRealtimeSource: ChartRealtimeSource; } const WidgetDashboardContext = createContext(null); export function useWidgetDashboard(): WidgetDashboardContextValue { const ctx = useContext(WidgetDashboardContext); if (!ctx) throw new Error('useWidgetDashboard must be used within WidgetDashboardProvider'); return ctx; } export function useWidgetDashboardOptional(): WidgetDashboardContextValue | null { return useContext(WidgetDashboardContext); } interface ProviderProps { theme: Theme; tickers: Map; marketInfos: MarketInfo[]; marketLoading: boolean; usdRate: number; defaultMarket: string; strategies: StrategyDto[]; summary: PaperSummaryDto | null; trades: PaperTradeDto[]; refreshPaperData: () => void; paperTradingEnabled: boolean; paperAutoTradeEnabled: boolean; onPaperOrderFilled?: () => void; chartRealtimeSource: ChartRealtimeSource; children: React.ReactNode; } export const WidgetDashboardProvider: React.FC = ({ theme, tickers, marketInfos, marketLoading, usdRate, defaultMarket, strategies, summary, trades, refreshPaperData, paperTradingEnabled, paperAutoTradeEnabled, onPaperOrderFilled, chartRealtimeSource, children, }) => { const [selectedMarket, setSelectedMarket] = useState(defaultMarket); const [selectedStrategyId, setSelectedStrategyId] = useState(null); useEffect(() => { if (defaultMarket) setSelectedMarket(defaultMarket); }, [defaultMarket]); const [fillBuy, setFillBuy] = useState(null); const [fillSell, setFillSell] = useState(null); const tradePrice = useMemo(() => { const t = tickers.get(selectedMarket); return t?.tradePrice ?? null; }, [tickers, selectedMarket]); const value = useMemo(() => ({ theme, selectedMarket, setSelectedMarket, tickers, marketInfos, marketLoading, usdRate, strategies, selectedStrategyId, setSelectedStrategyId, summary, trades, refreshPaperData, tradePrice, fillBuy, fillSell, setFillBuy, setFillSell, paperTradingEnabled, paperAutoTradeEnabled, onPaperOrderFilled, chartRealtimeSource, }), [ theme, selectedMarket, tickers, marketInfos, marketLoading, usdRate, strategies, selectedStrategyId, summary, trades, refreshPaperData, tradePrice, fillBuy, fillSell, paperTradingEnabled, paperAutoTradeEnabled, onPaperOrderFilled, chartRealtimeSource, ]); return ( {children} ); }; export function useWidgetOrderbookFillHandler(): (price: number, rowType: 'ask' | 'bid') => void { const ctx = useWidgetDashboard(); return useCallback((price: number, rowType: 'ask' | 'bid') => { const side = rowType === 'bid' ? 'buy' : 'sell'; const req: TradeOrderFillRequest = { market: ctx.selectedMarket, price, side, seq: Date.now() }; if (side === 'buy') ctx.setFillBuy(req); else ctx.setFillSell(req); }, [ctx]); }