보조지표 추가 팝업에서 지표 드래그 이동문제

This commit is contained in:
Macbook
2026-06-03 02:16:48 +09:00
parent 8de3b77479
commit 8cba6a612a
5 changed files with 114 additions and 25 deletions
@@ -27,6 +27,11 @@ import {
usePanelResize,
} from './strategyEditor/usePanelResize';
import '../styles/strategyEditorTheme.css';
import { getKoreanName } from '../utils/marketNameCache';
function toUpbitMarket(symbol: string): string {
return symbol.startsWith('KRW-') ? symbol : `KRW-${symbol}`;
}
const LEFT_KEY = 'btd-left-width';
const RIGHT_KEY = 'btd-right-width';
@@ -188,11 +193,25 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
return null;
}, [tab, selectedBacktest, selectedLive, backtestDetail]);
const centerTitle = tab === 'backtest' && selectedBacktest
? `${selectedBacktest.strategyName || '전략'} · ${selectedBacktest.symbol.replace(/^KRW-/, '')} · ${selectedBacktest.timeframe}`
: tab === 'live' && selectedLive
? `${selectedLive.strategyLabel} · ${selectedLive.symbol.replace(/^KRW-/, '')} · ${selectedLive.sourceLabel}`
: '';
const centerHead = useMemo(() => {
if (tab === 'backtest' && selectedBacktest) {
const market = toUpbitMarket(selectedBacktest.symbol);
return {
ko: getKoreanName(market),
strategy: selectedBacktest.strategyName || '전략 없음',
meta: selectedBacktest.timeframe,
};
}
if (tab === 'live' && selectedLive) {
const market = toUpbitMarket(selectedLive.symbol);
return {
ko: getKoreanName(market),
strategy: selectedLive.strategyLabel,
meta: selectedLive.sourceLabel,
};
}
return null;
}, [tab, selectedBacktest, selectedLive]);
const footerTrades = equityData.trades;
@@ -252,9 +271,13 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
/>
<main className="btd-main">
{centerTitle && (
{centerHead && (
<div className="btd-main-head">
<h2 className="btd-main-title">{centerTitle}</h2>
<h2 className="btd-main-ko">{centerHead.ko}</h2>
<p className="btd-main-strategy">
{centerHead.strategy}
{centerHead.meta ? <span className="btd-main-meta"> · {centerHead.meta}</span> : null}
</p>
</div>
)}
<div className="btd-main-content">
+26 -7
View File
@@ -340,6 +340,7 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
const [category, setCategory] = React.useState<IndicatorTabKey>('All');
const [customTabs, setCustomTabs] = React.useState<IndicatorCustomTab[]>(() => loadCustomTabs());
const [mainOrderTick, setMainOrderTick] = React.useState(0);
const [customTabsRevision, setCustomTabsRevision] = React.useState(0);
const [draggingType, setDraggingType] = React.useState<string | null>(null);
const [dragOverType, setDragOverType] = React.useState<string | null>(null);
const [editorOpen, setEditorOpen] = React.useState(false);
@@ -349,8 +350,15 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
const refreshCustomTabs = useCallback(() => {
setCustomTabs(loadCustomTabs());
setCustomTabsRevision(t => t + 1);
}, []);
useEffect(() => {
const onTabsChanged = () => refreshCustomTabs();
window.addEventListener('gc-indicator-custom-tabs-changed', onTabsChanged);
return () => window.removeEventListener('gc-indicator-custom-tabs-changed', onTabsChanged);
}, [refreshCustomTabs]);
const selectedCustomTab = React.useMemo(() => {
const id = parseCustomTabKey(category);
return id ? customTabs.find(t => t.id === id) ?? null : null;
@@ -390,13 +398,17 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
const tabTypeOrder = React.useMemo((): string[] | null => {
void mainOrderTick;
void customTabsRevision;
if (search.trim()) return null;
if (category === 'Main') return getOrderedMainIndicatorTypes();
if (isCustomTabKey(category) && selectedCustomTab) {
return [...selectedCustomTab.indicatorTypes];
if (isCustomTabKey(category)) {
const id = parseCustomTabKey(category);
if (!id) return null;
const tab = customTabs.find(t => t.id === id);
return tab ? [...tab.indicatorTypes] : null;
}
return null;
}, [category, selectedCustomTab, customTabs, search, mainOrderTick]);
}, [category, customTabs, search, mainOrderTick, customTabsRevision]);
const canReorderList = tabTypeOrder != null && tabTypeOrder.length > 1;
@@ -414,12 +426,19 @@ const IndDropdown: React.FC<IndDropdownProps> = ({
setMainOrderTick(t => t + 1);
saveIndicatorListOrder(mergeIndicatorListOrder(next, getSettingsIndicatorTypes()));
onIndicatorOrderChange?.(next);
} else if (isCustomTabKey(category) && selectedCustomTab) {
updateCustomTab(selectedCustomTab.id, { indicatorTypes: next });
refreshCustomTabs();
} else if (isCustomTabKey(category)) {
const id = parseCustomTabKey(category);
if (!id) return;
const updated = updateCustomTab(id, { indicatorTypes: next });
if (updated) {
setCustomTabs(prev => prev.map(t => (t.id === id ? updated : t)));
setCustomTabsRevision(t => t + 1);
} else {
refreshCustomTabs();
}
onIndicatorOrderChange?.(next);
}
}, [tabTypeOrder, category, selectedCustomTab, refreshCustomTabs, onIndicatorOrderChange]);
}, [tabTypeOrder, category, refreshCustomTabs, onIndicatorOrderChange]);
useEffect(() => {
const clearDrag = () => {
@@ -5,6 +5,11 @@ import BacktestSparkline from './BacktestSparkline';
import { buildEquityFromSignals } from '../../utils/backtestEquity';
import { paperTradesToSignals } from '../../utils/liveExecutionGroups';
import { fmtListTimestamp, fmtShortTimestamp, pct, pctAbs } from '../../utils/backtestUiUtils';
import { getKoreanName } from '../../utils/marketNameCache';
function toUpbitMarket(symbol: string): string {
return symbol.startsWith('KRW-') ? symbol : `KRW-${symbol}`;
}
export type ExecutionListTab = 'backtest' | 'live';
export type BacktestListSort = 'strategy' | 'symbol' | 'return';
@@ -82,12 +87,15 @@ export default function BacktestExecutionList({
const ret = r.totalReturn ?? 0;
const positive = ret >= 0;
const active = selectedBacktestId === r.id;
const sym = r.symbol.replace(/^KRW-/, '');
const market = toUpbitMarket(r.symbol);
const ko = getKoreanName(market);
const strategy = r.strategyName || '전략 없음';
return (
<button key={r.id} type="button" className={`btd-history-card${active ? ' btd-history-card--active' : ''}`} onClick={() => onSelectBacktest(r)}>
<div className="btd-history-card-row">
<div className="btd-history-card-main">
<span className="btd-history-title">{r.strategyName || '전략 없음'} | {sym} | {r.timeframe}</span>
<span className="btd-history-ko">{ko}</span>
<span className="btd-history-strategy">{strategy} · {r.timeframe}</span>
<span className="btd-history-date">{fmtListTimestamp(r.createdAt)}</span>
</div>
<div className="btd-history-card-side">
@@ -109,12 +117,14 @@ export default function BacktestExecutionList({
liveItems.map(item => {
const positive = item.totalReturnPct >= 0;
const active = selectedLiveId === item.id;
const sym = item.symbol.replace(/^KRW-/, '');
const market = toUpbitMarket(item.symbol);
const ko = getKoreanName(market);
return (
<button key={item.id} type="button" className={`btd-history-card${active ? ' btd-history-card--active' : ''}`} onClick={() => onSelectLive(item)}>
<div className="btd-history-card-row">
<div className="btd-history-card-main">
<span className="btd-history-title">{item.strategyLabel} | {sym} | {item.sourceLabel}</span>
<span className="btd-history-ko">{ko}</span>
<span className="btd-history-strategy">{item.strategyLabel} · {item.sourceLabel}</span>
<span className="btd-history-date">{fmtShortTimestamp(item.createdAt)}</span>
</div>
<div className="btd-history-card-side">
+43 -6
View File
@@ -970,13 +970,36 @@
.btd-main-head {
flex-shrink: 0;
padding: 8px 12px 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.btd-main-title {
.btd-main-ko {
margin: 0;
font-size: 0.88rem;
font-size: 0.92rem;
font-weight: 700;
color: var(--btd-gold);
color: var(--se-text);
line-height: 1.25;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.btd-main-strategy {
margin: 0;
font-size: 0.75rem;
font-weight: 600;
color: var(--se-text-muted);
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.btd-main-meta {
font-weight: 500;
color: var(--se-text-dim);
}
.btd-main-content {
@@ -1121,13 +1144,27 @@
min-width: 0;
}
.btd-history-title {
.btd-history-ko {
display: block;
font-size: 0.72rem;
font-size: 0.8rem;
font-weight: 700;
color: var(--se-text);
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.btd-history-strategy {
display: block;
font-size: 0.68rem;
font-weight: 600;
color: var(--se-text-muted);
line-height: 1.35;
margin-bottom: 4px;
margin-bottom: 3px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.btd-history-date {
@@ -45,7 +45,7 @@ export function updateCustomTab(
...patch,
name: patch.name != null ? patch.name.trim() : tabs[idx].name,
indicatorTypes: patch.indicatorTypes != null
? [...new Set(patch.indicatorTypes)]
? [...patch.indicatorTypes]
: tabs[idx].indicatorTypes,
};
tabs[idx] = next;