/** * 실시간 차트 우측 패널 * - 매매 탭: 상단 매수 / 하단 매도 * - 호가 탭: 실시간 호가창 */ import React, { useState, useEffect, useRef, useCallback } from 'react'; import { TradeSplitOrderPanel, TradeOrderbookPanelContent, TradeRightPanelTabs, TradeRightPanelBody, type TradeRightPanelTab, } from './trade'; import type { OrderbookDisplayUnit, WsStatus } from '../hooks/useUpbitOrderbook'; import { useUpbitRecentTrades } from '../hooks/useUpbitRecentTrades'; import type { OrderbookTickerInfo } from './OrderbookPanel'; import type { TradeOrderFillRequest } from '../types'; type PanelTab = 'trade' | 'orderbook'; export interface RightSidePanelProps { /** 모바일 등 외부 제어 시 열림 상태 */ open?: boolean; onOpenChange?: (open: boolean) => void; /** 모바일 도크에서 호가/매매 탭 지정 */ activeTab?: TradeRightPanelTab; onTabChange?: (tab: TradeRightPanelTab) => void; market: string; tradePrice: number | null; asks: OrderbookDisplayUnit[]; bids: OrderbookDisplayUnit[]; totalAskSize: number; totalBidSize: number; wsStatus: WsStatus; bestAsk: number; bestBid: number; spread: number; spreadPct: number; prevClose: number; tickerInfo?: OrderbookTickerInfo; fillRequest?: TradeOrderFillRequest | null; onOrderbookPick?: (price: number, rowType: 'ask' | 'bid') => void; onMarketSelect?: (market: string) => void; availableKrw?: number; availableCoinQty?: number; paperTradingEnabled?: boolean; paperAutoTradeEnabled?: boolean; onPaperOrderFilled?: () => void; } const RightSidePanel: React.FC = ({ open: openControlled, onOpenChange, activeTab: activeTabControlled, onTabChange, market, tradePrice, asks, bids, totalAskSize, totalBidSize, wsStatus, bestAsk, bestBid, spread, spreadPct, prevClose, tickerInfo, fillRequest, onOrderbookPick, onMarketSelect, availableKrw = 0, availableCoinQty = 0, paperTradingEnabled = false, paperAutoTradeEnabled = false, onPaperOrderFilled, }) => { const [openInternal, setOpenInternal] = useState(false); const [tabInternal, setTabInternal] = useState('trade'); const tradeSectionRef = useRef(null); const { trades: recentTrades, strength: tradeStrength } = useUpbitRecentTrades(market); const isOpenControlled = openControlled !== undefined && onOpenChange !== undefined; const open = isOpenControlled ? !!openControlled : openInternal; const setOpen = (next: boolean | ((p: boolean) => boolean)) => { const cur = isOpenControlled ? !!openControlled : openInternal; const value = typeof next === 'function' ? next(cur) : next; if (isOpenControlled && onOpenChange) onOpenChange(value); else setOpenInternal(value); }; const activeTab = activeTabControlled ?? tabInternal; const setActiveTab = useCallback((tab: TradeRightPanelTab) => { if (onTabChange) onTabChange(tab); else setTabInternal(tab); }, [onTabChange]); useEffect(() => { if (!fillRequest) return; setOpen(true); setActiveTab('trade'); }, [fillRequest?.seq, fillRequest?.side, setOpen, setActiveTab]); const handleOrderbookRowClick = useCallback((price: number, rowType: 'ask' | 'bid') => { setActiveTab('trade'); onOrderbookPick?.(price, rowType); }, [onOrderbookPick]); return (
{activeTab === 'trade' && ( )} {activeTab === 'orderbook' && ( )}
); }; export default RightSidePanel;