import React, { useState, useEffect } from 'react'; import { Dialog, DialogTitle, DialogContent, Button, Box, Typography, TextField, Switch, Slider, Divider, Paper, Grid, IconButton, Tooltip, Alert, Select, MenuItem, FormControl, } from '@mui/material'; import { Close, Refresh } from '@mui/icons-material'; import { useIndicatorVisibility } from '../contexts/IndicatorVisibilityContext'; import { useChartColors, ChartColorSettings } from '../contexts/ChartColorContext'; import type { MultiChartBollingerState } from '../stores/useDashboardStore'; import { bollingerDialogDefaultsFromStore, bollingerDialogToStore, bollingerStoreToDialogColors, bollingerStoreToDialogSettings, } from '../utils/multiChartToolbarSettings'; import DraggablePaper from './DraggablePaper'; interface BollingerSettingsDialogProps { open: boolean; onClose: () => void; multiChartToolbar?: { bollinger: MultiChartBollingerState; setBollinger: ( value: MultiChartBollingerState | ((prev: MultiChartBollingerState) => MultiChartBollingerState) ) => void; }; } const BollingerSettingsDialog: React.FC = ({ open, onClose, multiChartToolbar }) => { const { settings, updateSettings } = useIndicatorVisibility(); const { colors, updateColors } = useChartColors(); // 로컬 상태 (볼린저 밴드 관련 설정만 포함) const [localSettings, setLocalSettings] = useState<{ bollingerUpper: boolean; bollingerMiddle: boolean; bollingerLower: boolean; bollingerPeriod: number; bollingerStdDev: number; }>({ bollingerUpper: settings.bollingerUpper, bollingerMiddle: settings.bollingerMiddle, bollingerLower: settings.bollingerLower, bollingerPeriod: settings.bollingerPeriod || 20, bollingerStdDev: settings.bollingerStdDev || 2.0, }); const [localColors, setLocalColors] = useState({ bollingerUpperColor: colors.bollingerUpperColor, bollingerMiddleColor: colors.bollingerMiddleColor, bollingerLowerColor: colors.bollingerLowerColor, bollingerUpperWidth: colors.bollingerUpperWidth, bollingerMiddleWidth: colors.bollingerMiddleWidth, bollingerLowerWidth: colors.bollingerLowerWidth, bollingerUpperLineStyle: colors.bollingerUpperLineStyle, bollingerMiddleLineStyle: colors.bollingerMiddleLineStyle, bollingerLowerLineStyle: colors.bollingerLowerLineStyle, bollingerBandBackgroundColor: colors.bollingerBandBackgroundColor, }); // 배경 투명도 상태 const [bandOpacity, setBandOpacity] = useState(0.15); // ✅ 기본값 증가 (더 잘 보이도록) // rgba에서 alpha 값 추출 함수 const extractOpacity = (color: string): number => { const match = color.match(/rgba?\([^,]+,[^,]+,[^,]+,\s*([\d.]+)\)/); return match ? parseFloat(match[1]) : 0.1; }; // rgba에서 rgb hex 추출 함수 const extractRgbFromRgba = (rgba: string): string => { const match = rgba.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); if (match) { const r = parseInt(match[1]).toString(16).padStart(2, '0'); const g = parseInt(match[2]).toString(16).padStart(2, '0'); const b = parseInt(match[3]).toString(16).padStart(2, '0'); return `#${r}${g}${b}`; } return '#000000'; }; // hex to rgba 변환 함수 const hexToRgba = (hex: string, opacity: number): string => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); if (result) { const r = parseInt(result[1], 16); const g = parseInt(result[2], 16); const b = parseInt(result[3], 16); return `rgba(${r}, ${g}, ${b}, ${opacity})`; } return `rgba(0, 0, 0, ${opacity})`; }; useEffect(() => { if (!open || !multiChartToolbar) return; const b = multiChartToolbar.bollinger; setLocalSettings(bollingerStoreToDialogSettings(b)); setLocalColors(bollingerStoreToDialogColors(b)); setBandOpacity(extractOpacity(b.bandBackgroundColor)); }, [open, multiChartToolbar]); useEffect(() => { if (!open || multiChartToolbar) return; setLocalSettings({ bollingerUpper: settings.bollingerUpper, bollingerMiddle: settings.bollingerMiddle, bollingerLower: settings.bollingerLower, bollingerPeriod: settings.bollingerPeriod || 20, bollingerStdDev: settings.bollingerStdDev || 2.0, }); setLocalColors({ bollingerUpperColor: colors.bollingerUpperColor, bollingerMiddleColor: colors.bollingerMiddleColor, bollingerLowerColor: colors.bollingerLowerColor, bollingerUpperWidth: colors.bollingerUpperWidth, bollingerMiddleWidth: colors.bollingerMiddleWidth, bollingerLowerWidth: colors.bollingerLowerWidth, bollingerBandBackgroundColor: colors.bollingerBandBackgroundColor, bollingerUpperLineStyle: colors.bollingerUpperLineStyle, bollingerMiddleLineStyle: colors.bollingerMiddleLineStyle, bollingerLowerLineStyle: colors.bollingerLowerLineStyle, }); setBandOpacity(extractOpacity(colors.bollingerBandBackgroundColor)); }, [open, multiChartToolbar, settings, colors]); const handleSave = () => { if (multiChartToolbar) { multiChartToolbar.setBollinger(bollingerDialogToStore(localSettings, localColors)); } else { console.log('💾 [볼린저밴드 설정] 저장 시작:', { 이전설정: { period: settings.bollingerPeriod, stdDev: settings.bollingerStdDev, upper: settings.bollingerUpper, middle: settings.bollingerMiddle, lower: settings.bollingerLower, }, 새설정: localSettings, }); updateSettings({ ...settings, ...localSettings, }); updateColors(localColors); console.log('✅ [볼린저밴드 설정] updateSettings 및 updateColors 호출 완료'); } onClose(); }; const handleReset = () => { if (multiChartToolbar) { const { localSettings: ls, localColors: lc } = bollingerDialogDefaultsFromStore(); setLocalSettings(ls); setLocalColors(lc); setBandOpacity(extractOpacity(lc.bollingerBandBackgroundColor)); } else { setLocalSettings({ bollingerUpper: false, bollingerMiddle: false, bollingerLower: false, bollingerPeriod: 20, bollingerStdDev: 2.0, }); setLocalColors({ bollingerUpperColor: '#FF9800', bollingerMiddleColor: '#2196F3', bollingerLowerColor: '#4CAF50', bollingerUpperWidth: 1.5, bollingerMiddleWidth: 1.5, bollingerLowerWidth: 1.5, bollingerUpperLineStyle: 'solid' as const, bollingerMiddleLineStyle: 'solid' as const, bollingerLowerLineStyle: 'solid' as const, bollingerBandBackgroundColor: 'rgba(33, 150, 243, 0.15)', }); setBandOpacity(0.15); } }; const handlePeriodChange = (value: number) => { setLocalSettings({ ...localSettings, bollingerPeriod: value }); }; const handleStdDevChange = (value: number) => { setLocalSettings({ ...localSettings, bollingerStdDev: value }); }; // 색상 테이블 컴포넌트 const ColorTable = () => { const configs: Array<{ label: string; enableKey: 'bollingerUpper' | 'bollingerMiddle' | 'bollingerLower'; colorKey: 'bollingerUpperColor' | 'bollingerMiddleColor' | 'bollingerLowerColor'; widthKey: 'bollingerUpperWidth' | 'bollingerMiddleWidth' | 'bollingerLowerWidth'; lineStyleKey: 'bollingerUpperLineStyle' | 'bollingerMiddleLineStyle' | 'bollingerLowerLineStyle'; }> = [ { label: '상한선', enableKey: 'bollingerUpper', colorKey: 'bollingerUpperColor', widthKey: 'bollingerUpperWidth', lineStyleKey: 'bollingerUpperLineStyle' }, { label: '중간선', enableKey: 'bollingerMiddle', colorKey: 'bollingerMiddleColor', widthKey: 'bollingerMiddleWidth', lineStyleKey: 'bollingerMiddleLineStyle' }, { label: '하한선', enableKey: 'bollingerLower', colorKey: 'bollingerLowerColor', widthKey: 'bollingerLowerWidth', lineStyleKey: 'bollingerLowerLineStyle' }, ]; return ( 🎨 색상, 선 굵기, 선 유형 {/* 헤더 행 */} theme.palette.mode === 'dark' ? '#616161' : '#9e9e9e', borderRadius: 1, }}> ON/OFF 항목 색상 굵기 선 유형 미리보기 {/* 각 색상 설정 행 */} {configs.map((cfg) => { const colorValue = String(localColors[cfg.colorKey] || '#666666'); const widthValue = Number(localColors[cfg.widthKey] ?? 1.5); const lineStyleValue = String(localColors[cfg.lineStyleKey] ?? 'solid'); const enabledValue = Boolean(localSettings[cfg.enableKey]); return ( {/* ON/OFF 토글 */} setLocalSettings({ ...localSettings, [cfg.enableKey]: e.target.checked })} size="small" /> {/* 항목 이름 */} {cfg.label} {/* 색상 선택 */} setLocalColors({ ...localColors, [cfg.colorKey]: e.target.value })} style={{ width: '100%', height: '30px', border: '1px solid rgba(0, 0, 0, 0.23)', borderRadius: '4px', cursor: 'pointer', backgroundColor: colorValue, }} /> {/* 굵기 슬라이더 */} setLocalColors({ ...localColors, [cfg.widthKey]: value as number })} min={1} max={5} step={0.5} size="small" sx={{ flex: 1 }} /> {widthValue}px {/* 선 유형 */} {/* 미리보기 */} ); })} ); }; return ( 볼린저 밴드 설정 e.stopPropagation()} > 볼린저 밴드는 가격의 변동성을 측정하는 지표입니다. 상한선과 하한선은 표준편차를 이용하여 계산됩니다. {/* 기간 설정 */} 파라미터 설정 기간 (Period): {localSettings.bollingerPeriod}일 handlePeriodChange(value as number)} min={5} max={50} step={1} marks={[ { value: 10, label: '10' }, { value: 20, label: '20' }, { value: 30, label: '30' }, { value: 40, label: '40' }, ]} valueLabelDisplay="auto" sx={{ flex: 1 }} /> handlePeriodChange(Math.max(5, Math.min(50, Number(e.target.value))))} size="small" sx={{ width: 70 }} inputProps={{ min: 5, max: 50 }} /> 표준편차 승수 (StdDev): {localSettings.bollingerStdDev} handleStdDevChange(value as number)} min={1.0} max={3.0} step={0.1} marks={[ { value: 1.5, label: '1.5' }, { value: 2.0, label: '2.0' }, { value: 2.5, label: '2.5' }, ]} valueLabelDisplay="auto" sx={{ flex: 1 }} /> handleStdDevChange(Math.max(1.0, Math.min(3.0, Number(e.target.value))))} size="small" sx={{ width: 70 }} inputProps={{ min: 1.0, max: 3.0, step: 0.1 }} /> {/* 색상 및 선 설정 테이블 */} {/* 배경색 설정 */} 배경색 설정 밴드 배경색 { const rgbaColor = hexToRgba(e.target.value, bandOpacity); setLocalColors({ ...localColors, bollingerBandBackgroundColor: rgbaColor }); }} style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', opacity: 0, cursor: 'pointer', }} /> {localColors.bollingerBandBackgroundColor} 투명도: {(bandOpacity * 100).toFixed(0)}% { const newOpacity = value as number; setBandOpacity(newOpacity); const baseColor = extractRgbFromRgba(localColors.bollingerBandBackgroundColor); const rgbaColor = hexToRgba(baseColor, newOpacity); setLocalColors({ ...localColors, bollingerBandBackgroundColor: rgbaColor }); }} min={0} max={1} step={0.05} valueLabelDisplay="auto" valueLabelFormat={(value) => `${(value * 100).toFixed(0)}%`} sx={{ width: '100%' }} /> 상한선과 하한선 사이 영역을 반투명하게 표시합니다 ); }; export default BollingerSettingsDialog;