Files
goldenChart/frontend_golden/src/components/VerticalLineDialog.tsx
T
2026-05-23 15:11:48 +09:00

370 lines
12 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Box,
Checkbox,
IconButton,
Typography,
Divider,
Select,
MenuItem,
FormControl,
Slider,
Popover,
TextField,
} from '@mui/material';
import { Close as CloseIcon, Edit as EditIcon } from '@mui/icons-material';
import DraggablePaper from './DraggablePaper';
export interface VerticalLineSettings {
showLine: boolean; // 라인
extended: boolean; // 확장 (보조지표까지)
showLabel: boolean; // 타입 라벨 (날짜)
customText?: string; // 커스텀 텍스트 (상단 표시)
color: string;
lineStyle: 'solid' | 'dashed' | 'dotted';
lineWidth: number;
opacity?: number; // 투명도 (0-100)
}
interface VerticalLineDialogProps {
open: boolean;
onClose: () => void;
settings: VerticalLineSettings;
onSave: (settings: VerticalLineSettings) => void;
onDelete?: () => void;
}
export const defaultVerticalLineSettings: VerticalLineSettings = {
showLine: true,
extended: true, // ✅ 기본값: 보조지표까지 확장
showLabel: true,
customText: '',
color: '#2962ff',
lineStyle: 'solid',
lineWidth: 2,
opacity: 100,
};
const VerticalLineDialog: React.FC<VerticalLineDialogProps> = ({
open,
onClose,
settings,
onSave,
onDelete,
}) => {
const [localSettings, setLocalSettings] = useState<VerticalLineSettings>(settings);
const [colorPickerAnchor, setColorPickerAnchor] = useState<HTMLElement | null>(null);
useEffect(() => {
setLocalSettings(settings);
}, [settings]);
// 색상 팔레트
const colorPalette = [
['#000000', '#434651', '#666666', '#999999', '#B2B5BE', '#D1D4DC', '#E1E3EB', '#F0F3FA'],
['#F23645', '#FF9800', '#FDD835', '#26A69A', '#00BCD4', '#2196F3', '#3F51B5', '#9C27B0'],
['#880E4F', '#B71C1C', '#E65100', '#F57F17', '#827717', '#33691E', '#1B5E20', '#004D40'],
['#FF5252', '#FF6E40', '#FFAB40', '#FFD740', '#AEEA00', '#69F0AE', '#40C4FF', '#448AFF'],
['#FF80AB', '#FF4081', '#F50057', '#C51162', '#880E4F', '#4A148C', '#311B92', '#1A237E'],
['#EA80FC', '#E040FB', '#D500F9', '#AA00FF', '#6200EA', '#304FFE', '#2962FF', '#0091EA'],
['#8C9EFF', '#536DFE', '#3D5AFE', '#1976D2', '#0277BD', '#006064', '#004D40', '#1B5E20'],
];
const handleColorPickerOpen = (event: React.MouseEvent<HTMLElement>) => {
setColorPickerAnchor(event.currentTarget);
};
const handleColorPickerClose = () => {
setColorPickerAnchor(null);
};
const handleColorSelect = (color: string) => {
setLocalSettings({ ...localSettings, color });
};
const handleSave = () => {
onSave(localSettings);
onClose();
};
const handleReset = () => {
setLocalSettings(defaultVerticalLineSettings);
};
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="xs"
fullWidth
hideBackdrop={true}
disableScrollLock={true}
PaperComponent={DraggablePaper}
sx={{
pointerEvents: 'none',
'& .MuiDialog-container': {
pointerEvents: 'none',
alignItems: 'flex-start',
},
}}
PaperProps={{
sx: {
bgcolor: 'background.paper',
borderRadius: 2,
pointerEvents: 'auto',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
border: '2px solid rgba(80, 100, 160, 0.5)',
},
}}
>
<DialogTitle sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
bgcolor: '#1976d2',
color: '#fff',
py: 1.2,
}}>
<Typography variant="h6" sx={{ fontWeight: 600, fontSize: '16px', color: '#fff' }}>
세로줄
</Typography>
<IconButton onClick={onClose} size="small" sx={{ color: '#fff' }}>
<CloseIcon />
</IconButton>
</DialogTitle>
<Divider />
<DialogContent sx={{ pt: 3, pb: 2 }}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{/* 라인 체크박스 */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Checkbox
checked={localSettings.showLine}
onChange={(e) => setLocalSettings({ ...localSettings, showLine: e.target.checked })}
size="small"
/>
<Typography variant="body2">라인</Typography>
{/* 색상 선택 */}
{localSettings.showLine && (
<Box
onClick={handleColorPickerOpen}
sx={{
ml: 'auto',
width: 60,
height: 30,
bgcolor: localSettings.color,
borderRadius: 1,
border: '2px solid',
borderColor: 'primary.main',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
'&:hover': {
opacity: 0.8,
},
}}
>
<EditIcon sx={{ fontSize: 16, color: 'white', textShadow: '0 0 2px black' }} />
</Box>
)}
</Box>
{/* 선 스타일 */}
{localSettings.showLine && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, pl: 4 }}>
<Typography variant="caption" sx={{ minWidth: 60 }}>스타일:</Typography>
<FormControl size="small" sx={{ minWidth: 120 }}>
<Select
value={localSettings.lineStyle}
onChange={(e) => setLocalSettings({ ...localSettings, lineStyle: e.target.value as any })}
>
<MenuItem value="solid">━━━</MenuItem>
<MenuItem value="dashed">- - -</MenuItem>
<MenuItem value="dotted">· · ·</MenuItem>
</Select>
</FormControl>
<Typography variant="caption" sx={{ minWidth: 40, ml: 2 }}>두께:</Typography>
<FormControl size="small" sx={{ minWidth: 80 }}>
<Select
value={localSettings.lineWidth}
onChange={(e) => setLocalSettings({ ...localSettings, lineWidth: Number(e.target.value) })}
>
<MenuItem value={1}>1</MenuItem>
<MenuItem value={2}>2</MenuItem>
<MenuItem value={3}>3</MenuItem>
<MenuItem value={4}>4</MenuItem>
</Select>
</FormControl>
</Box>
)}
<Divider />
{/* 확장 체크박스 */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Checkbox
checked={localSettings.extended}
onChange={(e) => setLocalSettings({ ...localSettings, extended: e.target.checked })}
size="small"
/>
<Typography variant="body2">확장</Typography>
<Typography variant="caption" color="text.secondary" sx={{ ml: 'auto' }}>
{localSettings.extended ? '보조지표까지 표시' : '캔들차트만 표시'}
</Typography>
</Box>
{/* 타입 라벨 체크박스 */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Checkbox
checked={localSettings.showLabel}
onChange={(e) => setLocalSettings({ ...localSettings, showLabel: e.target.checked })}
size="small"
/>
<Typography variant="body2">타입 라벨</Typography>
<Typography variant="caption" color="text.secondary" sx={{ ml: 'auto' }}>
{localSettings.showLabel ? '날짜 표시' : '날짜 숨김'}
</Typography>
</Box>
<Divider />
{/* 커스텀 텍스트 입력 */}
<Box>
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>
수직선 상단 텍스트
</Typography>
<TextField
fullWidth
size="small"
placeholder="텍스트를 입력하세요"
value={localSettings.customText || ''}
onChange={(e) => setLocalSettings({ ...localSettings, customText: e.target.value })}
helperText="입력한 텍스트가 수직선 상단에 표시됩니다"
/>
</Box>
</Box>
{/* 색상 팔레트 팝오버 */}
<Popover
open={Boolean(colorPickerAnchor)}
anchorEl={colorPickerAnchor}
onClose={handleColorPickerClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
>
<Box sx={{ p: 2, width: 240 }}>
{/* 색상 팔레트 */}
{colorPalette.map((row, rowIndex) => (
<Box key={rowIndex} sx={{ display: 'flex', gap: 0.5, mb: 0.5 }}>
{row.map((color, colIndex) => (
<Box
key={colIndex}
onClick={() => {
handleColorSelect(color);
handleColorPickerClose();
}}
sx={{
width: 24,
height: 24,
bgcolor: color,
cursor: 'pointer',
border: '1px solid rgba(0,0,0,0.1)',
'&:hover': {
border: '2px solid white',
boxShadow: '0 0 4px rgba(0,0,0,0.3)',
},
}}
/>
))}
</Box>
))}
{/* 투명도 슬라이더 */}
<Box sx={{ mt: 2 }}>
<Typography variant="caption" gutterBottom>
투명도
</Typography>
<Slider
value={localSettings.opacity || 100}
onChange={(e, value) => {
setLocalSettings({ ...localSettings, opacity: value as number });
}}
min={0}
max={100}
valueLabelDisplay="auto"
valueLabelFormat={(value) => `${value}%`}
sx={{ width: '100%' }}
/>
</Box>
{/* 커스텀 색상 입력 */}
<Box sx={{ mt: 2, display: 'flex', gap: 1, alignItems: 'center' }}>
<Typography variant="caption">색상 코드:</Typography>
<input
type="text"
placeholder="#000000"
value={localSettings.color}
onChange={(e) => {
const color = e.target.value;
if (/^#[0-9A-F]{6}$/i.test(color)) {
handleColorSelect(color);
}
}}
style={{
flex: 1,
padding: '4px 8px',
border: '1px solid #ccc',
borderRadius: '4px',
fontSize: '12px',
}}
/>
</Box>
</Box>
</Popover>
</DialogContent>
<Divider />
<DialogActions sx={{ p: 2, gap: 1 }}>
{onDelete && (
<Button
onClick={() => {
if (window.confirm('이 수직선을 삭제하시겠습니까?')) {
onDelete();
onClose();
}
}}
variant="outlined"
color="error"
>
삭제
</Button>
)}
<Button onClick={handleReset} variant="outlined" color="secondary">
초기화
</Button>
<Box sx={{ flex: 1 }} />
<Button onClick={onClose} variant="outlined">
취소
</Button>
<Button onClick={handleSave} variant="contained" color="primary">
확인
</Button>
</DialogActions>
</Dialog>
);
};
export default VerticalLineDialog;