저장팝업 텍스트 색상 수정

This commit is contained in:
Macbook
2026-05-26 23:50:13 +09:00
parent 94e96b9a9b
commit fe812389cc
13 changed files with 728 additions and 62 deletions
+140 -35
View File
@@ -1,12 +1,23 @@
import React, { useRef, useEffect, useState, useCallback } from 'react';
import { useDraggablePanel } from '../hooks/useDraggablePanel';
import ReactDOM from 'react-dom';
import { INDICATOR_REGISTRY, type IndicatorDef, type IndicatorCategory } from '../utils/indicatorRegistry';
import { INDICATOR_REGISTRY, type IndicatorDef } from '../utils/indicatorRegistry';
import {
MAIN_INDICATOR_TYPES,
MAIN_INDICATOR_LABELS,
customTabKey,
isCustomTabKey,
parseCustomTabKey,
type IndicatorTabKey,
} from '../utils/indicatorMainTab';
import {
loadCustomTabs,
createCustomTab,
updateCustomTab,
deleteCustomTab,
type IndicatorCustomTab,
} from '../utils/indicatorCustomTabsStorage';
import IndicatorCustomTabEditor from './IndicatorCustomTabEditor';
import { getIndicatorListLabels } from '../utils/indicatorSettingsList';
// IndicatorCategory는 IndDropdown 상태 타입에 사용됨
import type { ChartType, Theme, Timeframe, ChartMode, IndicatorConfig } from '../types';
@@ -263,18 +274,10 @@ const IcReplay = () => (
</svg>
);
// ─── 카테고리 목록 ─────────────────────────────────────────────────────────
const CATEGORIES: Array<{ key: IndicatorTabKey; label: string }> = [
{ key: 'All', label: '전체' },
{ key: 'Main', label: 'Main' },
{ key: 'Moving Averages', label: 'MA' },
{ key: 'Channels & Bands', label: '밴드' },
{ key: 'Oscillators', label: '오실레이터' },
{ key: 'Momentum', label: '모멘텀' },
{ key: 'Trend', label: '추세' },
{ key: 'Volatility', label: '변동성' },
{ key: 'Volume', label: '거래량' },
{ key: 'Candlestick Patterns',label: '패턴' },
// ─── 기본 탭 (사용자 정의 탭은 localStorage) ───────────────────────────────
const BUILTIN_TABS: Array<{ key: IndicatorTabKey; label: string }> = [
{ key: 'All', label: '전체' },
{ key: 'Main', label: '주요지표' },
];
// ─── 차트 타입 아이콘 매핑 ─────────────────────────────────────────────────
@@ -309,8 +312,52 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
}) => {
const [search, setSearch] = React.useState('');
const [category, setCategory] = React.useState<IndicatorTabKey>('All');
const [customTabs, setCustomTabs] = React.useState<IndicatorCustomTab[]>(() => loadCustomTabs());
const [editorOpen, setEditorOpen] = React.useState(false);
const [editorMode, setEditorMode] = React.useState<'create' | 'edit'>('create');
const searchRef = useRef<HTMLInputElement>(null);
const refreshCustomTabs = useCallback(() => {
setCustomTabs(loadCustomTabs());
}, []);
const selectedCustomTab = React.useMemo(() => {
const id = parseCustomTabKey(category);
return id ? customTabs.find(t => t.id === id) ?? null : null;
}, [category, customTabs]);
const openCreateEditor = () => {
setEditorMode('create');
setEditorOpen(true);
};
const openEditEditor = () => {
if (!selectedCustomTab) return;
setEditorMode('edit');
setEditorOpen(true);
};
const handleDeleteTab = () => {
if (!selectedCustomTab) return;
if (!window.confirm(`"${selectedCustomTab.name}" 탭을 삭제할까요?`)) return;
deleteCustomTab(selectedCustomTab.id);
refreshCustomTabs();
setCategory('All');
};
const handleEditorSave = (name: string, indicatorTypes: string[]) => {
if (editorMode === 'create') {
const tab = createCustomTab(name, indicatorTypes);
refreshCustomTabs();
setCategory(customTabKey(tab.id));
} else if (selectedCustomTab) {
updateCustomTab(selectedCustomTab.id, { name, indicatorTypes });
refreshCustomTabs();
}
setEditorOpen(false);
setSearch('');
};
useEffect(() => {
searchRef.current?.focus();
// ESC 키로 닫기
@@ -342,11 +389,18 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
.filter(matchesQuery);
}
return INDICATOR_REGISTRY.filter(d => {
const matchCat = category === 'All' || d.category === category;
return matchCat && matchesQuery(d);
});
}, [search, category]);
if (isCustomTabKey(category)) {
const tabId = parseCustomTabKey(category);
const tab = tabId ? customTabs.find(t => t.id === tabId) : null;
if (!tab) return [];
return tab.indicatorTypes
.map(type => INDICATOR_REGISTRY.find(d => d.type === type))
.filter((d): d is IndicatorDef => d != null)
.filter(matchesQuery);
}
return INDICATOR_REGISTRY.filter(matchesQuery);
}, [search, category, customTabs]);
const {
panelRef,
@@ -396,24 +450,57 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
</div>
{/* 카테고리 탭 */}
<div className="ind-panel-cats">
{CATEGORIES.map(c => {
const cnt = c.key === 'All'
? INDICATOR_REGISTRY.length
: c.key === 'Main'
? MAIN_INDICATOR_TYPES.length
: INDICATOR_REGISTRY.filter(d => d.category === c.key).length;
return (
<div className="ind-panel-cats-row">
<div className="ind-panel-cats">
{BUILTIN_TABS.map(c => {
const cnt = c.key === 'All'
? INDICATOR_REGISTRY.length
: MAIN_INDICATOR_TYPES.length;
return (
<button
key={c.key}
type="button"
className={`ind-panel-cat${category === c.key ? ' active' : ''}`}
onClick={() => { setCategory(c.key); setSearch(''); }}
>
{c.label}
<span className="ind-panel-cat-cnt"> {cnt}</span>
</button>
);
})}
{customTabs.map(tab => (
<button
key={c.key}
className={`ind-panel-cat${category === c.key ? ' active' : ''}`}
onClick={() => { setCategory(c.key); setSearch(''); }}
key={tab.id}
type="button"
className={`ind-panel-cat${category === customTabKey(tab.id) ? ' active' : ''}`}
onClick={() => { setCategory(customTabKey(tab.id)); setSearch(''); }}
>
{c.label}
<span className="ind-panel-cat-cnt"> {cnt}</span>
{tab.name}
<span className="ind-panel-cat-cnt"> {tab.indicatorTypes.length}</span>
</button>
);
})}
))}
</div>
<div className="ind-panel-cats-actions">
<button type="button" className="ind-panel-cat-action" onClick={openCreateEditor}>
</button>
<button
type="button"
className="ind-panel-cat-action"
disabled={!selectedCustomTab}
onClick={openEditEditor}
>
</button>
<button
type="button"
className="ind-panel-cat-action danger"
disabled={!selectedCustomTab}
onClick={handleDeleteTab}
>
</button>
</div>
</div>
{/* 지표 목록 */}
@@ -456,11 +543,19 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
)}
{filtered.length === 0 ? (
<div className="ind-panel-empty">"{search}" </div>
<div className="ind-panel-empty">
{search
? `"${search}" 검색 결과 없음`
: isCustomTabKey(category)
? '이 탭에 표시할 지표가 없습니다. 수정 버튼으로 지표를 추가해 주세요.'
: '표시할 지표가 없습니다'}
</div>
) : (
filtered.map(def => {
const active = isActive(def.type);
const mainLbl = category === 'Main' ? MAIN_INDICATOR_LABELS[def.type] : undefined;
const mainLbl = (category === 'Main' || isCustomTabKey(category))
? MAIN_INDICATOR_LABELS[def.type]
: undefined;
return (
<div key={def.type} className={`ind-panel-item${active ? ' active' : ''}`}>
<div className="ind-panel-item-info">
@@ -503,6 +598,16 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
)}
</div>
{editorOpen && (
<IndicatorCustomTabEditor
mode={editorMode}
initialName={editorMode === 'edit' && selectedCustomTab ? selectedCustomTab.name : ''}
initialTypes={editorMode === 'edit' && selectedCustomTab ? selectedCustomTab.indicatorTypes : []}
onSave={handleEditorSave}
onClose={() => setEditorOpen(false)}
/>
)}
</div>
</div>
);