매매 시그널 화면 추가 수정

This commit is contained in:
Macbook
2026-05-29 01:32:18 +09:00
parent 19ae5f5fae
commit 094a285363
5 changed files with 407 additions and 153 deletions
@@ -37,6 +37,8 @@ const TradeNotificationHScrollPane: React.FC<TradeNotificationHScrollPaneProps>
const syncScrollState = useCallback(() => {
const el = viewportRef.current;
if (!el) return;
const pageWidth = Math.max(160, el.clientWidth - 20);
el.style.setProperty('--tnl-hscroll-page-width', `${pageWidth}px`);
const max = Math.max(0, el.scrollWidth - el.clientWidth);
const overflow = max > 2;
setCanScroll(overflow);
@@ -61,7 +63,8 @@ const TradeNotificationHScrollPane: React.FC<TradeNotificationHScrollPaneProps>
const scrollByStep = useCallback((dir: -1 | 1) => {
const el = viewportRef.current;
if (!el) return;
const step = Math.max(120, Math.round(el.clientWidth * SCROLL_STEP_RATIO));
const pageWidth = Math.max(120, el.clientWidth - 20);
const step = Math.max(120, Math.round(pageWidth * SCROLL_STEP_RATIO));
el.scrollBy({ left: dir * step, behavior: 'smooth' });
}, []);
@@ -1,5 +1,5 @@
/**
* 매매 시그널 알림 목록 행 — 좌: 요약 카드(고정) · 우: 캔들+지표 카드(가로 스크롤)
* 매매 시그널 알림 목록 행 — 4칸이 목록 너비를 꽉 채움, 보조지표 2개↑ 시 우측 슬롯만 스크롤
*/
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { Theme } from '../../types';
@@ -174,28 +174,10 @@ const TradeNotificationListRow: React.FC<Props> = ({
]);
const receiving = useLiveReceiveFlash(receiveSignal, liveReceiveEnabled);
/** 차트 영역 카드 수 — 알림상세(1) : 차트카드(N) 동일 비율 배분용 */
const chartCardCount = useMemo(() => {
if (!isListLayout) return 0;
if (chartExpanded) return 1;
let n = 1;
if (signalActive) n += 1;
n += indicatorCards.length;
if (item.strategyId != null && indicatorCards.length === 0 && chartActive) n += 1;
return n;
}, [
isListLayout,
chartExpanded,
signalActive,
indicatorCards.length,
item.strategyId,
chartActive,
]);
const rowGalleryStyle = useMemo(
() => ({ '--tnl-chart-card-count': String(chartCardCount) }) as React.CSSProperties,
[chartCardCount],
);
const indicatorChartCount = indicatorCards.length;
/** 보조지표 2개 이상이면 첫 1개만 보이고 나머지는 좌우 스크롤 */
const indicatorScrollNeeded = indicatorChartCount >= 2;
const indicatorPlaceholder = item.strategyId != null && indicatorChartCount === 0 && chartActive;
const handleRowClick = useCallback((e: React.MouseEvent) => {
const t = e.target as HTMLElement;
@@ -324,7 +306,6 @@ const TradeNotificationListRow: React.FC<Props> = ({
>
<div
className={['tnl-row-gallery', chartExpanded ? 'tnl-row-gallery--chart-expanded' : ''].filter(Boolean).join(' ')}
style={rowGalleryStyle}
>
<TradeNotificationHScrollPane
className="tnl-row-gallery-detail"
@@ -333,11 +314,11 @@ const TradeNotificationListRow: React.FC<Props> = ({
{summaryCard}
</TradeNotificationHScrollPane>
<TradeNotificationHScrollPane
className={['tnl-row-gallery-charts', chartExpanded ? 'tnl-row-gallery-charts--expanded' : ''].filter(Boolean).join(' ')}
label={chartExpanded ? '차트 전체보기' : '신호·캔들·지표'}
>
{chartExpanded ? (
{chartExpanded ? (
<TradeNotificationHScrollPane
className="tnl-row-gallery-indicators tnl-row-gallery-indicators--expanded"
label="차트 전체보기"
>
<div className="tnl-charts-expanded-inner">
{expandedChartKey === 'candle' && (
<TradeSignalChartCard
@@ -370,9 +351,14 @@ const TradeNotificationListRow: React.FC<Props> = ({
/>
)}
</div>
) : (
<div className="tnl-charts-track">
{signalActive && (
</TradeNotificationHScrollPane>
) : (
<>
{signalActive && (
<TradeNotificationHScrollPane
className="tnl-row-gallery-signal"
label="신호 일치율"
>
<TradeNotificationSignalCard
market={item.market}
strategyId={item.strategyId}
@@ -388,8 +374,10 @@ const TradeNotificationListRow: React.FC<Props> = ({
signalLoading={signalLoading}
onExpandCharts={() => setExpandedChartKey('candle')}
/>
)}
</TradeNotificationHScrollPane>
)}
<div className="tnl-row-gallery-candle" aria-label="캔들 차트">
<TradeSignalChartCard
label="캔들"
meta={candleKo}
@@ -403,32 +391,42 @@ const TradeNotificationListRow: React.FC<Props> = ({
onRestore={() => setExpandedChartKey(null)}
onRealtimeActivity={chartActive ? bumpChartActivity : undefined}
/>
{indicatorCards.map(card => (
<TradeSignalChartCard
key={card.id}
label={card.label}
meta={candleKo}
market={item.market}
timeframe={chartTf}
theme={theme}
indicators={card.config}
enabled={chartActive}
expanded={false}
onExpand={() => setExpandedChartKey(card.id)}
onRestore={() => setExpandedChartKey(null)}
onRealtimeActivity={chartActive ? bumpChartActivity : undefined}
/>
))}
{item.strategyId != null && indicatorCards.length === 0 && chartActive && (
<section className="tnl-chart-card tnl-chart-card--placeholder">
<p className="tnl-muted"> </p>
</section>
)}
</div>
)}
</TradeNotificationHScrollPane>
<TradeNotificationHScrollPane
className={[
'tnl-row-gallery-indicators',
indicatorScrollNeeded ? 'tnl-row-gallery-indicators--scroll' : '',
].filter(Boolean).join(' ')}
label="보조지표"
>
<div className="tnl-charts-track tnl-charts-track--indicators-only">
{indicatorCards.map(card => (
<TradeSignalChartCard
key={card.id}
label={card.label}
meta={candleKo}
market={item.market}
timeframe={chartTf}
theme={theme}
indicators={card.config}
enabled={chartActive}
expanded={false}
onExpand={() => setExpandedChartKey(card.id)}
onRestore={() => setExpandedChartKey(null)}
onRealtimeActivity={chartActive ? bumpChartActivity : undefined}
/>
))}
{indicatorPlaceholder && (
<section className="tnl-chart-card tnl-chart-card--placeholder">
<p className="tnl-muted"> </p>
</section>
)}
</div>
</TradeNotificationHScrollPane>
</>
)}
</div>
</li>
);