알림팝업 전체닫기 오류 수정

This commit is contained in:
Macbook
2026-05-23 21:25:46 +09:00
parent 70ac67afe7
commit 4df8279a31
11 changed files with 290 additions and 170 deletions
+4 -1
View File
@@ -17,6 +17,7 @@ import type { TradeOrderFillRequest } from '../types';
import PaperMiniChart from './paper/PaperMiniChart';
import PaperIndicatorPanel from './paper/PaperIndicatorPanel';
import PaperCompactOrderbook from './paper/PaperCompactOrderbook';
import type { Theme } from '../types';
const COIN_COLORS: Record<string, string> = {
BTC: '#f7931a', ETH: '#627eea', XRP: '#23292f', SOL: '#9945ff',
@@ -28,6 +29,7 @@ type HistoryTab = 'open' | 'recent';
type RightTab = 'trade' | 'orderbook';
interface Props {
theme?: Theme;
tickers?: Map<string, { tradePrice: number | null }>;
onGoChart?: (market: string) => void;
refreshKey?: number;
@@ -66,6 +68,7 @@ function coinIcon(code: string): string {
}
const PaperTradingPage: React.FC<Props> = ({
theme = 'dark',
tickers,
refreshKey = 0,
defaultMarket = 'KRW-BTC',
@@ -264,7 +267,7 @@ const PaperTradingPage: React.FC<Props> = ({
<main className="ptd-col ptd-col--center">
<div className="ptd-card ptd-chart-card">
<PaperMiniChart market={selectedMarket} />
<PaperMiniChart market={selectedMarket} theme={theme} />
<PaperIndicatorPanel market={selectedMarket} />
</div>
</main>
@@ -93,8 +93,17 @@ export const TradeSignalSnackbar: React.FC<Props> = ({
setPageIndex(p => Math.min(totalPages - 1, p + 1));
}, [totalPages]);
const dismissAll = useCallback(() => {
dismissAllToasts();
setPageIndex(0);
positionsRef.current = {};
}, [dismissAllToasts]);
const dismissCurrentPage = useCallback(() => {
dismissToasts(pageItems.map(i => i.id));
positionsRef.current = Object.fromEntries(
Object.entries(positionsRef.current).filter(([id]) => !pageItems.some(p => p.id === id)),
);
}, [dismissToasts, pageItems]);
const handleDragStart = useCallback((e: React.PointerEvent, key: string) => {
@@ -219,7 +228,7 @@ export const TradeSignalSnackbar: React.FC<Props> = ({
<button
type="button"
className="tsn-dismiss-all"
onClick={dismissAllToasts}
onClick={dismissAll}
title={`대기 중인 알림 ${toastNotifications.length}건 전체 닫기`}
>
({toastNotifications.length})
@@ -99,10 +99,10 @@ const BacktestAssetChart: React.FC<Props> = memo(({ curve, markers }) => {
{[0.25, 0.5, 0.75].map(r => {
const y = pad.t + innerH * (1 - r);
return <line key={r} x1={pad.l} y1={y} x2={W - pad.r} y2={y} stroke="rgba(201,162,39,0.07)" strokeWidth="1" />;
return <line key={r} x1={pad.l} y1={y} x2={W - pad.r} y2={y} className="btd-chart-grid" />;
})}
<text x={pad.l - 8} y={pad.t - 6} textAnchor="start" fill="#6272a4" fontSize="8"></text>
<text x={pad.l - 8} y={pad.t - 6} textAnchor="start" className="btd-chart-label" fontSize="8"></text>
{chart.yTicks.map(v => (
<text
@@ -111,7 +111,7 @@ const BacktestAssetChart: React.FC<Props> = memo(({ curve, markers }) => {
y={chart.toY(v)}
textAnchor="end"
dominantBaseline="middle"
fill="#6272a4"
className="btd-chart-label"
fontSize="8.5"
>
{fmtY(v)}
@@ -124,7 +124,7 @@ const BacktestAssetChart: React.FC<Props> = memo(({ curve, markers }) => {
key={i}
d={seg.d}
fill="none"
stroke={seg.up ? '#22c55e' : '#ef4444'}
className={seg.up ? 'btd-chart-line-up' : 'btd-chart-line-down'}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
@@ -152,7 +152,7 @@ const BacktestAssetChart: React.FC<Props> = memo(({ curve, markers }) => {
))}
{chart.xLabels.map((l, i) => (
<text key={i} x={l.x} y={H - 5} textAnchor="middle" fill="#6272a4" fontSize="8">
<text key={i} x={l.x} y={H - 5} textAnchor="middle" className="btd-chart-label" fontSize="8">
{l.label}
</text>
))}
@@ -1,44 +1,84 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { createChart, CandlestickSeries, type IChartApi, type ISeriesApi, type Time, ColorType } from 'lightweight-charts';
import type { Timeframe } from '../../types';
import type { Theme, Timeframe } from '../../types';
import { fetchUpbitCandles } from '../../utils/upbitApi';
const TF_OPTIONS: Timeframe[] = ['1m', '5m', '15m', '1h', '4h', '1D'];
interface Props {
market: string;
theme?: Theme;
wsLabel?: string;
}
const PaperMiniChart: React.FC<Props> = ({ market, wsLabel = 'WebSocket <100ms' }) => {
function readChartTheme(container: HTMLElement | null) {
const root = container?.closest('.app') ?? document.documentElement;
const s = getComputedStyle(root);
const v = (name: string, fallback: string) => s.getPropertyValue(name).trim() || fallback;
return {
bgColor: v('--bg3', '#1f2335'),
textColor: v('--text', '#c0caf5'),
gridColor: v('--sep', 'rgba(122, 162, 247, 0.15)'),
borderColor: v('--border', 'rgba(122, 162, 247, 0.15)'),
upColor: v('--up', '#ff6b6b'),
downColor: v('--down', '#4dabf7'),
};
}
const PaperMiniChart: React.FC<Props> = ({ market, theme = 'dark', wsLabel = 'WebSocket <100ms' }) => {
const containerRef = useRef<HTMLDivElement>(null);
const chartRef = useRef<IChartApi | null>(null);
const seriesRef = useRef<ISeriesApi<'Candlestick'> | null>(null);
const [timeframe, setTimeframe] = useState<Timeframe>('1h');
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!containerRef.current) return undefined;
const chart = createChart(containerRef.current, {
const applyTheme = useCallback((chart: IChartApi, series: ISeriesApi<'Candlestick'>) => {
const t = readChartTheme(containerRef.current);
chart.applyOptions({
layout: {
background: { type: ColorType.Solid, color: '#121520' },
textColor: '#9aa5ce',
background: { type: ColorType.Solid, color: t.bgColor },
textColor: t.textColor,
},
grid: {
vertLines: { color: 'rgba(122,162,247,0.06)' },
horzLines: { color: 'rgba(122,162,247,0.06)' },
vertLines: { color: t.gridColor },
horzLines: { color: t.gridColor },
},
rightPriceScale: { borderColor: 'rgba(122,162,247,0.12)' },
timeScale: { borderColor: 'rgba(122,162,247,0.12)' },
rightPriceScale: { borderColor: t.borderColor },
timeScale: { borderColor: t.borderColor },
});
series.applyOptions({
upColor: t.upColor,
downColor: t.downColor,
borderUpColor: t.upColor,
borderDownColor: t.downColor,
wickUpColor: t.upColor,
wickDownColor: t.downColor,
});
}, []);
useEffect(() => {
if (!containerRef.current) return undefined;
const t = readChartTheme(containerRef.current);
const chart = createChart(containerRef.current, {
layout: {
background: { type: ColorType.Solid, color: t.bgColor },
textColor: t.textColor,
},
grid: {
vertLines: { color: t.gridColor },
horzLines: { color: t.gridColor },
},
rightPriceScale: { borderColor: t.borderColor },
timeScale: { borderColor: t.borderColor },
crosshair: { mode: 1 },
});
const series = chart.addSeries(CandlestickSeries, {
upColor: '#22c55e',
downColor: '#ef4444',
borderUpColor: '#22c55e',
borderDownColor: '#ef4444',
wickUpColor: '#22c55e',
wickDownColor: '#ef4444',
upColor: t.upColor,
downColor: t.downColor,
borderUpColor: t.upColor,
borderDownColor: t.downColor,
wickUpColor: t.upColor,
wickDownColor: t.downColor,
});
chartRef.current = chart;
seriesRef.current = series;
@@ -61,6 +101,12 @@ const PaperMiniChart: React.FC<Props> = ({ market, wsLabel = 'WebSocket <100ms'
};
}, []);
useEffect(() => {
if (chartRef.current && seriesRef.current) {
applyTheme(chartRef.current, seriesRef.current);
}
}, [theme, applyTheme]);
useEffect(() => {
let cancelled = false;
setLoading(true);