저장팝업 텍스트 색상 수정
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;
|
||||
@@ -145,6 +145,7 @@ export default function StrategyEditorPage({ theme }: Props) {
|
||||
const [stratDesc, setStratDesc] = useState('');
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [saveOpen, setSaveOpen] = useState(false);
|
||||
const [saveNameError, setSaveNameError] = useState(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
const [descOpen, setDescOpen] = useState(false);
|
||||
const [deleteId, setDeleteId] = useState<number | null>(null);
|
||||
@@ -493,7 +494,12 @@ export default function StrategyEditorPage({ theme }: Props) {
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!stratName.trim()) { showSnack('전략 이름을 입력하세요', false); return; }
|
||||
if (!stratName.trim()) {
|
||||
setSaveNameError(true);
|
||||
showSnack('전략 이름을 입력하세요', false);
|
||||
return;
|
||||
}
|
||||
setSaveNameError(false);
|
||||
const encodedBuy = encodeConditionForSave(buyEditorState);
|
||||
const encodedSell = encodeConditionForSave(sellEditorState);
|
||||
if (!encodedBuy && !encodedSell) { showSnack('조건을 최소 1개 추가하세요', false); return; }
|
||||
@@ -830,7 +836,7 @@ export default function StrategyEditorPage({ theme }: Props) {
|
||||
>
|
||||
⬆
|
||||
</button>
|
||||
<button type="button" className="se-btn se-btn--gold" onClick={() => setSaveOpen(true)}>저장하기</button>
|
||||
<button type="button" className="se-btn se-btn--gold" onClick={() => { setSaveNameError(false); setSaveOpen(true); }}>저장하기</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -1166,10 +1172,18 @@ export default function StrategyEditorPage({ theme }: Props) {
|
||||
</div>
|
||||
|
||||
{saveOpen && (
|
||||
<DraggableModalFrame onClose={() => setSaveOpen(false)} title="전략 저장">
|
||||
<DraggableModalFrame onClose={() => { setSaveNameError(false); setSaveOpen(false); }} title="전략 저장">
|
||||
<div className="se-modal-body">
|
||||
<label className="se-field-lbl">전략명 *</label>
|
||||
<input className="se-field-inp" value={stratName} onChange={e => setStratName(e.target.value)} placeholder="예: Golden_RSI_V1" />
|
||||
<input
|
||||
className={`se-field-inp${saveNameError ? ' se-field-inp--error' : ''}`}
|
||||
value={stratName}
|
||||
onChange={e => { setStratName(e.target.value); setSaveNameError(false); }}
|
||||
placeholder="예: Golden_RSI_V1"
|
||||
/>
|
||||
{saveNameError && (
|
||||
<p className="se-field-error" role="alert">전략 이름을 입력하세요</p>
|
||||
)}
|
||||
<label className="se-field-lbl">설명</label>
|
||||
<textarea className="se-field-ta" value={stratDesc} onChange={e => setStratDesc(e.target.value)} rows={2} />
|
||||
<div className="se-modal-actions">
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
type StrategyFlowNodeData,
|
||||
} from '../../utils/strategyFlowLayout';
|
||||
import { hasNodeSettings } from '../../utils/conditionPeriods';
|
||||
import { getStrategyIndicatorDisplayName } from '../../utils/strategyPaletteStorage';
|
||||
import ConditionNodeSettings from './ConditionNodeSettings';
|
||||
|
||||
const LOGIC_COLORS: Record<string, string> = {
|
||||
@@ -220,6 +221,7 @@ export const ConditionFlowNode = memo(function ConditionFlowNode({ id, data, sel
|
||||
const node = d.logicNode!;
|
||||
const cond = node.condition;
|
||||
const ind = cond?.indicatorType ?? 'COND';
|
||||
const indLabel = getStrategyIndicatorDisplayName(ind);
|
||||
const condType = cond?.conditionType ?? '';
|
||||
const isCross = ['CROSS_UP', 'CROSS_DOWN'].includes(condType);
|
||||
const tone = isCross ? 'cross' : 'value';
|
||||
@@ -245,7 +247,7 @@ export const ConditionFlowNode = memo(function ConditionFlowNode({ id, data, sel
|
||||
canTargetConnect={d.canTargetConnect !== false}
|
||||
/>
|
||||
<div className="se-flow-cond-top">
|
||||
<div className="se-flow-cond-badge">{ind}</div>
|
||||
<div className="se-flow-cond-badge">{indLabel}</div>
|
||||
<div className="se-flow-cond-actions">
|
||||
{showSettings && (
|
||||
<button
|
||||
@@ -267,7 +269,7 @@ export const ConditionFlowNode = memo(function ConditionFlowNode({ id, data, sel
|
||||
<button type="button" className="se-flow-del" title="삭제" onClick={() => d.onDelete?.(id)}>×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="se-flow-cond-text">{d.label ?? ind}</div>
|
||||
<div className="se-flow-cond-text">{d.label ?? indLabel}</div>
|
||||
{settingsOpen && cond && d.def && (
|
||||
<ConditionNodeSettings
|
||||
condition={cond}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import type { LogicNode } from '../../utils/strategyTypes';
|
||||
import { nodeToText, type DefType } from '../../utils/strategyEditorShared';
|
||||
import { getStrategyIndicatorDisplayName } from '../../utils/strategyPaletteStorage';
|
||||
import { getIndicatorPaletteCategory } from './paletteCategories';
|
||||
import {
|
||||
collectEditorBranches,
|
||||
@@ -21,16 +22,18 @@ interface Props {
|
||||
function renderCondition(node: LogicNode, def: DefType): React.ReactNode {
|
||||
const full = nodeToText(node, def);
|
||||
const ind = node.condition?.indicatorType ?? '';
|
||||
const tail = ind && full.startsWith(`${ind} -`) ? full.slice(ind.length) : full;
|
||||
const indLabel = getStrategyIndicatorDisplayName(ind);
|
||||
const prefix = `${indLabel} -`;
|
||||
const tail = indLabel && full.startsWith(prefix) ? full.slice(prefix.length) : full;
|
||||
const cat = getIndicatorPaletteCategory(ind);
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className="se-formula-punc">(</span>
|
||||
{ind ? (
|
||||
{indLabel ? (
|
||||
<>
|
||||
<span className={`se-formula-ind se-formula-ind--${cat}`}>{ind}</span>
|
||||
{tail ? <span className="se-formula-detail">{tail}</span> : null}
|
||||
<span className={`se-formula-ind se-formula-ind--${cat}`}>{indLabel}</span>
|
||||
{tail ? <span className="se-formula-detail">{tail.startsWith(' ') ? tail : ` ${tail}`}</span> : null}
|
||||
</>
|
||||
) : (
|
||||
<span className="se-formula-detail">{full || '?'}</span>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
type DefType,
|
||||
} from '../../utils/strategyEditorShared';
|
||||
import { hasNodeSettings } from '../../utils/conditionPeriods';
|
||||
import { getStrategyIndicatorDisplayName } from '../../utils/strategyPaletteStorage';
|
||||
import ConditionNodeSettings from './ConditionNodeSettings';
|
||||
import {
|
||||
type EditorConditionState,
|
||||
@@ -109,7 +110,9 @@ const TreeNodeComp: React.FC<TreeNodeProps> = ({
|
||||
>
|
||||
<div className="sp-node-head sp-node-head--cond" style={{ borderLeftColor: color }}>
|
||||
<span className="sp-node-badge" style={{ background: color }}>
|
||||
{node.type === 'CONDITION' && node.condition ? node.condition.indicatorType : node.type}
|
||||
{node.type === 'CONDITION' && node.condition
|
||||
? getStrategyIndicatorDisplayName(node.condition.indicatorType)
|
||||
: node.type}
|
||||
</span>
|
||||
<span className="sp-node-label">{label}</span>
|
||||
<div className="sp-node-actions">
|
||||
|
||||
Reference in New Issue
Block a user