import React, { useState, useCallback, useRef, useEffect } from 'react'; import { type ChartCustomOverlaySelection, type CustomOverlaySectionId, cloneChartCustomOverlaySelection, smaMaLabel, BB_CUSTOM_LABELS, ICHIMOKU_CUSTOM_ROWS, isCustomSectionAllSelected, setCustomSectionAllSelected, } from '../utils/chartCustomOverlay'; import { SMA_MA_COUNT, smaPlotId } from '../utils/smaConfig'; import { useDraggablePanel } from '../hooks/useDraggablePanel'; interface ChartCustomOverlaySettingsModalProps { title?: string; selection: ChartCustomOverlaySelection; onSave: (selection: ChartCustomOverlaySelection) => void; onCancel: () => void; } const SECTION_META: Array<{ id: CustomOverlaySectionId; ko: string; en: string; cols: 2 | 3; }> = [ { id: 'sma', ko: '이동평균선', en: 'Simple Moving Average (SMA)', cols: 3 }, { id: 'bollinger', ko: '볼린저 밴드', en: 'Bollinger Bands', cols: 2 }, { id: 'ichimoku', ko: '일목균형표', en: 'Ichimoku Cloud', cols: 3 }, { id: 'candle', ko: '캔들차트', en: 'Candlestick', cols: 2 }, ]; const ALL_SECTIONS = new Set(SECTION_META.map(s => s.id)); const GridToggle: React.FC<{ label: string; checked: boolean; onChange: (v: boolean) => void; }> = ({ label, checked, onChange }) => (
{label}
); const ChartCustomOverlaySettingsModal: React.FC = ({ title = 'Custom 표시 설정', selection, onSave, onCancel, }) => { const draftRef = useRef(cloneChartCustomOverlaySelection(selection)); const [, bump] = useState(0); const refresh = useCallback(() => bump(n => n + 1), []); const [expanded, setExpanded] = useState>( () => new Set(ALL_SECTIONS), ); const { panelRef, dragging, onHeaderPointerDown, headerTouchStyle, panelStyle, headerCursor, } = useDraggablePanel({ centerOnMount: true }); useEffect(() => { draftRef.current = cloneChartCustomOverlaySelection(selection); setExpanded(new Set(ALL_SECTIONS)); refresh(); }, [selection, refresh]); const draft = draftRef.current; const patch = useCallback((patchFn: (d: ChartCustomOverlaySelection) => void) => { patchFn(draftRef.current); refresh(); }, [refresh]); const toggleSectionExpand = useCallback((id: CustomOverlaySectionId) => { setExpanded(prev => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }, []); const toggleSectionAll = useCallback((id: CustomOverlaySectionId) => { patch(d => { const allOn = isCustomSectionAllSelected(d, id); setCustomSectionAllSelected(d, id, !allOn); }); }, [patch]); const renderSectionItems = (sectionId: CustomOverlaySectionId) => { switch (sectionId) { case 'sma': return Array.from({ length: SMA_MA_COUNT }, (_, i) => { const pid = smaPlotId(i); return ( patch(d => { d.smaPlots[pid] = v; })} /> ); }); case 'bollinger': return ( <> patch(d => { d.bollinger.basis = v; })} /> patch(d => { d.bollinger.upper = v; })} /> patch(d => { d.bollinger.lower = v; })} /> patch(d => { d.bollinger.background = v; })} /> ); case 'ichimoku': return ICHIMOKU_CUSTOM_ROWS.map(row => ( patch(d => { d.ichimoku[row.key] = v; })} /> )); case 'candle': return ( patch(d => { d.candle = v; })} /> ); default: return null; } }; return (
{ if (e.target === e.currentTarget) onCancel(); }} >
e.stopPropagation()} style={{ ...panelStyle, zIndex: 5002, cursor: dragging ? 'grabbing' : undefined, }} >
{title}

Custom 적용 시 아래에서 켠 항목만 차트에 표시됩니다. 각 지표 설정의 on/off와는 별개이며, 지표 우측 스위치는 해당 지표의 모든 항목을 한 번에 선택·해제합니다.

{SECTION_META.map(meta => { const isExpanded = expanded.has(meta.id); const allOn = isCustomSectionAllSelected(draft, meta.id); return (
{meta.ko} {meta.en}
{allOn ? '전체 선택' : '일부 선택'}
{isExpanded && (
{renderSectionItems(meta.id)}
)}
); })}
); }; export default ChartCustomOverlaySettingsModal;