136 lines
4.6 KiB
TypeScript
136 lines
4.6 KiB
TypeScript
/**
|
|
* 매매 시그널 알림 — 차트 전체화면 모달
|
|
* 알림 아이템에 이미 표시된 캔들+보조지표 차트를 전체 화면으로 확대 표시
|
|
*/
|
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
import type { Theme } from '../../types';
|
|
import type { TradeNotificationItem } from '../../contexts/TradeNotificationContext';
|
|
import type { StrategyDto } from '../../utils/backendApi';
|
|
import { loadStrategy } from '../../utils/backendApi';
|
|
import { buildStrategyChartIndicators, candleTypeToTimeframe } from '../../utils/strategyToChartIndicators';
|
|
import { useIndicatorSettings } from '../../hooks/useIndicatorSettings';
|
|
import { formatStrategyCandleLabel } from '../../utils/strategyStartNodes';
|
|
import TradeSignalMiniChart from './TradeSignalMiniChart';
|
|
import { TRADE_BUY_COLOR, TRADE_SELL_COLOR } from '../../utils/tradeSignalColors';
|
|
import { formatSignalTime } from '../../utils/tradeSignalDisplay';
|
|
import { formatUpbitKrwPrice } from '../../utils/safeFormat';
|
|
|
|
const MAX_INDICATOR_CARDS = 8;
|
|
|
|
interface Props {
|
|
item: TradeNotificationItem;
|
|
theme: Theme;
|
|
onClose: () => void;
|
|
}
|
|
|
|
const FullscreenChartModal: React.FC<Props> = ({ item, theme, onClose }) => {
|
|
const { getParams, getVisualConfig } = useIndicatorSettings();
|
|
const [strategy, setStrategy] = useState<StrategyDto | null>(null);
|
|
|
|
/* 전략 로딩 */
|
|
useEffect(() => {
|
|
if (!item.strategyId) return;
|
|
loadStrategy(item.strategyId)
|
|
.then(s => setStrategy(s))
|
|
.catch(() => {});
|
|
}, [item.strategyId]);
|
|
|
|
/* 보조지표 config */
|
|
const allIndicatorConfigs = useMemo(() => {
|
|
if (!strategy) return [];
|
|
const all = buildStrategyChartIndicators(strategy, getParams, getVisualConfig);
|
|
return all.slice(0, MAX_INDICATOR_CARDS);
|
|
}, [strategy, getParams, getVisualConfig]);
|
|
|
|
const timeframe = candleTypeToTimeframe(item.candleType ?? '1m');
|
|
const isBuy = item.signalType === 'BUY';
|
|
const accentColor = isBuy ? TRADE_BUY_COLOR : TRADE_SELL_COLOR;
|
|
const sym = item.market.replace(/^KRW-/, '');
|
|
const tfLabel = formatStrategyCandleLabel(item.candleType ?? '1m');
|
|
|
|
/* ESC 키로 닫기 */
|
|
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') onClose();
|
|
}, [onClose]);
|
|
|
|
useEffect(() => {
|
|
document.addEventListener('keydown', handleKeyDown);
|
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
}, [handleKeyDown]);
|
|
|
|
/* body 스크롤 막기 */
|
|
useEffect(() => {
|
|
const prev = document.body.style.overflow;
|
|
document.body.style.overflow = 'hidden';
|
|
return () => { document.body.style.overflow = prev; };
|
|
}, []);
|
|
|
|
return (
|
|
<div
|
|
className="tnl-chart-fs-overlay"
|
|
onClick={onClose}
|
|
role="dialog"
|
|
aria-modal
|
|
aria-label="차트 전체화면"
|
|
>
|
|
<div
|
|
className="tnl-chart-fs-inner"
|
|
onClick={e => e.stopPropagation()}
|
|
>
|
|
{/* 헤더 */}
|
|
<div className="tnl-chart-fs-header">
|
|
<div className="tnl-chart-fs-title">
|
|
<span
|
|
className={`tnl-chart-fs-signal-badge${isBuy ? ' tnl-chart-fs-signal-badge--buy' : ' tnl-chart-fs-signal-badge--sell'}`}
|
|
style={{ background: accentColor }}
|
|
>
|
|
{item.signalType}
|
|
</span>
|
|
<span className="tnl-chart-fs-market">{sym}</span>
|
|
<span className="tnl-chart-fs-tf">{tfLabel}</span>
|
|
<span className="tnl-chart-fs-price" style={{ color: accentColor }}>
|
|
₩{formatUpbitKrwPrice(item.price)}
|
|
</span>
|
|
<span className="tnl-chart-fs-time">
|
|
{formatSignalTime(item.candleTime)}
|
|
</span>
|
|
</div>
|
|
<div className="tnl-chart-fs-actions">
|
|
{item.strategyName && (
|
|
<span className="tnl-chart-fs-strategy">{item.strategyName}</span>
|
|
)}
|
|
<button
|
|
type="button"
|
|
className="tnl-chart-fs-close"
|
|
onClick={onClose}
|
|
aria-label="닫기 (ESC)"
|
|
title="닫기 (ESC)"
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 차트 바디 */}
|
|
<div className="tnl-chart-fs-body">
|
|
<TradeSignalMiniChart
|
|
market={item.market}
|
|
timeframe={timeframe}
|
|
theme={theme}
|
|
indicators={allIndicatorConfigs}
|
|
enabled
|
|
fillHeight
|
|
signalMarker={{
|
|
candleTime: item.candleTime,
|
|
signalType: item.signalType,
|
|
price: item.price,
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default FullscreenChartModal;
|