목록 로딩 로직 개선

This commit is contained in:
Macbook
2026-06-07 16:57:30 +09:00
parent 59c548cb41
commit 7ec754770d
25 changed files with 1081 additions and 431 deletions
@@ -3,11 +3,30 @@
*/
import { useCallback, useEffect, useRef, useState } from 'react';
import { isStrategyMissingOnServer, loadStrategy, type StrategyDto } from '../utils/backendApi';
import { extractVirtualConditions } from '../utils/virtualStrategyConditions';
import { normalizeConditionRow } from '../utils/virtualSignalMetrics';
import {
fetchLiveSnapshot,
type VirtualIndicatorSnapshot,
} from './useVirtualIndicatorSnapshots';
function staticFallback(
market: string,
strategyId: number,
strategy: StrategyDto,
): VirtualIndicatorSnapshot | undefined {
const baseRows = extractVirtualConditions(strategy);
if (baseRows.length === 0) return undefined;
return {
market,
strategyId,
timeframe: [...new Set(baseRows.map(r => r.timeframe))].join(', '),
rows: baseRows.map(r => normalizeConditionRow({ ...r, currentValue: null, satisfied: null })),
updatedAt: Date.now(),
matchRate: 0,
};
}
export function useTradeNotificationSignalSnapshot(
market: string,
strategyId: number | null | undefined,
@@ -19,6 +38,7 @@ export function useTradeNotificationSignalSnapshot(
const [loading, setLoading] = useState(false);
const emptyRetryRef = useRef(0);
const genRef = useRef(0);
const refreshInFlightRef = useRef(false);
/** strategy prop 이 undefined 일 때 직접 로드한 전략 캐시 */
const fallbackStrategyRef = useRef<StrategyDto | null>(null);
@@ -28,40 +48,52 @@ export function useTradeNotificationSignalSnapshot(
setLoading(false);
return;
}
if (refreshInFlightRef.current) return;
const gen = ++genRef.current;
refreshInFlightRef.current = true;
setLoading(true);
// strategy prop 이 undefined 면 내부에서 직접 로드해 baseRows 를 확보한다.
// 이렇게 하면 strategy 가 늦게 주입되어도 조건 목록이 즉시 표시된다.
let effectiveStrategy = strategy;
if (!effectiveStrategy) {
if (fallbackStrategyRef.current) {
effectiveStrategy = fallbackStrategyRef.current;
} else if (!isStrategyMissingOnServer(strategyId)) {
try {
const loaded = await loadStrategy(strategyId);
if (loaded) {
fallbackStrategyRef.current = loaded;
effectiveStrategy = loaded;
}
} catch { /* ignore */ }
if (gen !== genRef.current) return;
try {
let effectiveStrategy = strategy;
if (!effectiveStrategy) {
if (fallbackStrategyRef.current) {
effectiveStrategy = fallbackStrategyRef.current;
} else if (!isStrategyMissingOnServer(strategyId)) {
try {
const loaded = await loadStrategy(strategyId);
if (loaded) {
fallbackStrategyRef.current = loaded;
effectiveStrategy = loaded;
}
} catch { /* ignore */ }
if (gen !== genRef.current) return;
}
}
const pinFirst = emptyRetryRef.current === 0;
let snap = await fetchLiveSnapshot(market, strategyId, effectiveStrategy, pinFirst);
if (gen !== genRef.current) return;
if (!snap && effectiveStrategy) {
snap = staticFallback(market, strategyId, effectiveStrategy) ?? null;
}
if (snap && snap.rows.length > 0) {
emptyRetryRef.current = 0;
} else {
emptyRetryRef.current += 1;
}
if (gen === genRef.current) {
setSnapshot(snap ?? undefined);
}
} finally {
refreshInFlightRef.current = false;
if (gen === genRef.current) {
setLoading(false);
}
}
const pinFirst = emptyRetryRef.current === 0;
const snap = await fetchLiveSnapshot(market, strategyId, effectiveStrategy, pinFirst);
if (gen !== genRef.current) return;
if (snap && snap.rows.length > 0) {
emptyRetryRef.current = 0;
} else {
emptyRetryRef.current += 1;
}
setSnapshot(snap ?? undefined);
setLoading(false);
}, [market, strategyId, strategy, enabled]);
useEffect(() => {
@@ -72,13 +104,14 @@ export function useTradeNotificationSignalSnapshot(
return;
}
emptyRetryRef.current = 0;
fallbackStrategyRef.current = null; // strategy prop 변경 시 캐시 초기화
setLoading(true); // 활성화 즉시 로딩 표시
fallbackStrategyRef.current = null;
setLoading(true);
void refresh();
const id = window.setInterval(() => void refresh(), pollMs);
return () => {
clearInterval(id);
genRef.current += 1;
refreshInFlightRef.current = false;
};
}, [market, strategyId, enabled, pollMs, refresh]);