가상투자 메뉴 기능 구현
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import VirtualTargetCard from './VirtualTargetCard';
|
||||
import VirtualStrategyChartPopup from './VirtualStrategyChartPopup';
|
||||
import type { StrategyDto } from '../../utils/backendApi';
|
||||
import type { Theme } from '../../types';
|
||||
import type { VirtualTargetItem, VirtualCardViewMode } from '../../utils/virtualTradingStorage';
|
||||
import { loadVirtualCardViewMode, saveVirtualCardViewMode } from '../../utils/virtualTradingStorage';
|
||||
import type { VirtualLiveStatus } from '../../hooks/useVirtualTargetLiveStatus';
|
||||
import type { VirtualIndicatorSnapshot } from '../../hooks/useVirtualIndicatorSnapshots';
|
||||
|
||||
interface Props {
|
||||
targets: VirtualTargetItem[];
|
||||
strategies: StrategyDto[];
|
||||
snapshots: Record<string, VirtualIndicatorSnapshot>;
|
||||
running: boolean;
|
||||
globalStrategyId: number | null;
|
||||
liveStatusByMarket?: Record<string, VirtualLiveStatus>;
|
||||
theme?: Theme;
|
||||
}
|
||||
|
||||
const VirtualTargetGrid: React.FC<Props> = ({
|
||||
targets, strategies, snapshots, running, globalStrategyId, liveStatusByMarket = {}, theme = 'dark',
|
||||
}) => {
|
||||
const [viewMode, setViewMode] = useState<VirtualCardViewMode>(() => loadVirtualCardViewMode());
|
||||
const [chartTarget, setChartTarget] = useState<{ market: string; strategy: StrategyDto | undefined } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
saveVirtualCardViewMode(viewMode);
|
||||
}, [viewMode]);
|
||||
|
||||
if (targets.length === 0) {
|
||||
return (
|
||||
<div className="vtd-grid-empty">
|
||||
<p className="vtd-muted">좌측에서 투자대상 종목을 추가하면 카드가 표시됩니다.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="vtd-grid-wrap">
|
||||
<div className="vtd-grid-head">
|
||||
<span className="vtd-grid-head-title">종목별 매매시그널 일치 현황</span>
|
||||
<div className="vtd-grid-head-right">
|
||||
<div className="vtd-view-toggle" role="group" aria-label="카드 표시 방식">
|
||||
<button
|
||||
type="button"
|
||||
className={`vtd-view-toggle-btn${viewMode === 'summary' ? ' vtd-view-toggle-btn--on' : ''}`}
|
||||
onClick={() => setViewMode('summary')}
|
||||
>
|
||||
요약표시
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`vtd-view-toggle-btn${viewMode === 'detail' ? ' vtd-view-toggle-btn--on' : ''}`}
|
||||
onClick={() => setViewMode('detail')}
|
||||
>
|
||||
상세표시
|
||||
</button>
|
||||
</div>
|
||||
{viewMode === 'detail' && (
|
||||
<div className="vtd-grid-head-legend">
|
||||
<span className="vtd-legend vtd-legend--match">● 충족</span>
|
||||
<span className="vtd-legend vtd-legend--pending">● 대기</span>
|
||||
<span className="vtd-legend vtd-legend--nomatch">● 미충족</span>
|
||||
</div>
|
||||
)}
|
||||
{running && <span className="vtd-grid-live">실시간 갱신 3초</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="vtd-grid">
|
||||
{targets.map(t => {
|
||||
const strat = strategies.find(s => s.id === (t.strategyId ?? globalStrategyId));
|
||||
return (
|
||||
<VirtualTargetCard
|
||||
key={t.market}
|
||||
market={t.market}
|
||||
strategy={strat}
|
||||
snapshot={snapshots[t.market]}
|
||||
running={running}
|
||||
liveStatus={liveStatusByMarket[t.market] ?? (running ? 'connecting' : 'idle')}
|
||||
viewMode={viewMode}
|
||||
onOpenChart={() => setChartTarget({ market: t.market, strategy: strat })}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{chartTarget && (
|
||||
<VirtualStrategyChartPopup
|
||||
market={chartTarget.market}
|
||||
strategy={chartTarget.strategy}
|
||||
theme={theme}
|
||||
running={running}
|
||||
onClose={() => setChartTarget(null)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default VirtualTargetGrid;
|
||||
Reference in New Issue
Block a user