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

229 lines
7.9 KiB
TypeScript

import React, { useState } from 'react';
import type { Drawing, DrawingStyle } from '../types';
import { useDraggablePanel } from '../hooks/useDraggablePanel';
const PRESET_COLORS = [
'#FF6D00','#FF5722','#EF5350','#E91E63','#9C27B0',
'#673AB7','#3F51B5','#2196F3','#03A9F4','#00BCD4',
'#009688','#4CAF50','#8BC34A','#CDDC39','#FFC107',
'#FF9800','#795548','#9E9E9E','#607D8B','#FFFFFF',
];
const LINE_WIDTHS = [1, 2, 3, 4] as const;
const LINE_STYLES: { value: DrawingStyle; label: string }[] = [
{ value: 'solid', label: '실선' },
{ value: 'dashed', label: '파선' },
{ value: 'dotted', label: '점선' },
];
interface Props {
drawing: Drawing;
onSave: (updated: Drawing) => void;
onCancel: () => void;
}
const DrawingSettingsModal: React.FC<Props> = ({ drawing, onSave, onCancel }) => {
const [color, setColor] = useState(drawing.color);
const [lineWidth, setLineWidth] = useState(drawing.lineWidth);
const [style, setStyle] = useState<DrawingStyle>(drawing.style);
const [text, setText] = useState(drawing.text ?? '');
const handleSave = () => {
onSave({
...drawing,
color,
lineWidth,
style,
...(drawing.type === 'text' ? { text: text.trim() || drawing.text } : {}),
});
};
const {
panelRef,
dragging,
onHeaderPointerDown, headerTouchStyle,
panelStyle,
headerCursor,
} = useDraggablePanel({ centerOnMount: true });
return (
<div
className="dsm-overlay"
style={{ zIndex: 1200 }}
onMouseDown={e => { if (e.target === e.currentTarget) onCancel(); }}
>
<div
ref={panelRef}
className="dsm-dialog"
style={{
...panelStyle,
minWidth: 300,
cursor: dragging ? 'grabbing' : undefined,
}}
onMouseDown={e => e.stopPropagation()}
>
<div
className="gc-popup-header"
onPointerDown={onHeaderPointerDown}
style={{ cursor: headerCursor, ...headerTouchStyle }}
>
<span className="gc-popup-title">드로잉 설정</span>
<button type="button" className="gc-popup-close" onClick={onCancel} aria-label="닫기"></button>
</div>
<div className="dsm-body">
{/* 색상 */}
<div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 11, color: 'var(--text-muted, #6e7191)', marginBottom: 8 }}>색상</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
{PRESET_COLORS.map(c => (
<button
key={c}
onClick={() => setColor(c)}
style={{
width: 20,
height: 20,
borderRadius: '50%',
background: c,
border: color === c ? '2px solid #fff' : '2px solid transparent',
cursor: 'pointer',
padding: 0,
flexShrink: 0,
}}
/>
))}
{/* 커스텀 컬러 피커 */}
<input
type="color"
value={color}
onChange={e => setColor(e.target.value)}
title="커스텀 색상"
style={{
width: 20,
height: 20,
borderRadius: '50%',
border: 'none',
padding: 0,
cursor: 'pointer',
background: 'none',
}}
/>
</div>
</div>
{/* 선 굵기 (pencil·text·measure 제외) */}
{drawing.type !== 'text' && drawing.type !== 'measure' && (
<div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 11, color: 'var(--text-muted, #6e7191)', marginBottom: 8 }}> 굵기</div>
<div style={{ display: 'flex', gap: 6 }}>
{LINE_WIDTHS.map(w => (
<button
key={w}
onClick={() => setLineWidth(w)}
style={{
width: 34,
height: 28,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: lineWidth === w ? 'var(--accent-bg, rgba(122,162,247,0.15))' : 'transparent',
border: lineWidth === w ? '1px solid var(--accent, #7aa2f7)' : '1px solid var(--border, rgba(122,162,247,0.2))',
borderRadius: 4,
cursor: 'pointer',
color: 'inherit',
}}
>
<div style={{ width: 20, height: w, background: 'currentColor', borderRadius: 1 }} />
</button>
))}
</div>
</div>
)}
{/* 선 스타일 (hline, vline, trendline, ray, channel 계열) */}
{!['text', 'measure', 'pencil', 'fib', 'rect'].includes(drawing.type) && (
<div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 11, color: 'var(--text-muted, #6e7191)', marginBottom: 8 }}> 스타일</div>
<div style={{ display: 'flex', gap: 6 }}>
{LINE_STYLES.map(({ value, label }) => (
<button
key={value}
onClick={() => setStyle(value)}
style={{
flex: 1,
height: 28,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: style === value ? 'var(--accent-bg, rgba(122,162,247,0.15))' : 'transparent',
border: style === value ? '1px solid var(--accent, #7aa2f7)' : '1px solid var(--border, rgba(122,162,247,0.2))',
borderRadius: 4,
cursor: 'pointer',
color: 'inherit',
fontSize: 12,
}}
>
{label}
</button>
))}
</div>
</div>
)}
{/* 텍스트 (text 타입만) */}
{drawing.type === 'text' && (
<div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 11, color: 'var(--text-muted, #6e7191)', marginBottom: 8 }}>텍스트</div>
<input
value={text}
onChange={e => setText(e.target.value)}
style={{
width: '100%',
boxSizing: 'border-box',
background: 'rgba(255,255,255,0.05)',
border: '1px solid var(--border, rgba(122,162,247,0.25))',
borderRadius: 4,
padding: '6px 8px',
color: 'inherit',
fontSize: 13,
}}
/>
</div>
)}
{/* 버튼 */}
<div className="dsm-footer">
<button
onClick={onCancel}
style={{
padding: '6px 16px',
background: 'transparent',
border: '1px solid var(--border, rgba(122,162,247,0.25))',
borderRadius: 5,
cursor: 'pointer',
color: 'inherit',
fontSize: 13,
}}
>취소</button>
<button
onClick={handleSave}
style={{
padding: '6px 16px',
background: 'var(--accent, #7aa2f7)',
border: 'none',
borderRadius: 5,
cursor: 'pointer',
color: '#1a1b26',
fontWeight: 600,
fontSize: 13,
}}
>확인</button>
</div>
</div>
</div>
</div>
);
};
export default DrawingSettingsModal;