전략평가 화면 수정

This commit is contained in:
Macbook
2026-06-12 15:22:48 +09:00
parent 7579ed9de4
commit 8aae8489ae
8 changed files with 298 additions and 152 deletions
@@ -0,0 +1,92 @@
import React, { useMemo } from 'react';
import { useSignalVisualRailHeight } from '../../hooks/useSignalVisualRailHeight';
import {
buildConditionMetrics,
computeMatchRate,
getTrafficLightState,
type ConditionMetric,
} from '../../utils/virtualSignalMetrics';
import VirtualSignalEqualizer from './VirtualSignalEqualizer';
import VirtualSignalTrafficLight from './VirtualSignalTrafficLight';
import VirtualConditionList from './VirtualConditionList';
interface Props {
metrics: ConditionMetric[];
title?: React.ReactNode;
/** 백엔드 집계 matchRate — 미지정 시 metrics 로컬 계산 */
backendMatchRate?: number | null;
className?: string;
panelClassName?: string;
}
const VirtualSignalMatchVisual: React.FC<Props> = ({
metrics,
title,
backendMatchRate,
className = '',
panelClassName = '',
}) => {
const matchRate = useMemo(
() => computeMatchRate(metrics, backendMatchRate),
[metrics, backendMatchRate],
);
const trafficState = useMemo(
() => getTrafficLightState(matchRate, metrics),
[matchRate, metrics],
);
const { visualRef, lightRef } = useSignalVisualRailHeight([
metrics.length,
matchRate,
trafficState,
title,
]);
return (
<div className={['vtd-sig-panel', panelClassName || 'vtd-sig-panel--summary'].filter(Boolean).join(' ')}>
{title != null && title !== '' && (
<div className="vtd-sig-panel-title">{title}</div>
)}
<div className={['vtd-sig-visual', className].filter(Boolean).join(' ')} ref={visualRef}>
<div className="vtd-sig-visual-pane vtd-sig-visual-eq">
<VirtualSignalEqualizer matchRate={matchRate} />
</div>
<div className="vtd-sig-visual-pane vtd-sig-visual-heat">
<VirtualConditionList metrics={metrics} />
</div>
<div className="vtd-sig-visual-pane vtd-sig-visual-light">
<div ref={lightRef} className="vtd-sig-light-rail">
<VirtualSignalTrafficLight state={trafficState} matchRate={matchRate} />
</div>
</div>
</div>
</div>
);
};
/** rows → metrics 변환 후 시각 블록 렌더 (편의 래퍼) */
export function VirtualSignalMatchVisualFromRows({
rows,
title,
backendMatchRate,
className,
panelClassName,
}: {
rows: Parameters<typeof buildConditionMetrics>[0];
title?: React.ReactNode;
backendMatchRate?: number | null;
className?: string;
panelClassName?: string;
}) {
const metrics = useMemo(() => buildConditionMetrics(rows), [rows]);
return (
<VirtualSignalMatchVisual
metrics={metrics}
title={title}
backendMatchRate={backendMatchRate}
className={className}
panelClassName={panelClassName}
/>
);
}
export default VirtualSignalMatchVisual;
@@ -1,14 +1,7 @@
import React, { useMemo } from 'react';
import { useSignalVisualRailHeight } from '../../hooks/useSignalVisualRailHeight';
import type { VirtualIndicatorSnapshot } from '../../hooks/useVirtualIndicatorSnapshots';
import {
buildConditionMetrics,
computeMatchRate,
getTrafficLightState,
} from '../../utils/virtualSignalMetrics';
import VirtualSignalEqualizer from './VirtualSignalEqualizer';
import VirtualSignalTrafficLight from './VirtualSignalTrafficLight';
import VirtualConditionList from './VirtualConditionList';
import { buildConditionMetrics } from '../../utils/virtualSignalMetrics';
import VirtualSignalMatchVisual from './VirtualSignalMatchVisual';
import VirtualIndicatorCompareTable from './VirtualIndicatorCompareTable';
import type { VirtualCardViewMode } from '../../utils/virtualTradingStorage';
@@ -38,17 +31,6 @@ const VirtualTargetSignalPanel: React.FC<Props> = ({
const isDetail = viewMode === 'detail';
const metrics = useMemo(() => buildConditionMetrics(rows), [rows]);
const matchRate = useMemo(
() => computeMatchRate(metrics, snapshot?.matchRate),
[metrics, snapshot?.matchRate],
);
const trafficState = useMemo(() => getTrafficLightState(matchRate, metrics), [matchRate, metrics]);
const { visualRef, lightRef } = useSignalVisualRailHeight([
rows.length,
matchRate,
trafficState,
viewMode,
]);
if (rows.length === 0) {
return (
@@ -71,22 +53,12 @@ const VirtualTargetSignalPanel: React.FC<Props> = ({
className,
].filter(Boolean).join(' ')}
>
<div className={`vtd-sig-panel${isDetail ? ' vtd-sig-panel--detail-top' : ' vtd-sig-panel--summary'}`}>
<div className="vtd-sig-panel-title">SIGNAL INTELLIGENCE &amp; MATCH RATES</div>
<div className="vtd-sig-visual" ref={visualRef}>
<div className="vtd-sig-visual-pane vtd-sig-visual-eq">
<VirtualSignalEqualizer matchRate={matchRate} />
</div>
<div className="vtd-sig-visual-pane vtd-sig-visual-heat">
<VirtualConditionList metrics={metrics} />
</div>
<div className="vtd-sig-visual-pane vtd-sig-visual-light">
<div ref={lightRef} className="vtd-sig-light-rail">
<VirtualSignalTrafficLight state={trafficState} matchRate={matchRate} />
</div>
</div>
</div>
</div>
<VirtualSignalMatchVisual
metrics={metrics}
title="SIGNAL INTELLIGENCE & MATCH RATES"
backendMatchRate={snapshot?.matchRate}
panelClassName={isDetail ? 'vtd-sig-panel--detail-top' : 'vtd-sig-panel--summary'}
/>
{isDetail && (
<div className="vtd-sig-detail-table" aria-label="지표별 상태 목록">
<VirtualIndicatorCompareTable metrics={metrics} />