feat(stock): 미리보기 결과 세션 히스토리 + 결과 비교 컬럼
- useScreenerRun: 실행 시마다 previewHistory에 누적 (최대 10, 메모리만 — 새로고침 시 사라짐, DB 부하 없음). top_ticker/score 요약 포함. - RunHistoryList: '이번 세션 미리보기'와 '저장된 실행' 두 섹션으로 분리. 미리보기 항목은 클릭으로 결과 표 로드 + '비교' 버튼으로 비교 대상 지정. - ResultTable: compareWith prop으로 비교 모드. 순위Δ(▲▼NEW)·점수Δ 컬럼 추가, 이번엔 빠진 종목은 'OUT'으로 별도 섹션에 회색 표시. - 헤더에 'vs HH:MM:SS (통과 X)' 라벨로 비교 대상 명시.
This commit is contained in:
@@ -1,17 +1,92 @@
|
||||
export default function RunHistoryList({ runs, loading, onSelect, selectedId }) {
|
||||
if (loading) return <section className="screener-card"><p>로딩…</p></section>;
|
||||
function formatTime(iso) {
|
||||
if (!iso) return '-';
|
||||
const d = new Date(iso);
|
||||
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
export default function RunHistoryList({
|
||||
runs, loading, onSelect, selectedId,
|
||||
previewHistory = [], onSelectPreview, selectedPreviewId,
|
||||
onSetCompare, compareId,
|
||||
}) {
|
||||
const hasPreview = previewHistory.length > 0;
|
||||
|
||||
return (
|
||||
<section className="screener-card">
|
||||
<h3>최근 실행</h3>
|
||||
<ul style={{listStyle:'none', padding:0, margin:0, fontSize:13}}>
|
||||
{(runs || []).map((r) => (
|
||||
<li key={r.id} style={{padding:'6px 0', borderBottom:'1px solid #1f2937', cursor:'pointer',
|
||||
color: selectedId === r.id ? '#fbbf24' : '#e5e7eb'}}
|
||||
onClick={() => onSelect(r.id)}>
|
||||
{r.asof} · {r.mode}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p style={{ fontSize: 11, color: '#6b7280', marginTop: 0 }}>
|
||||
💡 클릭하면 결과 표에 로드. 우측 "비교"를 누르면 다른 실행과 함께 표시
|
||||
</p>
|
||||
|
||||
{hasPreview && (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ fontSize: 11, color: '#9ca3af', marginBottom: 4 }}>
|
||||
이번 세션 미리보기 (새로고침 시 사라짐)
|
||||
</div>
|
||||
<ul style={{ listStyle: 'none', padding: 0, margin: 0, fontSize: 12 }}>
|
||||
{previewHistory.map((p) => {
|
||||
const isSelected = selectedPreviewId === p.id;
|
||||
const isCompare = compareId === p.id;
|
||||
return (
|
||||
<li key={p.id} style={{
|
||||
padding: '6px 4px',
|
||||
borderBottom: '1px solid #1f2937',
|
||||
background: isSelected ? '#1f2937' : 'transparent',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
}}>
|
||||
<span
|
||||
onClick={() => onSelectPreview?.(p.id)}
|
||||
style={{ cursor: 'pointer', flex: 1, color: isSelected ? '#fbbf24' : '#e5e7eb' }}
|
||||
>
|
||||
{formatTime(p.timestamp)} · {p.mode}
|
||||
<br />
|
||||
<span style={{ fontSize: 10, color: '#9ca3af' }}>
|
||||
통과 {p.survivors_count ?? '-'} · Top1 {p.top_name ?? '-'}
|
||||
</span>
|
||||
</span>
|
||||
<button
|
||||
onClick={() => onSetCompare?.(isCompare ? null : p.id)}
|
||||
style={{
|
||||
padding: '2px 6px', fontSize: 10,
|
||||
background: isCompare ? '#fbbf24' : '#374151',
|
||||
color: isCompare ? '#0b0f17' : '#e5e7eb',
|
||||
border: 'none', borderRadius: 4, cursor: 'pointer',
|
||||
}}
|
||||
title="이 결과를 비교 대상으로 설정"
|
||||
>
|
||||
{isCompare ? '✓ 비교중' : '비교'}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<div style={{ fontSize: 11, color: '#9ca3af', marginBottom: 4 }}>
|
||||
저장된 실행 (자동 잡 + 스냅샷 저장)
|
||||
</div>
|
||||
{loading ? <p style={{ fontSize: 12 }}>로딩…</p> : (
|
||||
<ul style={{ listStyle: 'none', padding: 0, margin: 0, fontSize: 13 }}>
|
||||
{(runs || []).length === 0 && (
|
||||
<li style={{ fontSize: 11, color: '#6b7280' }}>저장된 실행 없음</li>
|
||||
)}
|
||||
{(runs || []).map((r) => (
|
||||
<li key={r.id} style={{
|
||||
padding: '6px 0', borderBottom: '1px solid #1f2937', cursor: 'pointer',
|
||||
color: selectedId === r.id ? '#fbbf24' : '#e5e7eb',
|
||||
}}
|
||||
onClick={() => onSelect?.(r.id)}>
|
||||
{r.asof} · {r.mode}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user