모의투자 관리 기능 추가
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 우측 매매 패널 — 매수 주문 화면 (공용)
|
||||
*/
|
||||
import React from 'react';
|
||||
import TradeOrderPanel, { type TradeOrderPanelProps } from '../TradeOrderPanel';
|
||||
|
||||
export type TradeBuyOrderPanelProps = Omit<
|
||||
TradeOrderPanelProps,
|
||||
'side' | 'availableCoinQty'
|
||||
>;
|
||||
|
||||
const TradeBuyOrderPanel: React.FC<TradeBuyOrderPanelProps> = ({
|
||||
showSymbolField = true,
|
||||
...props
|
||||
}) => (
|
||||
<div className="ptd-order-card ptd-order-card--trade-inline">
|
||||
<TradeOrderPanel side="buy" showSymbolField={showSymbolField} {...props} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default TradeBuyOrderPanel;
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 우측 호가 탭 — 실시간 호가 카드 (공용)
|
||||
*/
|
||||
import React from 'react';
|
||||
import { OrderbookPanel } from '../OrderbookPanel';
|
||||
import type { OrderbookDisplayUnit, WsStatus } from '../../hooks/useUpbitOrderbook';
|
||||
import type { OrderbookTickerInfo } from '../OrderbookPanel';
|
||||
import type { RecentTrade } from '../../hooks/useUpbitRecentTrades';
|
||||
|
||||
export interface TradeOrderbookPanelContentProps {
|
||||
market: string;
|
||||
asks: OrderbookDisplayUnit[];
|
||||
bids: OrderbookDisplayUnit[];
|
||||
totalAskSize: number;
|
||||
totalBidSize: number;
|
||||
wsStatus: WsStatus;
|
||||
bestAsk: number;
|
||||
bestBid: number;
|
||||
spread: number;
|
||||
spreadPct: number;
|
||||
prevClose: number;
|
||||
tickerInfo?: OrderbookTickerInfo;
|
||||
recentTrades?: RecentTrade[];
|
||||
tradeStrength?: number;
|
||||
onRowClick?: (price: number, rowType: 'ask' | 'bid') => void;
|
||||
}
|
||||
|
||||
const TradeOrderbookPanelContent: React.FC<TradeOrderbookPanelContentProps> = (props) => (
|
||||
<div className="rsp-ob-stack ptd-ob-stack--fill">
|
||||
<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 {...props} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default TradeOrderbookPanelContent;
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 우측 패널 본문 — bps-right-body + ptd-right-body (paperDashboard compact 매매 UI)
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
bodyClassName?: string;
|
||||
anchorRef?: React.Ref<HTMLDivElement>;
|
||||
/** BuilderPageShell이 이미 bps-right-body를 제공할 때 (모의·가상매매) */
|
||||
embeddedInShell?: boolean;
|
||||
}
|
||||
|
||||
const TradeRightPanelBody: React.FC<Props> = ({
|
||||
children,
|
||||
className = '',
|
||||
bodyClassName = '',
|
||||
anchorRef,
|
||||
embeddedInShell = false,
|
||||
}) => {
|
||||
const inner = (
|
||||
<div
|
||||
className={`ptd-right-body${bodyClassName ? ` ${bodyClassName}` : ''}`}
|
||||
ref={anchorRef}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
if (embeddedInShell) {
|
||||
return inner;
|
||||
}
|
||||
return (
|
||||
<div className={`bps-right-body${className ? ` ${className}` : ''}`}>
|
||||
{inner}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TradeRightPanelBody;
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 우측 패널 — 매매 / 호가 탭 (알림목록·모의투자·실시간 차트 공용)
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
export type TradeRightPanelTab = 'trade' | 'orderbook';
|
||||
|
||||
interface Props {
|
||||
activeTab: TradeRightPanelTab;
|
||||
onTabChange: (tab: TradeRightPanelTab) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const TradeRightPanelTabs: React.FC<Props> = ({ activeTab, onTabChange, className = '' }) => (
|
||||
<div className={`bps-right-tabs${className ? ` ${className}` : ''}`}>
|
||||
<button
|
||||
type="button"
|
||||
className={`bps-right-tab${activeTab === 'trade' ? ' bps-right-tab--on' : ''}`}
|
||||
onClick={() => onTabChange('trade')}
|
||||
>
|
||||
매매
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`bps-right-tab${activeTab === 'orderbook' ? ' bps-right-tab--on' : ''}`}
|
||||
onClick={() => onTabChange('orderbook')}
|
||||
>
|
||||
호가
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default TradeRightPanelTabs;
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 우측 매매 패널 — 매도 주문 화면 (공용)
|
||||
*/
|
||||
import React from 'react';
|
||||
import TradeOrderPanel, { type TradeOrderPanelProps } from '../TradeOrderPanel';
|
||||
|
||||
export type TradeSellOrderPanelProps = Omit<
|
||||
TradeOrderPanelProps,
|
||||
'side' | 'availableKrw' | 'availableSymbolBuyKrw'
|
||||
>;
|
||||
|
||||
const TradeSellOrderPanel: React.FC<TradeSellOrderPanelProps> = ({
|
||||
showSymbolField = false,
|
||||
...props
|
||||
}) => (
|
||||
<div className="ptd-order-card ptd-order-card--trade-inline">
|
||||
<TradeOrderPanel side="sell" showSymbolField={showSymbolField} {...props} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default TradeSellOrderPanel;
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 우측 매매 탭 — 상단 매수 / 하단 매도 분할 레이아웃 (알림목록·모의투자·가상매매·차트 공용)
|
||||
*/
|
||||
import React from 'react';
|
||||
import type { TradeOrderFillRequest } from '../../types';
|
||||
import PaperSplitPanel from '../paper/PaperSplitPanel';
|
||||
import TradeBuyOrderPanel from './TradeBuyOrderPanel';
|
||||
import TradeSellOrderPanel from './TradeSellOrderPanel';
|
||||
|
||||
export interface TradeSplitOrderPanelProps {
|
||||
market: string;
|
||||
tradePrice: number | null;
|
||||
availableKrw?: number;
|
||||
/** 종목별 추가 매수 가능 (모의투자 한도) */
|
||||
availableSymbolBuyKrw?: number;
|
||||
availableCoinQty?: number;
|
||||
fillBuy?: TradeOrderFillRequest | null;
|
||||
fillSell?: TradeOrderFillRequest | null;
|
||||
/** 단일 fill (차트 호가 연동 — side에 따라 매수/매도에만 적용) */
|
||||
fillRequest?: TradeOrderFillRequest | null;
|
||||
searchAnchorRef?: React.RefObject<HTMLElement | null>;
|
||||
onMarketSelect?: (market: string) => void;
|
||||
paperTradingEnabled?: boolean;
|
||||
paperAutoTradeEnabled?: boolean;
|
||||
onPaperOrderFilled?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const TradeSplitOrderPanel: React.FC<TradeSplitOrderPanelProps> = ({
|
||||
market,
|
||||
tradePrice,
|
||||
availableKrw = 0,
|
||||
availableSymbolBuyKrw,
|
||||
availableCoinQty = 0,
|
||||
fillBuy,
|
||||
fillSell,
|
||||
fillRequest,
|
||||
searchAnchorRef,
|
||||
onMarketSelect,
|
||||
paperTradingEnabled = false,
|
||||
paperAutoTradeEnabled = false,
|
||||
onPaperOrderFilled,
|
||||
className = '',
|
||||
}) => {
|
||||
const resolvedBuyFill = fillBuy ?? (fillRequest?.side === 'buy' ? fillRequest : null);
|
||||
const resolvedSellFill = fillSell ?? (fillRequest?.side === 'sell' ? fillRequest : null);
|
||||
|
||||
return (
|
||||
<PaperSplitPanel
|
||||
className={`ptd-split-panel--right${className ? ` ${className}` : ''}`}
|
||||
topTitle="매수"
|
||||
bottomTitle="매도"
|
||||
top={(
|
||||
<TradeBuyOrderPanel
|
||||
market={market}
|
||||
tradePrice={tradePrice}
|
||||
availableKrw={availableKrw}
|
||||
availableSymbolBuyKrw={availableSymbolBuyKrw}
|
||||
fillRequest={resolvedBuyFill}
|
||||
searchAnchorRef={searchAnchorRef}
|
||||
onMarketSelect={onMarketSelect}
|
||||
paperTradingEnabled={paperTradingEnabled}
|
||||
paperAutoTradeEnabled={paperAutoTradeEnabled}
|
||||
onPaperOrderFilled={onPaperOrderFilled}
|
||||
/>
|
||||
)}
|
||||
bottom={(
|
||||
<TradeSellOrderPanel
|
||||
market={market}
|
||||
tradePrice={tradePrice}
|
||||
availableCoinQty={availableCoinQty}
|
||||
fillRequest={resolvedSellFill}
|
||||
onMarketSelect={onMarketSelect}
|
||||
paperTradingEnabled={paperTradingEnabled}
|
||||
paperAutoTradeEnabled={paperAutoTradeEnabled}
|
||||
onPaperOrderFilled={onPaperOrderFilled}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TradeSplitOrderPanel;
|
||||
@@ -0,0 +1,11 @@
|
||||
export { default as TradeBuyOrderPanel } from './TradeBuyOrderPanel';
|
||||
export type { TradeBuyOrderPanelProps } from './TradeBuyOrderPanel';
|
||||
export { default as TradeSellOrderPanel } from './TradeSellOrderPanel';
|
||||
export type { TradeSellOrderPanelProps } from './TradeSellOrderPanel';
|
||||
export { default as TradeSplitOrderPanel } from './TradeSplitOrderPanel';
|
||||
export type { TradeSplitOrderPanelProps } from './TradeSplitOrderPanel';
|
||||
export { default as TradeOrderbookPanelContent } from './TradeOrderbookPanelContent';
|
||||
export type { TradeOrderbookPanelContentProps } from './TradeOrderbookPanelContent';
|
||||
export { default as TradeRightPanelTabs } from './TradeRightPanelTabs';
|
||||
export type { TradeRightPanelTab } from './TradeRightPanelTabs';
|
||||
export { default as TradeRightPanelBody } from './TradeRightPanelBody';
|
||||
Reference in New Issue
Block a user