전략편집기 시간봉 선택오류 수정

This commit is contained in:
Macbook
2026-06-12 21:32:19 +09:00
parent 046bcc6379
commit 6118230471
8 changed files with 164 additions and 97 deletions
+29 -33
View File
@@ -206,6 +206,10 @@ export interface ChartSlotProps {
chartLiveReceiveHighlight?: boolean;
/** 실시간 차트 화면 표시 여부 (설정 등 다른 메뉴에서 숨김 시 false) */
chartVisible?: boolean;
/** 워크스페이스·지표 설정 반영 완료 — STOMP 시작 게이트 */
chartConfigReady?: boolean;
/** paint 완료 알림 (멀티차트 전체 로딩용) */
onPaintReadyChange?: (slotIndex: number, ready: boolean) => void;
/** pane 구분선 */
chartPaneSeparator?: ChartPaneSeparatorOptions;
/** 크로스헤어 이동 시 OHLC·보조지표 수치 표시 */
@@ -241,6 +245,8 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
compactMode = false,
chartLiveReceiveHighlight = true,
chartVisible = true,
chartConfigReady = true,
onPaintReadyChange,
chartPaneSeparator,
chartCrosshairInfoVisible = true,
chartHoverToolbarVisible = true,
@@ -508,17 +514,6 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
// ※ TradingChart를 재마운트(key 변경)하면 managerRef가 초기화되어 MarketSearchPanel 등
// 다른 상태에 영향을 줄 수 있으므로, 재마운트 대신 resetPaneHeights 재시도만 수행한다.
const slotContainerRef = useRef<HTMLDivElement | null>(null);
// 데이터 로딩 완료 후 차트가 여전히 비어 있으면 1회만 재마운트 (data + blank guard)
const [reloadTick, setReloadTick] = useState(0);
const reloadTriggeredRef = useRef(false); // 재마운트는 최대 1회
useLayoutEffect(() => {
reloadTriggeredRef.current = false;
chartLiveReadyRef.current = false;
pendingRealtimeBarRef.current = null;
pendingSyncVisibleRef.current = null;
pendingSyncLogicalRef.current = null;
skipSyncApplyRef.current = true;
}, [symbol, timeframe]);
useEffect(() => {
const checks = [200, 500, 1000, 2000].map(delay =>
setTimeout(() => {
@@ -528,21 +523,13 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
const h = el.clientHeight;
if (h <= 0) return;
if (mgr.hasMainSeries()) {
// 차트 데이터 있음 → pane 높이만 재적용
mgr.resetPaneHeights(h);
} else if (!reloadTriggeredRef.current) {
// 차트 비어 있고 아직 재시도 안 했음 → 1회만 재마운트
// (bars 로딩 지연일 수 있으므로, 늦은 타이머에서만 트리거)
if (delay >= 1000) {
reloadTriggeredRef.current = true;
setReloadTick(t => t + 1);
}
}
}, delay)
);
return () => checks.forEach(clearTimeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [symbol, timeframe]);
// ── 데이터 ────────────────────────────────────────────────────────────
const useUpbit = isUpbitMarket(symbol);
@@ -567,12 +554,26 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
const handleCandlesReady = useCallback(() => {
chartLiveReadyRef.current = true;
onPaintReadyChange?.(slotIndex, true);
const pending = pendingRealtimeBarRef.current;
const mgr = managerRef.current;
if (!pending || !mgr || barsMarketRef.current !== symbolRef.current) return;
pendingRealtimeBarRef.current = null;
mgr.updateBar(pending);
}, []);
}, [slotIndex, onPaintReadyChange]);
const handleSlotPaintReadyChange = useCallback((ready: boolean) => {
onPaintReadyChange?.(slotIndex, ready);
}, [slotIndex, onPaintReadyChange]);
useLayoutEffect(() => {
chartLiveReadyRef.current = false;
pendingRealtimeBarRef.current = null;
pendingSyncVisibleRef.current = null;
pendingSyncLogicalRef.current = null;
skipSyncApplyRef.current = true;
onPaintReadyChange?.(slotIndex, false);
}, [symbol, timeframe, slotIndex, onPaintReadyChange]);
const handleTickUpdate = useCallback((bar: OHLCVBar) => {
if (barsMarketRef.current !== symbolRef.current) return;
@@ -604,15 +605,18 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
useMemo(() => ({
onTickUpdate: handleTickUpdate,
onNewCandle: handleNewCandle,
enabled: useUpbit && chartVisible,
enabled: useUpbit && chartVisible && chartConfigReady,
source: chartRealtimeSource,
}), [handleTickUpdate, handleNewCandle, useUpbit, chartVisible, chartRealtimeSource]),
}), [handleTickUpdate, handleNewCandle, useUpbit, chartVisible, chartConfigReady, chartRealtimeSource]),
);
const bars = useUpbit ? upbitBars : simBars;
const barsMarket = useUpbit ? upbitBarsMarket : symbol;
barsMarketRef.current = barsMarket;
const slotDataFetched = !useUpbit || (!isLoading && bars.length >= 2 && barsMarket === symbol);
const slotChartLoading = useUpbit && chartVisible && (!chartConfigReady || !slotDataFetched);
const lastKnownBar = useUpbit
? (latestBar ?? (bars.length > 0 ? bars[bars.length - 1] : null))
: (bars.length > 0 ? bars[bars.length - 1] : null);
@@ -966,15 +970,6 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
className={compactMode ? 'vtd-card-chart-canvas slot-chart-wrap slot-chart-wrap--compact' : 'slot-chart-wrap'}
style={{ flex: 1, minHeight: 0, position: 'relative', overflow: 'hidden', display: 'flex', flexDirection: 'column' }}
>
{useUpbit && isLoading && (
compactMode ? (
<div className="vtd-card-chart-loading"> </div>
) : (
<div className="chart-loading" style={{ position: 'absolute', zIndex: 10 }}>
<div className="loading-spinner" />
</div>
)
)}
{isLoadingMore && (
<div className="chart-history-loading">
<div className="loading-spinner" style={{ width: 14, height: 14 }} />
@@ -983,8 +978,9 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
)}
<TradingChart
key={reloadTick}
chartVisible={chartVisible}
dataLoading={slotChartLoading}
onChartPaintReadyChange={handleSlotPaintReadyChange}
bars={bars}
barsMarket={barsMarket}
market={symbol}