지표탭 추가

This commit is contained in:
Macbook
2026-05-27 09:32:26 +09:00
parent d0985f8f0f
commit 8876dbd752
12 changed files with 595 additions and 73 deletions
+103 -28
View File
@@ -72,6 +72,7 @@ export interface ToolbarProps {
onFitContent: () => void;
onScrollToNow?: () => void;
onAddIndicator: (type: string) => void;
onAddIndicators?: (types: string[]) => void;
onRemoveByType: (type: string) => void;
onAutoFib: () => void;
onClearFib: () => void;
@@ -302,13 +303,14 @@ const CHART_TYPES: { value: ChartType; label: string }[] = [
interface IndDropdownProps {
activeIndicators: IndicatorConfig[];
onAdd: (type: string) => void;
onAddMany?: (types: string[]) => void;
onRemove: (type: string) => void;
onOpenSettings?: (type: string) => void;
onClose: () => void;
}
const IndDropdown: React.FC<IndDropdownProps> = ({
activeIndicators, onAdd, onRemove, onOpenSettings, onClose,
activeIndicators, onAdd, onAddMany, onRemove, onOpenSettings, onClose,
}) => {
const [search, setSearch] = React.useState('');
const [category, setCategory] = React.useState<IndicatorTabKey>('All');
@@ -316,6 +318,7 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
const [editorOpen, setEditorOpen] = React.useState(false);
const [editorMode, setEditorMode] = React.useState<'create' | 'edit'>('create');
const searchRef = useRef<HTMLInputElement>(null);
const searchFocusedRef = useRef(false);
const refreshCustomTabs = useCallback(() => {
setCustomTabs(loadCustomTabs());
@@ -358,13 +361,63 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
setSearch('');
};
const addAllContext = React.useMemo(() => {
if (category === 'All') return null;
if (category === 'Main') {
return { label: '주요지표', types: [...MAIN_INDICATOR_TYPES] };
}
if (isCustomTabKey(category) && selectedCustomTab) {
const types = selectedCustomTab.indicatorTypes.filter(
t => INDICATOR_REGISTRY.some(d => d.type === t),
);
return { label: selectedCustomTab.name, types };
}
return null;
}, [category, selectedCustomTab]);
const addAllDisabled = category === 'All' || !addAllContext || addAllContext.types.length === 0;
const handleAddAllInTab = () => {
if (!addAllContext || addAllContext.types.length === 0) return;
const validTypes = addAllContext.types.filter(
type => INDICATOR_REGISTRY.some(d => d.type === type),
);
const toAdd = validTypes.filter(type => !isActive(type));
if (toAdd.length === 0) {
window.alert('추가할 지표가 없습니다. (이미 모두 차트에 있습니다)');
return;
}
const msg = `${addAllContext.label} 탭에 있는 지표 ${toAdd.length}개를 차트에 추가하시겠습니까?`;
if (!window.confirm(msg)) return;
if (onAddMany) {
onAddMany(toAdd);
} else {
toAdd.forEach(type => onAdd(type));
}
};
const stopHeaderBtn = (e: React.MouseEvent) => {
e.stopPropagation();
};
useEffect(() => {
if (editorOpen || searchFocusedRef.current) return;
searchRef.current?.focus();
// ESC 키로 닫기
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
searchFocusedRef.current = true;
}, [editorOpen]);
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key !== 'Escape') return;
if (editorOpen) {
setEditorOpen(false);
return;
}
onClose();
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [onClose]);
}, [onClose, editorOpen]);
const isActive = (type: string) => activeIndicators.some(a => a.type === type);
@@ -429,7 +482,35 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
style={{ cursor: headerCursor, ...headerTouchStyle }}
>
<span className="gc-popup-title"> </span>
<button type="button" className="gc-popup-close" onClick={onClose} aria-label="닫기"></button>
<div className="ind-panel-header-actions">
<button type="button" className="gc-popup-close" onClick={onClose} aria-label="닫기"></button>
<button
type="button"
className="ind-panel-header-btn"
onPointerDown={stopHeaderBtn}
onClick={openCreateEditor}
>
</button>
<button
type="button"
className="ind-panel-header-btn"
disabled={!selectedCustomTab}
onPointerDown={stopHeaderBtn}
onClick={openEditEditor}
>
</button>
<button
type="button"
className="ind-panel-header-btn danger"
disabled={!selectedCustomTab}
onPointerDown={stopHeaderBtn}
onClick={handleDeleteTab}
>
</button>
</div>
</div>
{/* 검색창 */}
@@ -451,7 +532,7 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
{/* 카테고리 탭 */}
<div className="ind-panel-cats-row">
<div className="ind-panel-cats">
<div className="ind-panel-cats" role="tablist" aria-label="지표 탭">
{BUILTIN_TABS.map(c => {
const cnt = c.key === 'All'
? INDICATOR_REGISTRY.length
@@ -460,6 +541,8 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
<button
key={c.key}
type="button"
role="tab"
aria-selected={category === c.key}
className={`ind-panel-cat${category === c.key ? ' active' : ''}`}
onClick={() => { setCategory(c.key); setSearch(''); }}
>
@@ -472,6 +555,8 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
<button
key={tab.id}
type="button"
role="tab"
aria-selected={category === customTabKey(tab.id)}
className={`ind-panel-cat${category === customTabKey(tab.id) ? ' active' : ''}`}
onClick={() => { setCategory(customTabKey(tab.id)); setSearch(''); }}
>
@@ -480,27 +565,16 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
</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>
<button
type="button"
className="ind-panel-add-all"
disabled={addAllDisabled}
title={addAllDisabled ? '전체 탭에서는 사용할 수 없습니다' : '현재 탭의 모든 지표 추가'}
aria-label="현재 탭의 모든 지표 추가"
onClick={handleAddAllInTab}
>
<IcPlus />
</button>
</div>
{/* 지표 목록 */}
@@ -618,7 +692,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
symbol, timeframe, chartType, theme, mode, logScale, showGrid,
activeIndicators,
onSymbol, onTimeframe, onChartType, onTheme, onMode,
onFitContent, onScrollToNow, onAddIndicator, onRemoveByType,
onFitContent, onScrollToNow, onAddIndicator, onAddIndicators, onRemoveByType,
onAutoFib, onClearFib, onScreenshot,
onToggleStats, onToggleWatch, onToggleAlert,
tradeNotifyUnread = 0, onOpenTradeNotifications,
@@ -795,6 +869,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
<IndDropdown
activeIndicators={activeIndicators}
onAdd={type => onAddIndicator(type)}
onAddMany={onAddIndicators}
onRemove={type => onRemoveByType(type)}
onOpenSettings={onOpenIndicatorSettings}
onClose={() => setShowIndMenu(false)}