저장팝업 텍스트 색상 수정
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { INDICATOR_REGISTRY, type IndicatorDef } from '../utils/indicatorRegistry';
|
||||
import { MAIN_INDICATOR_LABELS } from '../utils/indicatorMainTab';
|
||||
|
||||
interface IndicatorCustomTabEditorProps {
|
||||
mode: 'create' | 'edit';
|
||||
initialName: string;
|
||||
initialTypes: string[];
|
||||
onSave: (name: string, indicatorTypes: string[]) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function indicatorLabel(def: IndicatorDef): { ko: string; en: string } {
|
||||
const main = MAIN_INDICATOR_LABELS[def.type];
|
||||
return main ?? { ko: def.description, en: def.name };
|
||||
}
|
||||
|
||||
const IndicatorCustomTabEditor: React.FC<IndicatorCustomTabEditorProps> = ({
|
||||
mode,
|
||||
initialName,
|
||||
initialTypes,
|
||||
onSave,
|
||||
onClose,
|
||||
}) => {
|
||||
const [name, setName] = useState(initialName);
|
||||
const [selected, setSelected] = useState<Set<string>>(() => new Set(initialTypes));
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => window.removeEventListener('keydown', onKey);
|
||||
}, [onClose]);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const q = search.toLowerCase().trim();
|
||||
if (!q) return INDICATOR_REGISTRY;
|
||||
return INDICATOR_REGISTRY.filter(d => {
|
||||
const lbl = indicatorLabel(d);
|
||||
return (
|
||||
lbl.ko.toLowerCase().includes(q)
|
||||
|| lbl.en.toLowerCase().includes(q)
|
||||
|| d.shortName.toLowerCase().includes(q)
|
||||
|| d.type.toLowerCase().includes(q)
|
||||
);
|
||||
});
|
||||
}, [search]);
|
||||
|
||||
const toggle = (type: string) => {
|
||||
setSelected(prev => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(type)) next.delete(type);
|
||||
else next.add(type);
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) {
|
||||
window.alert('탭 이름을 입력해 주세요.');
|
||||
return;
|
||||
}
|
||||
if (selected.size === 0) {
|
||||
window.alert('탭에 표시할 지표를 하나 이상 선택해 주세요.');
|
||||
return;
|
||||
}
|
||||
const ordered = INDICATOR_REGISTRY
|
||||
.filter(d => selected.has(d.type))
|
||||
.map(d => d.type);
|
||||
onSave(trimmed, ordered);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="ind-tab-editor-overlay"
|
||||
onMouseDown={e => { if (e.target === e.currentTarget) onClose(); }}
|
||||
>
|
||||
<div className="ind-tab-editor" onMouseDown={e => e.stopPropagation()}>
|
||||
<div className="ind-tab-editor-header">
|
||||
<span className="ind-tab-editor-title">
|
||||
{mode === 'create' ? '탭 추가' : '탭 수정'}
|
||||
</span>
|
||||
<button type="button" className="gc-popup-close" onClick={onClose} aria-label="닫기">✕</button>
|
||||
</div>
|
||||
|
||||
<div className="ind-tab-editor-body">
|
||||
<label className="ind-tab-editor-field">
|
||||
<span className="ind-tab-editor-label">탭 이름</span>
|
||||
<input
|
||||
className="ind-tab-editor-input"
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
placeholder="예: 내 지표"
|
||||
autoFocus
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="ind-tab-editor-field">
|
||||
<div className="ind-tab-editor-label-row">
|
||||
<span className="ind-tab-editor-label">
|
||||
지표 선택 ({selected.size}개)
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="ind-tab-editor-link"
|
||||
onClick={() => setSelected(new Set(INDICATOR_REGISTRY.map(d => d.type)))}
|
||||
>
|
||||
전체 선택
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ind-tab-editor-link"
|
||||
onClick={() => setSelected(new Set())}
|
||||
>
|
||||
전체 해제
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
className="ind-tab-editor-input"
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
placeholder="지표 검색..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ind-tab-editor-list">
|
||||
{filtered.length === 0 ? (
|
||||
<div className="ind-panel-empty">검색 결과 없음</div>
|
||||
) : (
|
||||
filtered.map(def => {
|
||||
const lbl = indicatorLabel(def);
|
||||
const checked = selected.has(def.type);
|
||||
return (
|
||||
<label key={def.type} className={`ind-tab-editor-item${checked ? ' checked' : ''}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={() => toggle(def.type)}
|
||||
/>
|
||||
<span className="ind-tab-editor-item-info">
|
||||
<span className="ind-tab-editor-item-name">{lbl.ko}</span>
|
||||
<span className="ind-tab-editor-item-desc">{lbl.en}</span>
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ind-tab-editor-footer">
|
||||
<button type="button" className="ind-tab-editor-btn cancel" onClick={onClose}>
|
||||
취소
|
||||
</button>
|
||||
<button type="button" className="ind-tab-editor-btn save" onClick={handleSave}>
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IndicatorCustomTabEditor;
|
||||
Reference in New Issue
Block a user