추세검색 설정 추가

This commit is contained in:
Macbook
2026-05-27 01:36:06 +09:00
parent c1bcf88c6c
commit 2e08c6b16f
42 changed files with 1507 additions and 226 deletions
+45 -1
View File
@@ -50,6 +50,8 @@ import {
syncVirtualTargetsToBackend,
stopVirtualLiveOnBackend,
} from '../utils/virtualLiveStrategySync';
import { persistVirtualTargetPinned } from '../utils/virtualTargetMutations';
import { virtualTargetLimitMessage } from '../utils/virtualTargetLimits';
import '../styles/virtualTradingDashboard.css';
type RightTab = 'trade' | 'orderbook' | 'history';
@@ -95,6 +97,7 @@ const VirtualTradingPage: React.FC<Props> = ({
?? 'BACKEND_STOMP') as ChartRealtimeSource;
const chartSeriesPriceLabels = appChartDefaults.chartSeriesPriceLabels;
const chartLiveReceiveHighlight = appChartDefaults.chartLiveReceiveHighlight;
const virtualTargetMaxCount = appChartDefaults.virtualTargetMaxCount;
useEffect(() => {
void Promise.all([
@@ -104,6 +107,29 @@ const VirtualTradingPage: React.FC<Props> = ({
]).finally(() => setLoading(false));
}, []);
/** DB is_pinned → localStorage 투자대상 목록 동기화 (최초 1회) */
useEffect(() => {
let cancelled = false;
const initial = loadVirtualTargets();
if (initial.length === 0) return;
void (async () => {
const merged = await Promise.all(
initial.map(async t => {
try {
const s = await loadLiveStrategySettings(t.market);
const pinned = !!s.isPinned;
if (!!t.pinned === pinned) return t;
return { ...t, pinned };
} catch {
return t;
}
}),
);
if (!cancelled) setTargets(merged);
})();
return () => { cancelled = true; };
}, []);
useEffect(() => { saveVirtualTargets(targets); }, [targets]);
useEffect(() => { saveVirtualSession(session); }, [session]);
useEffect(() => { saveVirtualCardViewMode(viewMode); }, [viewMode]);
@@ -185,11 +211,27 @@ const VirtualTradingPage: React.FC<Props> = ({
}, [tickers]);
const handleTargetsChange = useCallback((items: VirtualTargetItem[]) => {
if (items.length > virtualTargetMaxCount) {
window.alert(virtualTargetLimitMessage(virtualTargetMaxCount));
return;
}
setTargets(items);
if (items.length > 0 && !items.some(t => t.market === selectedMarket)) {
handleSelectMarket(items[0].market);
}
}, [selectedMarket, handleSelectMarket]);
}, [selectedMarket, handleSelectMarket, virtualTargetMaxCount]);
const handleTogglePin = useCallback((market: string) => {
setTargets(prev => {
const item = prev.find(t => t.market === market);
if (!item) return prev;
const pinned = !item.pinned;
void persistVirtualTargetPinned(market, pinned).catch(() => {
window.alert('고정 설정 저장에 실패했습니다.');
});
return prev.map(t => (t.market === market ? { ...t, pinned } : t));
});
}, []);
const handleGlobalStrategyChange = useCallback((strategyId: number | null) => {
const next = { ...session, globalStrategyId: strategyId };
@@ -386,6 +428,8 @@ const VirtualTradingPage: React.FC<Props> = ({
onTargetsChange={handleTargetsChange}
onSelectMarket={handleSelectMarket}
onTargetStrategyChange={(market, strategyId) => void handleTargetStrategyChange(market, strategyId)}
onTogglePin={handleTogglePin}
virtualTargetMaxCount={virtualTargetMaxCount}
/>
)}
center={(