import React, { useState } from 'react'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Box, Typography, Switch, FormControlLabel, Divider, useTheme, useMediaQuery, Alert, Paper, TextField, Slider, IconButton, } from '@mui/material'; import { DragIndicator, Close } from '@mui/icons-material'; import { useChartColors, ChartColorSettings } from '../contexts/ChartColorContext'; import { useIndicatorSettings, IndicatorSettings } from '../contexts/IndicatorSettingsContext'; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, TouchSensor, useSensor, useSensors, DragEndEvent, DragStartEvent, DragOverlay, } from '@dnd-kit/core'; import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { useIndicatorVisibility, IndicatorVisibilitySettings, IndicatorOrder } from '../contexts/IndicatorVisibilityContext'; import { useAuth } from '../contexts/AuthContext'; import DraggablePaper from './DraggablePaper'; interface IndicatorSettingsDialogProps { open: boolean; onClose: () => void; } // 지표별 색상/굵기 설정 키 매핑 (여러 색상 지원) interface ColorConfig { label: string; colorKey: keyof ChartColorSettings; widthKey?: keyof ChartColorSettings; } const indicatorColorConfigs: Record = { macdLine: [ { label: 'MACD', colorKey: 'macdLine', widthKey: 'macdLineWidth' }, { label: '시그널', colorKey: 'signalLine', widthKey: 'signalLineWidth' }, ], rsi: [ { label: '', colorKey: 'rsiColor', widthKey: 'rsiWidth' }, ], stochasticK: [ { label: '%K', colorKey: 'stochasticK', widthKey: 'stochasticKWidth' }, { label: '%D', colorKey: 'stochasticD', widthKey: 'stochasticDWidth' }, ], cci: [ { label: '', colorKey: 'cciColor', widthKey: 'cciWidth' }, ], adx: [ { label: '', colorKey: 'adxColor', widthKey: 'adxWidth' }, ], plusDi: [ { label: '+DI', colorKey: 'plusDiColor', widthKey: 'plusDiWidth' }, { label: '-DI', colorKey: 'minusDiColor', widthKey: 'minusDiWidth' }, ], minusDi: [], // plusDi에서 함께 처리 williamsR: [ { label: '', colorKey: 'williamsRColor', widthKey: 'williamsRWidth' }, ], trix: [ { label: 'TRIX', colorKey: 'trixColor', widthKey: 'trixWidth' }, { label: '시그널', colorKey: 'trixSignalColor', widthKey: 'trixSignalWidth' }, ], newPsychological: [ { label: '', colorKey: 'newPsychologicalColor', widthKey: 'newPsychologicalWidth' }, ], investPsychological: [ { label: '', colorKey: 'investPsychologicalColor', widthKey: 'investPsychologicalWidth' }, ], bwi: [ { label: '', colorKey: 'bwiColor', widthKey: 'bwiWidth' }, ], obv: [ { label: '', colorKey: 'obvColor', widthKey: 'obvWidth' }, ], volumeOsc: [ { label: '', colorKey: 'volumeOscColor', widthKey: 'volumeOscWidth' }, ], vr: [ { label: '', colorKey: 'vrColor', widthKey: 'vrWidth' }, ], disparity: [ { label: '초단기', colorKey: 'disparityUltraShortColor', widthKey: 'disparityUltraShortWidth' }, { label: '단기', colorKey: 'disparityShortColor', widthKey: 'disparityShortWidth' }, { label: '중기', colorKey: 'disparityMidColor', widthKey: 'disparityMidWidth' }, { label: '장기', colorKey: 'disparityLongColor', widthKey: 'disparityLongWidth' }, ], }; // 지표별 파라미터 키 매핑 const indicatorParamKeys: Record = { macdLine: [{ key: 'macdFastPeriod', label: '단기 EMA' }, { key: 'macdSlowPeriod', label: '장기 EMA' }, { key: 'macdSignalPeriod', label: '시그널' }], rsi: [{ key: 'rsiPeriod', label: '기간' }], stochasticK: [{ key: 'stochasticKPeriod', label: '기간' }, { key: 'stochasticSlowing', label: '%K' }, { key: 'stochasticDPeriod', label: '%D' }], cci: [{ key: 'cciPeriod', label: '기간' }, { key: 'cciSignalPeriod', label: '시그널' }], adx: [{ key: 'adxPeriod', label: '기간' }], williamsR: [{ key: 'williamsRPeriod', label: '기간' }], trix: [{ key: 'trixPeriod', label: '기간' }, { key: 'trixSignalPeriod', label: '시그널' }], newPsychological: [{ key: 'newPsychologicalPeriod', label: '기간' }], investPsychological: [{ key: 'investPsychologicalPeriod', label: '기간' }], bwi: [{ key: 'bwiPeriod', label: '기간' }], obv: [{ key: 'obvPeriod', label: '기간' }], volumeOsc: [{ key: 'volumeOscShortPeriod', label: '단기' }, { key: 'volumeOscLongPeriod', label: '장기' }], vr: [{ key: 'vrPeriod', label: '기간' }], disparity: [ { key: 'disparityUltraShortPeriod', label: '초단기' }, { key: 'disparityShortPeriod', label: '단기' }, { key: 'disparityMidPeriod', label: '중기' }, { key: 'disparityLongPeriod', label: '장기' }, ], plusDi: [{ key: 'dmiPeriod', label: '기간' }], minusDi: [{ key: 'dmiPeriod', label: '기간' }], }; // Sortable 아이템 컴포넌트 - 이동평균선 설정과 동일한 스타일 interface SortableItemProps { indicator: IndicatorOrder; isDisabled: boolean; checked: boolean; onToggle: () => void; colors: ChartColorSettings; updateColors: (c: Partial) => void; indicatorSettings: IndicatorSettings; updateIndicatorSettings: (s: Partial) => void; } const SortableItem: React.FC = ({ indicator, isDisabled, checked, onToggle, colors, updateColors, indicatorSettings, updateIndicatorSettings, }) => { const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: indicator.id, disabled: isDisabled || indicator.settingKey === 'candleChart' }); const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1, }; const canDrag = !isDisabled && indicator.settingKey !== 'candleChart'; const colorConfigs = indicatorColorConfigs[indicator.id] || []; const paramKeys = indicatorParamKeys[indicator.id]; const hasSettings = indicator.settingKey !== 'candleChart' && indicator.settingKey !== 'volume'; // 첫 번째 색상/굵기 설정 const primaryColor = colorConfigs[0]; // 추가 색상 설정 (시그널 등) const secondaryColors = colorConfigs.slice(1); return ( {/* 사용 체크박스 */} {/* 드래그 핸들 + 지표명 */} {canDrag && ( )} {indicator.label} {/* 파라미터 영역 */} {hasSettings && paramKeys && paramKeys.map((param, idx) => ( updateIndicatorSettings({ [param.key]: Number(e.target.value) })} onClick={(e) => e.stopPropagation()} disabled={!checked} inputProps={{ min: 1, max: 999, style: { fontSize: 12, padding: '6px 8px', textAlign: 'center' } }} sx={{ width: paramKeys.length > 2 ? 50 : 60, '& .MuiOutlinedInput-root': { bgcolor: 'background.default' }, }} /> ))} {/* 색상 영역 */} {hasSettings && primaryColor && ( updateColors({ [primaryColor.colorKey]: e.target.value })} onClick={(e) => e.stopPropagation()} disabled={!checked} style={{ position: 'absolute', top: 0, left: 0, width: 32, height: 32, opacity: 0, cursor: 'pointer' }} /> )} {/* 추가 색상 (시그널 등) */} {hasSettings && secondaryColors.map((cfg) => ( updateColors({ [cfg.colorKey]: e.target.value })} onClick={(e) => e.stopPropagation()} disabled={!checked} style={{ position: 'absolute', top: 0, left: 0, width: 24, height: 24, opacity: 0, cursor: 'pointer' }} /> ))} {/* 선 굵기 영역 */} {hasSettings && primaryColor?.widthKey && ( <> updateColors({ [primaryColor.widthKey!]: value as number })} onClick={(e) => e.stopPropagation()} disabled={!checked} min={0.5} max={5} step={0.5} sx={{ flex: 1, maxWidth: 150 }} size="small" /> {colors[primaryColor.widthKey]}px {/* 선 미리보기 */} )} ); }; const IndicatorSettingsDialog: React.FC = ({ open, onClose }) => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const { settings, order, updateSettings, updateOrder, resetSettings, saveSettings } = useIndicatorVisibility(); const { colors, updateColors } = useChartColors(); const { indicatorSettings, updateIndicatorSettings } = useIndicatorSettings(); const { isAuthenticated } = useAuth(); const [tempSettings, setTempSettings] = useState(settings); const [tempOrder, setTempOrder] = useState(order); const [activeId, setActiveId] = useState(null); // 드래그 센서 설정 - 즉시 드래그 시작 가능하도록 설정 const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 5, // 5px 이동 후 드래그 시작 }, }), useSensor(TouchSensor, { activationConstraint: { delay: 100, // 100ms 터치 후 드래그 시작 tolerance: 5, }, }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); // 다이얼로그가 열릴 때마다 현재 설정으로 초기화 React.useEffect(() => { if (open) { setTempSettings(settings); setTempOrder(order); } }, [open, settings, order]); const handleDragStart = (event: DragStartEvent) => { setActiveId(event.active.id as string); }; const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; setActiveId(null); if (over && active.id !== over.id) { setTempOrder((items) => { const oldIndex = items.findIndex((item) => item.id === active.id); const newIndex = items.findIndex((item) => item.id === over.id); return arrayMove(items, oldIndex, newIndex); }); } }; const handleToggle = (key: keyof IndicatorVisibilitySettings) => { // candleChart는 변경 불가 if (key === 'candleChart') return; setTempSettings(prev => ({ ...prev, [key]: !prev[key] })); }; const handleSave = async () => { updateSettings(tempSettings); updateOrder(tempOrder); await saveSettings(isAuthenticated); onClose(); }; const handleReset = () => { if (window.confirm('모든 보조지표 설정을 기본값으로 초기화하시겠습니까?')) { resetSettings(); onClose(); } }; return ( 📊 보조지표 표시 설정 {/* 헤더 행 - 이동평균선 설정과 동일한 스타일 */} 사용 이름 파라미터 색상 선 굵기 item.id)} strategy={verticalListSortingStrategy} > {tempOrder.map((indicator) => { const value = tempSettings[indicator.settingKey]; const isChecked = typeof value === 'boolean' ? value : false; return ( handleToggle(indicator.settingKey)} colors={colors} updateColors={updateColors} indicatorSettings={indicatorSettings} updateIndicatorSettings={updateIndicatorSettings} /> ); })} {activeId ? ( {tempOrder.find(item => item.id === activeId)?.label} ) : null} ); }; export default IndicatorSettingsDialog;