지표탭 추가
This commit is contained in:
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user