goldenChat base source add
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* 실시간 차트 우측 패널
|
||||
* - 매매 탭: 상단 매수 / 하단 매도
|
||||
* - 호가 탭: 실시간 호가창
|
||||
*/
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { OrderbookPanel } from './OrderbookPanel';
|
||||
import TradeOrderPanel from './TradeOrderPanel';
|
||||
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?: PanelTab;
|
||||
onTabChange?: (tab: PanelTab) => 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 PANEL_TABS: { id: PanelTab; label: string }[] = [
|
||||
{ id: 'trade', label: '매매' },
|
||||
{ id: 'orderbook', label: '호가' },
|
||||
];
|
||||
|
||||
const RightSidePanel: React.FC<RightSidePanelProps> = ({
|
||||
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<PanelTab>('trade');
|
||||
const tradeSectionRef = useRef<HTMLDivElement>(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: PanelTab) => {
|
||||
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 (
|
||||
<div className={`rsp-wrap ${open ? 'rsp-wrap--open' : ''}`}>
|
||||
<button
|
||||
type="button"
|
||||
className="rsp-handle"
|
||||
onClick={() => setOpen(v => !v)}
|
||||
title={open ? '패널 닫기' : '패널 열기'}
|
||||
>
|
||||
<svg
|
||||
width="8" height="14"
|
||||
viewBox="0 0 8 14"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.8"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
{open
|
||||
? <polyline points="2,1 6,7 2,13" />
|
||||
: <polyline points="6,1 2,7 6,13" />}
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div className="rsp-panel">
|
||||
<div className="rsp-tabs">
|
||||
{PANEL_TABS.map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
type="button"
|
||||
className={`rsp-tab ${activeTab === tab.id ? 'rsp-tab--active' : ''}`}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="rsp-body">
|
||||
{activeTab === 'trade' && (
|
||||
<div className="rsp-trade-stack" ref={tradeSectionRef}>
|
||||
<div className="rsp-trade-card rsp-trade-card--buy">
|
||||
<div className="rsp-trade-card-title">매수</div>
|
||||
<div className="rsp-trade-card-body">
|
||||
<TradeOrderPanel
|
||||
side="buy"
|
||||
market={market}
|
||||
tradePrice={tradePrice}
|
||||
availableKrw={availableKrw}
|
||||
fillRequest={fillRequest}
|
||||
searchAnchorRef={tradeSectionRef}
|
||||
onMarketSelect={onMarketSelect}
|
||||
showSymbolField
|
||||
paperTradingEnabled={paperTradingEnabled}
|
||||
paperAutoTradeEnabled={paperAutoTradeEnabled}
|
||||
onPaperOrderFilled={onPaperOrderFilled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rsp-trade-card rsp-trade-card--sell">
|
||||
<div className="rsp-trade-card-title">매도</div>
|
||||
<div className="rsp-trade-card-body">
|
||||
<TradeOrderPanel
|
||||
side="sell"
|
||||
market={market}
|
||||
tradePrice={tradePrice}
|
||||
availableKrw={availableKrw}
|
||||
availableCoinQty={availableCoinQty}
|
||||
fillRequest={fillRequest}
|
||||
searchAnchorRef={tradeSectionRef}
|
||||
onMarketSelect={onMarketSelect}
|
||||
showSymbolField={false}
|
||||
paperTradingEnabled={paperTradingEnabled}
|
||||
paperAutoTradeEnabled={paperAutoTradeEnabled}
|
||||
onPaperOrderFilled={onPaperOrderFilled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'orderbook' && (
|
||||
<div className="rsp-ob-stack">
|
||||
<div className="rsp-trade-card rsp-ob-card">
|
||||
<div className="rsp-trade-card-title rsp-ob-card-title">실시간 호가</div>
|
||||
<div className="rsp-trade-card-body rsp-ob-card-body">
|
||||
<OrderbookPanel
|
||||
market={market}
|
||||
asks={asks}
|
||||
bids={bids}
|
||||
totalAskSize={totalAskSize}
|
||||
totalBidSize={totalBidSize}
|
||||
wsStatus={wsStatus}
|
||||
bestAsk={bestAsk}
|
||||
bestBid={bestBid}
|
||||
spread={spread}
|
||||
spreadPct={spreadPct}
|
||||
prevClose={prevClose}
|
||||
tickerInfo={tickerInfo}
|
||||
recentTrades={recentTrades}
|
||||
tradeStrength={tradeStrength}
|
||||
onRowClick={handleOrderbookRowClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RightSidePanel;
|
||||
Reference in New Issue
Block a user