Files
goldenChart/frontend/src/components/analysisReport/TimelineAxisView.tsx
T
2026-06-10 16:53:03 +09:00

115 lines
4.1 KiB
TypeScript

/**
* 분석레포트 — 가로 봉 시간축 타임라인 (중앙 축 · 상단 매도 · 하단 매수)
*/
import React, { useMemo, useRef } from 'react';
import {
groupEventsByBarTime,
type LiveTimelineEvent,
} from '../../utils/liveTradeTimeline';
import TimelineBarTimeBox from './TimelineBarTimeBox';
import TimelineAxisEventCard from './TimelineAxisEventCard';
import { useHorizontalWheelScroll } from '../../hooks/useHorizontalWheelScroll';
import { useCenterAxisTimelineScroll } from '../../hooks/useCenterAxisTimelineScroll';
import type { Timeframe } from '../../types';
const SEGMENT_COLORS = ['c0', 'c1', 'c2', 'c3', 'c4'] as const;
interface Props {
events: LiveTimelineEvent[];
timeframe: Timeframe;
}
const TimelineAxisView: React.FC<Props> = ({ events, timeframe }) => {
const scrollRef = useRef<HTMLDivElement | null>(null);
const trackRef = useRef<HTMLDivElement | null>(null);
useHorizontalWheelScroll(scrollRef);
const slots = useMemo(
() => groupEventsByBarTime(events, timeframe),
[events, timeframe],
);
useCenterAxisTimelineScroll(scrollRef, trackRef, slots.length > 0, [slots.length, events.length]);
if (slots.length === 0) {
return <p className="arp-timeline-empty"> 시간 기준으로 표시할 거래 이력이 없습니다.</p>;
}
const trackMinWidth = Math.max(slots.length * 160, 320);
return (
<div
ref={scrollRef}
className="arp-timeline-axis-scroll arp-timeline-h-scroll"
aria-label="봉 시간축 거래 이력 타임라인"
>
<div className="arp-timeline-axis-scroll-inner">
<div
ref={trackRef}
className="arp-timeline-axis-track"
style={{ minWidth: trackMinWidth }}
>
<div className="arp-timeline-axis-zone arp-timeline-axis-zone--top">
<span className="arp-timeline-axis-zone-label">매도</span>
<div className="arp-timeline-axis-cols">
{slots.map(slot => (
<div key={`top-${slot.barTimeSec}`} className="arp-timeline-axis-col">
{slot.sells.map(ev => (
<div key={ev.id} className="arp-timeline-axis-col-stack">
<TimelineAxisEventCard ev={ev} side="top" />
<div
className="arp-timeline-axis-connector arp-timeline-axis-connector--down arp-timeline-axis-connector--sell"
aria-hidden
/>
</div>
))}
</div>
))}
</div>
</div>
<div className="arp-timeline-axis-rail" aria-hidden>
{slots.map((slot, i) => (
<div
key={slot.barTimeSec}
className={[
'arp-timeline-axis-segment',
`arp-timeline-axis-segment--${SEGMENT_COLORS[i % SEGMENT_COLORS.length]}`,
].join(' ')}
>
<TimelineBarTimeBox
barTimeSec={slot.barTimeSec}
timeframe={timeframe}
variant="segment"
showTimeframe={false}
/>
</div>
))}
</div>
<div className="arp-timeline-axis-zone arp-timeline-axis-zone--bottom">
<div className="arp-timeline-axis-cols">
{slots.map(slot => (
<div key={`bottom-${slot.barTimeSec}`} className="arp-timeline-axis-col">
{slot.buys.map(ev => (
<div key={ev.id} className="arp-timeline-axis-col-stack">
<div
className="arp-timeline-axis-connector arp-timeline-axis-connector--up arp-timeline-axis-connector--buy"
aria-hidden
/>
<TimelineAxisEventCard ev={ev} side="bottom" />
</div>
))}
</div>
))}
</div>
<span className="arp-timeline-axis-zone-label">매수</span>
</div>
</div>
</div>
</div>
);
};
export default TimelineAxisView;