매매 시그널 알림 우측패널 동작 수정

This commit is contained in:
Macbook
2026-05-29 01:38:58 +09:00
parent 094a285363
commit 3db7e85e2c
7 changed files with 241 additions and 88 deletions
@@ -13,8 +13,10 @@ import PaperSplitPanel from './paper/PaperSplitPanel';
import { useUpbitOrderbook } from '../hooks/useUpbitOrderbook';
import { useUpbitRecentTrades } from '../hooks/useUpbitRecentTrades';
import { useAppSettings } from '../hooks/useAppSettings';
import { readStoredBool, storeBool } from './strategyEditor/usePanelResize';
import '../styles/strategyEditorTheme.css';
import '../styles/virtualTradingDashboard.css';
import '../styles/builderPageShell.css';
import '../styles/tradeNotificationList.css';
import TradeNotificationListRow from './tradeNotification/TradeNotificationListRow';
import TradeNotificationGridLayoutPicker from './tradeNotification/TradeNotificationGridLayoutPicker';
@@ -32,6 +34,8 @@ import {
type RightTab = 'trade' | 'orderbook';
const TNL_RIGHT_OPEN_KEY = 'tnl-right-open';
const IcTrash = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<polyline points="3 6 5 6 21 6" />
@@ -106,6 +110,7 @@ export const TradeNotificationListPage: React.FC<Props> = ({
const [summary, setSummary] = useState<PaperSummaryDto | null>(null);
const orderAnchorRef = useRef<HTMLDivElement>(null);
const [refreshing, setRefreshing] = useState(false);
const [rightOpen, setRightOpen] = useState(() => readStoredBool(TNL_RIGHT_OPEN_KEY, true));
const { settings: appSettings } = useAppSettings();
const chartLiveReceiveHighlight = appSettings.chartLiveReceiveHighlight ?? true;
@@ -244,11 +249,30 @@ export const TradeNotificationListPage: React.FC<Props> = ({
saveTradeNotificationGridPreset(preset);
}, []);
const toggleRightPanel = useCallback(() => {
setRightOpen(prev => {
const next = !prev;
storeBool(TNL_RIGHT_OPEN_KEY, next);
return next;
});
}, []);
const isListView = listLayout === 'list';
const gridCols = getTradeNotificationGridPreset(gridPreset).cols;
return (
<div className={`tnl-page tnl-page--with-right bps-page--vtd se-page se-page--${theme} app ${theme}`}>
<div
className={[
'tnl-page',
'tnl-page--with-right',
'bps-page--vtd',
'se-page',
`se-page--${theme}`,
'app',
theme,
rightOpen ? '' : 'tnl-page--right-collapsed',
].filter(Boolean).join(' ')}
>
<div className="tnl-body">
<div className="tnl-main" style={{ containerType: 'inline-size', containerName: 'tnl-main' }}>
<div className="tnl-header">
@@ -375,91 +399,123 @@ export const TradeNotificationListPage: React.FC<Props> = ({
</div>
</div>
<aside className="tnl-right" aria-label="매매·호가">
<div className="bps-right-tabs tnl-right-tabs">
<button
type="button"
className={`bps-right-tab${rightTab === 'trade' ? ' bps-right-tab--on' : ''}`}
onClick={() => setRightTab('trade')}
<div className={`tnl-side-wrap tnl-side-wrap--right${rightOpen ? ' tnl-side-wrap--open' : ''}`}>
<button
type="button"
className={`bps-panel-handle bps-panel-handle--right${rightOpen ? ' bps-panel-handle--open' : ''}`}
onClick={toggleRightPanel}
title={rightOpen ? '매매·호가 패널 닫기' : '매매·호가 패널 열기'}
aria-expanded={rightOpen}
aria-label={rightOpen ? '매매·호가 패널 닫기' : '매매·호가 패널 열기'}
>
<svg
width="8"
height="14"
viewBox="0 0 8 14"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden
>
</button>
<button
type="button"
className={`bps-right-tab${rightTab === 'orderbook' ? ' bps-right-tab--on' : ''}`}
onClick={() => setRightTab('orderbook')}
>
</button>
</div>
<div className="tnl-right-body ptd-right-body" ref={orderAnchorRef}>
{rightTab === 'trade' ? (
<PaperSplitPanel
className="ptd-split-panel--right"
topTitle="매수"
bottomTitle="매도"
top={(
<div className="ptd-order-card">
<TradeOrderPanel
side="buy"
market={selectedMarket}
tradePrice={tradePrice}
availableKrw={coerceFiniteNumber(summary?.cashBalance) ?? 0}
fillRequest={fillBuy}
searchAnchorRef={orderAnchorRef}
onMarketSelect={m => handleSelectMarket(m)}
showSymbolField
paperTradingEnabled={paperTradingEnabled}
paperAutoTradeEnabled={paperAutoTradeEnabled}
onPaperOrderFilled={() => void refreshPaperData()}
/>
</div>
)}
bottom={(
<div className="ptd-order-card">
<TradeOrderPanel
side="sell"
market={selectedMarket}
tradePrice={tradePrice}
availableCoinQty={posQty}
fillRequest={fillSell}
onMarketSelect={m => handleSelectMarket(m)}
showSymbolField={false}
paperTradingEnabled={paperTradingEnabled}
paperAutoTradeEnabled={paperAutoTradeEnabled}
onPaperOrderFilled={() => void refreshPaperData()}
/>
</div>
)}
/>
) : (
<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
market={selectedMarket}
asks={orderbook.asks}
bids={orderbook.bids}
totalAskSize={orderbook.totalAskSize}
totalBidSize={orderbook.totalBidSize}
wsStatus={obWsStatus}
bestAsk={spread.bestAsk}
bestBid={spread.bestBid}
spread={spread.spread}
spreadPct={spread.pct}
prevClose={orderbookPrevClose}
tickerInfo={orderbookTickerInfo}
recentTrades={recentTrades}
tradeStrength={tradeStrength}
onRowClick={handleOrderbookRowClick}
/>
{rightOpen ? (
<polyline points="2,1 6,7 2,13" />
) : (
<polyline points="6,1 2,7 6,13" />
)}
</svg>
</button>
<aside
className={`tnl-right${rightOpen ? ' tnl-right--open' : ''}`}
aria-label="매매·호가"
aria-hidden={!rightOpen}
>
<div className="bps-right-tabs tnl-right-tabs">
<button
type="button"
className={`bps-right-tab${rightTab === 'trade' ? ' bps-right-tab--on' : ''}`}
onClick={() => setRightTab('trade')}
>
</button>
<button
type="button"
className={`bps-right-tab${rightTab === 'orderbook' ? ' bps-right-tab--on' : ''}`}
onClick={() => setRightTab('orderbook')}
>
</button>
</div>
<div className="tnl-right-body ptd-right-body" ref={orderAnchorRef}>
{rightTab === 'trade' ? (
<PaperSplitPanel
className="ptd-split-panel--right"
topTitle="매수"
bottomTitle="매도"
top={(
<div className="ptd-order-card">
<TradeOrderPanel
side="buy"
market={selectedMarket}
tradePrice={tradePrice}
availableKrw={coerceFiniteNumber(summary?.cashBalance) ?? 0}
fillRequest={fillBuy}
searchAnchorRef={orderAnchorRef}
onMarketSelect={m => handleSelectMarket(m)}
showSymbolField
paperTradingEnabled={paperTradingEnabled}
paperAutoTradeEnabled={paperAutoTradeEnabled}
onPaperOrderFilled={() => void refreshPaperData()}
/>
</div>
)}
bottom={(
<div className="ptd-order-card">
<TradeOrderPanel
side="sell"
market={selectedMarket}
tradePrice={tradePrice}
availableCoinQty={posQty}
fillRequest={fillSell}
onMarketSelect={m => handleSelectMarket(m)}
showSymbolField={false}
paperTradingEnabled={paperTradingEnabled}
paperAutoTradeEnabled={paperAutoTradeEnabled}
onPaperOrderFilled={() => void refreshPaperData()}
/>
</div>
)}
/>
) : (
<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
market={selectedMarket}
asks={orderbook.asks}
bids={orderbook.bids}
totalAskSize={orderbook.totalAskSize}
totalBidSize={orderbook.totalBidSize}
wsStatus={obWsStatus}
bestAsk={spread.bestAsk}
bestBid={spread.bestBid}
spread={spread.spread}
spreadPct={spread.pct}
prevClose={orderbookPrevClose}
tickerInfo={orderbookTickerInfo}
recentTrades={recentTrades}
tradeStrength={tradeStrength}
onRowClick={handleOrderbookRowClick}
/>
</div>
</div>
</div>
</div>
)}
</div>
</aside>
)}
</div>
</aside>
</div>
</div>
</div>
);