검증게시판 단계 추가

This commit is contained in:
Macbook
2026-05-28 16:24:14 +09:00
parent f64dc1e983
commit 7e3644cb62
375 changed files with 4539 additions and 251294 deletions
+6
View File
@@ -98,6 +98,12 @@ export function invalidateAppSettingsCache() {
_loadPromise = null;
}
/** 서버에서 app-settings 재로드 후 캐시 갱신 (모바일 ↻ 동기화 등) */
export function reloadAppSettingsCache(): Promise<AppSettingsDto> {
invalidateAppSettingsCache();
return ensureLoaded();
}
// ─────────────────────────────────────────────────────────────────────────────
/** DB 또는 fallback 기본값 접근 헬퍼 */
+78 -18
View File
@@ -1,7 +1,7 @@
/**
* 가상매매 공통 상태·동기화 (웹 VirtualTradingPage · 모바일 앱 공유)
*/
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { TickerData } from './useMarketTicker';
import {
loadLiveStrategySettings,
@@ -47,7 +47,13 @@ import {
virtualTargetLimitMessage,
} from '../utils/virtualTargetLimits';
import { resolveVirtualTargetNames } from '../utils/virtualTargetNames';
import { useAppSettings, resolveAppDefaults } from './useAppSettings';
import {
useAppSettings,
resolveAppDefaults,
subscribeAppSettings,
reloadAppSettingsCache,
} from './useAppSettings';
import { hydrateVirtualTargetsIfEmpty } from '../utils/virtualTargetsHydrate';
export interface UseVirtualTradingCoreOptions {
defaultMarket?: string;
@@ -71,7 +77,7 @@ export function useVirtualTradingCore(options: UseVirtualTradingCoreOptions = {}
settingsSessionKey = 0,
} = options;
const { settings: appSettings } = useAppSettings(settingsSessionKey);
const { settings: appSettings, isLoaded: settingsLoaded } = useAppSettings(settingsSessionKey);
const appChartDefaults = resolveAppDefaults(appSettings);
const paperTradingEnabled = paperTradingEnabledProp ?? appChartDefaults.paperTradingEnabled;
const paperAutoTradeEnabled = paperAutoTradeEnabledProp ?? appChartDefaults.paperAutoTradeEnabled;
@@ -86,20 +92,53 @@ export function useVirtualTradingCore(options: UseVirtualTradingCoreOptions = {}
const [loading, setLoading] = useState(true);
const [selectedMarket, setSelectedMarket] = useState(defaultMarket);
const [viewMode, setViewMode] = useState<VirtualCardViewMode>(() => loadVirtualCardViewMode());
/** 서버 ui_preferences 반영 전 빈 목록을 DB에 저장하지 않음 */
const preferencesHydratedRef = useRef(false);
useEffect(() => {
setLoading(true);
const reloadFromUiPreferences = useCallback(() => {
setTargets(loadVirtualTargets());
setSession(loadVirtualSession());
void Promise.all([
loadStrategies().then(setStrategies).catch(() => setStrategies([])),
loadPaperSummary().then(setSummary),
loadPaperTrades().then(setTrades),
]).finally(() => setLoading(false));
}, [settingsSessionKey]);
}, []);
useEffect(() => {
if (!settingsLoaded) return;
let cancelled = false;
preferencesHydratedRef.current = false;
setLoading(true);
void (async () => {
reloadFromUiPreferences();
let nextTargets = loadVirtualTargets();
if (nextTargets.length === 0) {
nextTargets = await hydrateVirtualTargetsIfEmpty();
if (!cancelled && nextTargets.length > 0) {
setTargets(nextTargets);
saveVirtualTargets(nextTargets);
}
}
if (!cancelled) {
preferencesHydratedRef.current = true;
}
await Promise.all([
loadStrategies().then(s => { if (!cancelled) setStrategies(s); }).catch(() => { if (!cancelled) setStrategies([]); }),
loadPaperSummary().then(s => { if (!cancelled) setSummary(s); }),
loadPaperTrades().then(t => { if (!cancelled) setTrades(t); }),
]);
if (!cancelled) setLoading(false);
})();
return () => { cancelled = true; };
}, [settingsSessionKey, settingsLoaded, reloadFromUiPreferences]);
/** DB ui_preferences 반영(웹·모바일 동일 계정) */
useEffect(() => {
if (!settingsLoaded) return;
return subscribeAppSettings(() => {
reloadFromUiPreferences();
});
}, [settingsLoaded, reloadFromUiPreferences]);
/** DB is_pinned → uiPreferences.virtual.targets 동기화 */
useEffect(() => {
if (!settingsLoaded) return;
let cancelled = false;
const initial = loadVirtualTargets();
if (initial.length === 0) return;
@@ -119,10 +158,16 @@ export function useVirtualTradingCore(options: UseVirtualTradingCoreOptions = {}
if (!cancelled) setTargets(merged);
})();
return () => { cancelled = true; };
}, []);
}, [settingsLoaded]);
useEffect(() => { saveVirtualTargets(targets); }, [targets]);
useEffect(() => { saveVirtualSession(session); }, [session]);
useEffect(() => {
if (!preferencesHydratedRef.current) return;
saveVirtualTargets(targets);
}, [targets]);
useEffect(() => {
if (!preferencesHydratedRef.current) return;
saveVirtualSession(session);
}, [session]);
useEffect(() => { saveVirtualCardViewMode(viewMode); }, [viewMode]);
useEffect(() => {
@@ -365,11 +410,26 @@ export function useVirtualTradingCore(options: UseVirtualTradingCoreOptions = {}
void resyncRunningSession(next);
}, [session, resyncRunningSession]);
/** 서버·DB에서 targets 다시 로드 (탭 포커스 등) */
/** 서버·DB에서 targets 다시 로드 (↻ 동기화) */
const reloadTargetsFromStorage = useCallback(() => {
setTargets(loadVirtualTargets());
setSession(loadVirtualSession());
}, []);
setLoading(true);
preferencesHydratedRef.current = false;
void reloadAppSettingsCache()
.then(async () => {
reloadFromUiPreferences();
let next = loadVirtualTargets();
if (next.length === 0) {
next = await hydrateVirtualTargetsIfEmpty();
if (next.length > 0) {
setTargets(next);
saveVirtualTargets(next);
}
}
preferencesHydratedRef.current = true;
})
.catch(() => reloadFromUiPreferences())
.finally(() => setLoading(false));
}, [reloadFromUiPreferences]);
return {
targets,