Files
goldenChart/frontend/src/components/virtual/VirtualTargetFocusView.tsx
T

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;