148 lines
5.4 KiB
TypeScript
148 lines
5.4 KiB
TypeScript
import React, { useMemo } from 'react';
|
|
import { getKoreanName } from '../../utils/marketNameCache';
|
|
import type { StrategyDto } from '../../utils/backendApi';
|
|
import type { VirtualIndicatorSnapshot } from '../../hooks/useVirtualIndicatorSnapshots';
|
|
import { useLiveReceiveFlash } from '../../hooks/useLiveReceiveFlash';
|
|
import type { ChartRealtimeSource } from '../../hooks/useChartRealtimeData';
|
|
import type { VirtualLiveStatus } from '../../hooks/useVirtualTargetLiveStatus';
|
|
import type { VirtualCardViewMode } from '../../utils/virtualTradingStorage';
|
|
import type { Theme } from '../../types';
|
|
import type { TickerData } from '../../hooks/useMarketTicker';
|
|
import VirtualLiveBadge from './VirtualLiveBadge';
|
|
import VirtualTargetQuote from './VirtualTargetQuote';
|
|
import VirtualTargetCardChart from './VirtualTargetCardChart';
|
|
import VirtualTargetSignalPanel from './VirtualTargetSignalPanel';
|
|
import VirtualTargetCardFoot from './VirtualTargetCardFoot';
|
|
import { candleTypeToTimeframe } from '../../utils/strategyToChartIndicators';
|
|
import { normalizeStartCandleType } from '../../utils/strategyStartNodes';
|
|
import type { Timeframe } from '../../types';
|
|
|
|
interface Props {
|
|
market: string;
|
|
strategy: StrategyDto | undefined;
|
|
strategies: StrategyDto[];
|
|
strategyId: number | null;
|
|
globalStrategyId: number | null;
|
|
onStrategyChange?: (strategyId: number | null) => void;
|
|
candleType: string;
|
|
onCandleTypeChange?: (candleType: string) => void;
|
|
snapshot: VirtualIndicatorSnapshot | undefined;
|
|
running: boolean;
|
|
liveStatus?: VirtualLiveStatus;
|
|
lastTickAt?: number;
|
|
viewMode?: VirtualCardViewMode;
|
|
onExit: () => void;
|
|
theme?: Theme;
|
|
chartRealtimeSource?: ChartRealtimeSource;
|
|
chartSeriesPriceLabels?: boolean;
|
|
chartLiveReceiveHighlight?: boolean;
|
|
ticker?: TickerData;
|
|
}
|
|
|
|
/** 전체보기 — 좌 차트 · 우 신호 */
|
|
const VirtualTargetFocusView: React.FC<Props> = ({
|
|
market,
|
|
strategy,
|
|
strategies,
|
|
strategyId,
|
|
globalStrategyId,
|
|
onStrategyChange,
|
|
candleType,
|
|
onCandleTypeChange,
|
|
snapshot,
|
|
running,
|
|
liveStatus = 'idle',
|
|
lastTickAt,
|
|
viewMode = 'summary',
|
|
onExit,
|
|
theme = 'dark',
|
|
chartRealtimeSource = 'BACKEND_STOMP',
|
|
chartSeriesPriceLabels = true,
|
|
chartLiveReceiveHighlight = true,
|
|
ticker,
|
|
}) => {
|
|
const ko = getKoreanName(market);
|
|
const sym = market.replace(/^KRW-/, '');
|
|
|
|
const receiveSignal = useMemo(() => {
|
|
if (!running) return null;
|
|
return `${lastTickAt ?? 0}|${snapshot?.updatedAt ?? 0}`;
|
|
}, [running, lastTickAt, snapshot?.updatedAt]);
|
|
const receiving = useLiveReceiveFlash(receiveSignal, running);
|
|
const highlightReceiving = chartLiveReceiveHighlight && receiving;
|
|
const evalCandleType = normalizeStartCandleType(candleType);
|
|
const chartTimeframe = candleTypeToTimeframe(evalCandleType) as Timeframe;
|
|
|
|
return (
|
|
<div className={`vtd-focus-wrap${highlightReceiving ? ' vtd-focus-wrap--receiving' : ''}`}>
|
|
<div className="vtd-focus-head">
|
|
<div className="vtd-focus-head-main">
|
|
<div className="vtd-focus-title">
|
|
<span className="vtd-card-ko">{ko}</span>
|
|
<span className="vtd-card-sym">{sym}</span>
|
|
</div>
|
|
{running && <VirtualLiveBadge status={liveStatus} receiving={receiving} />}
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className="vtd-focus-exit-btn"
|
|
onClick={onExit}
|
|
title="전체 종목 그리드로 돌아가기"
|
|
>
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
|
|
<rect x="1.5" y="1.5" width="5" height="5" rx="1" />
|
|
<rect x="9.5" y="1.5" width="5" height="5" rx="1" />
|
|
<rect x="1.5" y="9.5" width="5" height="5" rx="1" />
|
|
<rect x="9.5" y="9.5" width="5" height="5" rx="1" />
|
|
</svg>
|
|
<span>전체 종목</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="vtd-focus-meta">
|
|
<VirtualTargetQuote market={market} ticker={ticker} compact />
|
|
</div>
|
|
|
|
<div className="vtd-focus-split">
|
|
<section className="vtd-focus-pane vtd-focus-pane--chart" aria-label="차트 보기">
|
|
<div className="vtd-focus-pane-head">차트</div>
|
|
<div className="vtd-focus-pane-body">
|
|
<VirtualTargetCardChart
|
|
market={market}
|
|
strategy={strategy}
|
|
theme={theme}
|
|
running={running}
|
|
chartRealtimeSource={chartRealtimeSource}
|
|
chartSeriesPriceLabels={chartSeriesPriceLabels}
|
|
fillHeight
|
|
chartTimeframe={chartTimeframe}
|
|
/>
|
|
</div>
|
|
</section>
|
|
<section className="vtd-focus-pane vtd-focus-pane--signal" aria-label="신호 보기">
|
|
<div className="vtd-focus-pane-head">신호</div>
|
|
<div className="vtd-focus-pane-body">
|
|
<VirtualTargetSignalPanel
|
|
snapshot={snapshot}
|
|
viewMode={viewMode}
|
|
receiving={highlightReceiving}
|
|
/>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<VirtualTargetCardFoot
|
|
snapshot={snapshot}
|
|
strategies={strategies}
|
|
strategyId={strategyId}
|
|
globalStrategyId={globalStrategyId}
|
|
candleType={evalCandleType}
|
|
onStrategyChange={onStrategyChange}
|
|
onCandleTypeChange={onCandleTypeChange}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default VirtualTargetFocusView;
|