보조지표 추가 팝업에서 지표 드래그 이동문제
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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 });
|
||||
} 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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user