전략편집기 목록방식 오류 수정

This commit is contained in:
Macbook
2026-06-18 01:57:46 +09:00
parent ac55748aec
commit b96fc64c91
8 changed files with 371 additions and 70 deletions
+87 -22
View File
@@ -355,12 +355,18 @@ export default function StrategyEditorPage({
const sellConditionRef = useRef(sellCondition);
const buyOrphansRef = useRef(buyOrphans);
const sellOrphansRef = useRef(sellOrphans);
const selectedIdRef = useRef(selectedId);
const stratNameRef = useRef(stratName);
const stratDescRef = useRef(stratDesc);
buyLayoutRef.current = buyLayout;
sellLayoutRef.current = sellLayout;
buyConditionRef.current = buyCondition;
sellConditionRef.current = sellCondition;
buyOrphansRef.current = buyOrphans;
sellOrphansRef.current = sellOrphans;
selectedIdRef.current = selectedId;
stratNameRef.current = stratName;
stratDescRef.current = stratDesc;
const layoutRevisionRef = useRef(0);
const strategyAutosaveTimerRef = useRef<number | null>(null);
const layoutPersistReadyRef = useRef(false);
@@ -434,6 +440,8 @@ export default function StrategyEditorPage({
[sellCondition, sellLayout],
);
const currentEditorState = signalTab === 'buy' ? buyEditorState : sellEditorState;
const currentEditorStateRef = useRef(currentEditorState);
currentEditorStateRef.current = currentEditorState;
const selectedLogicNode = useMemo(() => {
if (!selectedNodeId) return null;
@@ -496,6 +504,38 @@ export default function StrategyEditorPage({
: s));
}, [selectedId, stratName, stratDesc, buildCurrentFlowLayout]);
/** 전략 전환·새 전략 전 — ref 스냅샷을 지정 id 로 저장 (selectedId 변경 후 빈 DSL 덮어쓰기 방지) */
const persistStrategySnapshot = useCallback(async (
strategyId: number,
name: string,
description: string,
) => {
const buy = toEditorState(buyConditionRef.current, buyLayoutRef.current);
const sell = toEditorState(sellConditionRef.current, sellLayoutRef.current);
const aligned = alignBuySellStartCandleTypesForSave(buy, sell);
const encodedBuy = encodeConditionForSave(aligned.buy);
const encodedSell = encodeConditionForSave(aligned.sell);
if (!encodedBuy && !encodedSell) return;
const flowLayout = buildCurrentFlowLayout();
const saved = await persistStrategyEditorState(
strategyId,
name,
description,
aligned.buy,
aligned.sell,
flowLayout,
);
setStrategies(prev => prev.map(s => s.id === strategyId
? {
...s,
buyCondition: encodedBuy,
sellCondition: encodedSell,
flowLayout,
updatedAt: saved?.updatedAt ?? s.updatedAt,
}
: s));
}, [buildCurrentFlowLayout]);
const flushStrategyPersist = useCallback(() => {
if (strategyAutosaveTimerRef.current != null) {
window.clearTimeout(strategyAutosaveTimerRef.current);
@@ -732,14 +772,19 @@ export default function StrategyEditorPage({
};
}, [selectedNodeId, selectedLogicNode, stochPairForest, applyStochPairSecondary]);
const handleEditorStateChange = useCallback((next: EditorConditionState) => {
setCurrentRoot(next.root);
const handleEditorStateChange = useCallback((
next: EditorConditionState | ((prev: EditorConditionState) => EditorConditionState),
) => {
const resolved = typeof next === 'function'
? next(currentEditorStateRef.current)
: next;
setCurrentRoot(resolved.root);
setCurrentLayout(prev => ({
...prev,
startMeta: next.startMeta,
extraStartIds: next.extraStartIds,
extraRoots: next.extraRoots,
startCombineOp: normalizeStartCombineOp(next.startCombineOp),
startMeta: resolved.startMeta,
extraStartIds: resolved.extraStartIds,
extraRoots: resolved.extraRoots,
startCombineOp: normalizeStartCombineOp(resolved.startCombineOp),
}));
scheduleStrategyPersist();
}, [setCurrentRoot, setCurrentLayout, scheduleStrategyPersist]);
@@ -784,21 +829,20 @@ export default function StrategyEditorPage({
};
}, [signalTab, layoutSeedKey, buyLayout, sellLayout]);
const persistStrategyToDbRef = useRef(persistStrategyToDb);
persistStrategyToDbRef.current = persistStrategyToDb;
useEffect(() => {
const id = selectedId;
const name = stratName;
return () => {
if (strategyAutosaveTimerRef.current != null) {
window.clearTimeout(strategyAutosaveTimerRef.current);
strategyAutosaveTimerRef.current = null;
}
if (id != null && name.trim()) {
void persistStrategyToDbRef.current().catch(() => { /* unmount flush */ });
}
};
}, [selectedId, stratName]);
const persistStrategySnapshotRef = useRef(persistStrategySnapshot);
persistStrategySnapshotRef.current = persistStrategySnapshot;
useEffect(() => () => {
if (strategyAutosaveTimerRef.current != null) {
window.clearTimeout(strategyAutosaveTimerRef.current);
strategyAutosaveTimerRef.current = null;
}
const id = selectedIdRef.current;
const name = stratNameRef.current;
const desc = stratDescRef.current;
if (id != null && name.trim()) {
void persistStrategySnapshotRef.current(id, name, desc).catch(() => { /* unmount flush */ });
}
}, []);
useEffect(() => {
if (!layoutPersistReadyRef.current) {
@@ -861,7 +905,24 @@ export default function StrategyEditorPage({
}).catch(() => {});
}, []);
const flushBeforeStrategySwitch = useCallback(() => {
if (editorMode === 'graph') layoutFlushRef.current?.();
if (strategyAutosaveTimerRef.current != null) {
window.clearTimeout(strategyAutosaveTimerRef.current);
strategyAutosaveTimerRef.current = null;
}
const prevId = selectedIdRef.current;
const prevName = stratNameRef.current;
const prevDesc = stratDescRef.current;
if (prevId != null && prevName.trim()) {
void persistStrategySnapshot(prevId, prevName, prevDesc).catch(() => { /* switch flush */ });
}
}, [editorMode, persistStrategySnapshot]);
const handleSelectStrategy = (s: StrategyDto) => {
if (selectedId !== s.id) {
flushBeforeStrategySwitch();
}
const buyDecoded = decodeConditionForEditor(s.buyCondition ?? null);
const sellDecoded = decodeConditionForEditor(s.sellCondition ?? null);
let stored = normalizeStrategyFlowLayout(s.flowLayout);
@@ -982,13 +1043,17 @@ export default function StrategyEditorPage({
}, [variant, initialStrategyId, strategies]);
const handleNew = () => {
flushBeforeStrategySwitch();
setSelectedId(null);
setStratName('');
setStratDesc('');
setBuyCondition(null);
setSellCondition(null);
setBuyOrphans([]);
setSellOrphans([]);
setSelectedNodeId(null);
resetFlowLayout('draft', signalTab);
resetFlowLayout('draft', 'buy');
setSignalTab('buy');
};
const openSaveDialog = useCallback((mode: SaveDialogMode) => {