import React, { useState, useMemo } from 'react'; import { useDraggablePanel } from '../hooks/useDraggablePanel'; import { INDICATOR_REGISTRY, type IndicatorDef, type IndicatorCategory } from '../utils/indicatorRegistry'; import type { IndicatorConfig } from '../types'; import { getNumericParamSpec } from '../utils/indicatorParamSpec'; import NumericParamInput from './NumericParamInput'; interface IndicatorModalProps { activeIndicators: IndicatorConfig[]; onAdd: (def: IndicatorDef, params: Record) => void; onRemove: (id: string) => void; /** type 단위 일괄 제거 (여러 인스턴스를 한 번에 제거) */ onRemoveByType?: (type: string) => void; onClose: () => void; } const CATEGORIES: IndicatorCategory[] = [ 'Moving Averages', 'Channels & Bands', 'Oscillators', 'Momentum', 'Trend', 'Volatility', 'Volume', 'Candlestick Patterns', ]; const IndicatorModal: React.FC = ({ activeIndicators, onAdd, onRemove, onRemoveByType, onClose, }) => { const [search, setSearch] = useState(''); const [category, setCategory] = useState('All'); const [selected, setSelected] = useState(null); const [params, setParams] = useState>({}); const filtered = useMemo(() => { return INDICATOR_REGISTRY.filter(d => { const matchCat = category === 'All' || d.category === category; const matchQ = !search || d.name.toLowerCase().includes(search.toLowerCase()) || d.shortName.toLowerCase().includes(search.toLowerCase()); return matchCat && matchQ; }); }, [search, category]); const select = (def: IndicatorDef) => { setSelected(def); setParams({ ...def.defaultParams }); }; const isActive = (type: string) => activeIndicators.some(a => a.type === type); const handleAdd = () => { if (!selected) return; onAdd(selected, params); setSelected(null); }; const handleRemoveAll = (type: string) => { if (onRemoveByType) { onRemoveByType(type); return; } activeIndicators.filter(a => a.type === type).forEach(a => onRemove(a.id)); }; const { panelRef, dragging, onHeaderPointerDown, headerTouchStyle, panelStyle, headerCursor, } = useDraggablePanel({ centerOnMount: true }); return (
{ if (e.target === e.currentTarget) onClose(); }}>
e.stopPropagation()} >
📈 지표 추가 {INDICATOR_REGISTRY.length}개 지표 지원
setSearch(e.target.value)} autoFocus />
{(['All', ...CATEGORIES] as Array<'All' | IndicatorCategory>).map(c => ( ))}
{filtered.map(def => (
select(def)} >
{def.name} {def.description}
{def.overlay && 오버레이} {def.returnsMarkers && 마커} {isActive(def.type) ? ( ) : ( )}
))} {filtered.length === 0 && (
'{search}' 검색 결과 없음
)}
{selected && (
{selected.name} 설정
{selected.description}
{Object.entries(params).map(([key, val]) => (
{typeof val === 'boolean' ? ( setParams(p => ({ ...p, [key]: e.target.checked }))} /> ) : typeof val === 'number' ? ( setParams(p => ({ ...p, [key]: v }))} /> ) : ( setParams(p => ({ ...p, [key]: e.target.value }))} /> )}
))}
{selected.plots.length > 0 && (
{selected.plots.map(p => (
{p.title}
))}
)}
)}
{activeIndicators.length > 0 && (
활성 지표 ({activeIndicators.length})
{activeIndicators.map(a => ( {a.type} ))}
)}
); }; export default IndicatorModal;