goldenChat base source add
This commit is contained in:
@@ -0,0 +1,369 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user