전략편집기 팝업 기능 적용

This commit is contained in:
Macbook
2026-06-16 10:48:13 +09:00
parent 484f477d6c
commit d5e864b230
7 changed files with 644 additions and 84 deletions
+42 -2
View File
@@ -191,6 +191,11 @@ function readRightPanelWidth(): number {
interface Props {
theme: Theme;
onNavigateToBacktest?: () => void;
/** 팝업(임베드) — 전략평가 등 */
variant?: 'page' | 'modal';
onClose?: () => void;
initialStrategyId?: number | null;
onStrategiesChanged?: () => void;
}
function toEditorState(
@@ -262,13 +267,25 @@ function isDuplicateStrategyName(
return strategies.some(s => s.name.trim() === trimmed && s.id !== excludeId);
}
export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Props) {
export default function StrategyEditorPage({
theme,
onNavigateToBacktest,
variant = 'page',
onClose,
initialStrategyId = null,
onStrategiesChanged,
}: Props) {
const { getParams, getVisualConfig, settingsRevision, settingsLoaded } = useIndicatorSettings();
const DEF = useMemo(
() => buildStrategyEditorDefFromSettings(getParams, getVisualConfig),
[getParams, getVisualConfig, settingsRevision],
);
const settingsSyncRef = useRef(-1);
const initialStrategyAppliedRef = useRef(false);
const notifyStrategiesChanged = useCallback(() => {
onStrategiesChanged?.();
}, [onStrategiesChanged]);
const [strategies, setStrategies] = useState<StrategyDto[]>(() => loadStratsLocal());
const [selectedId, setSelectedId] = useState<number | null>(null);
@@ -953,6 +970,16 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
})();
};
useEffect(() => {
if (variant !== 'modal' || initialStrategyId == null || initialStrategyAppliedRef.current) return;
if (strategies.length === 0) return;
const found = strategies.find(s => s.id === initialStrategyId);
if (!found) return;
initialStrategyAppliedRef.current = true;
handleSelectStrategy(found);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [variant, initialStrategyId, strategies]);
const handleNew = () => {
setSelectedId(null);
setStratName('');
@@ -1045,6 +1072,7 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
} else {
showSnack('전략이 저장되었습니다');
}
notifyStrategiesChanged();
} catch (e) {
showSnack(e instanceof Error ? e.message : '저장 실패', false);
} finally {
@@ -1061,6 +1089,7 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
setDeleteOpen(false);
setDeleteId(null);
showSnack('전략이 삭제되었습니다');
notifyStrategiesChanged();
};
const applyImportedFlowLayout = useCallback((layout: StrategyFlowLayoutStore | undefined, tab: 'buy' | 'sell' = signalTab) => {
@@ -1617,7 +1646,7 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
selectedPaletteKey === paletteKey(type, id);
return (
<div className={`se-page se-page--${theme}`}>
<div className={`se-page se-page--${theme}${variant === 'modal' ? ' se-page--modal' : ''}`}>
{needsPointerPaletteDrag() ? <PaletteDragOverlay /> : null}
{saveToast && (
<div className="se-save-toast"> DB에 .</div>
@@ -1711,6 +1740,17 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
</div>
<div className="se-header-actions">
{variant === 'modal' && onClose && (
<button
type="button"
className="se-btn se-btn--ghost se-btn--icon se-btn--modal-close"
title="닫기"
aria-label="전략 편집 닫기"
onClick={onClose}
>
×
</button>
)}
<button
type="button"
className="se-btn se-btn--ghost se-btn--icon se-btn--desc"