가상투자 화면 추가
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
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 VirtualLiveBadge from './VirtualLiveBadge';
|
||||
import VirtualTargetCardChart from './VirtualTargetCardChart';
|
||||
import VirtualTargetSignalPanel from './VirtualTargetSignalPanel';
|
||||
|
||||
interface Props {
|
||||
market: string;
|
||||
strategy: StrategyDto | undefined;
|
||||
strategies: StrategyDto[];
|
||||
strategyId: number | null;
|
||||
globalStrategyId: number | null;
|
||||
onStrategyChange?: (strategyId: number | null) => void;
|
||||
snapshot: VirtualIndicatorSnapshot | undefined;
|
||||
running: boolean;
|
||||
liveStatus?: VirtualLiveStatus;
|
||||
lastTickAt?: number;
|
||||
viewMode?: VirtualCardViewMode;
|
||||
onExit: () => void;
|
||||
theme?: Theme;
|
||||
chartRealtimeSource?: ChartRealtimeSource;
|
||||
chartSeriesPriceLabels?: boolean;
|
||||
}
|
||||
|
||||
/** 전체보기 — 좌 차트 · 우 신호 */
|
||||
const VirtualTargetFocusView: React.FC<Props> = ({
|
||||
market,
|
||||
strategy,
|
||||
strategies,
|
||||
strategyId,
|
||||
globalStrategyId,
|
||||
onStrategyChange,
|
||||
snapshot,
|
||||
running,
|
||||
liveStatus = 'idle',
|
||||
lastTickAt,
|
||||
viewMode = 'summary',
|
||||
onExit,
|
||||
theme = 'dark',
|
||||
chartRealtimeSource = 'BACKEND_STOMP',
|
||||
chartSeriesPriceLabels = true,
|
||||
}) => {
|
||||
const ko = getKoreanName(market);
|
||||
const sym = market.replace(/^KRW-/, '');
|
||||
const tf = snapshot?.timeframe ?? '—';
|
||||
|
||||
const receiveSignal = useMemo(() => {
|
||||
if (!running) return null;
|
||||
return `${lastTickAt ?? 0}|${snapshot?.updatedAt ?? 0}`;
|
||||
}, [running, lastTickAt, snapshot?.updatedAt]);
|
||||
const receiving = useLiveReceiveFlash(receiveSignal, running);
|
||||
|
||||
return (
|
||||
<div className={`vtd-focus-wrap${receiving ? ' 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>
|
||||
<label className="vtd-card-strategy-field" onClick={e => e.stopPropagation()}>
|
||||
<span className="vtd-card-strategy-label">투자전략</span>
|
||||
<select
|
||||
className="vtd-card-strategy-select"
|
||||
value={strategyId ?? globalStrategyId ?? ''}
|
||||
onChange={e => {
|
||||
const v = e.target.value;
|
||||
onStrategyChange?.(v ? Number(v) : null);
|
||||
}}
|
||||
>
|
||||
<option value="">— 선택 —</option>
|
||||
{strategies.map(s => (
|
||||
<option key={s.id} value={s.id}>{s.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<span className="vtd-card-tf">시간봉 {tf}</span>
|
||||
{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-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
|
||||
/>
|
||||
</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={receiving}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VirtualTargetFocusView;
|
||||
Reference in New Issue
Block a user