💡 컬럼 헤더와 노드 칩에 마우스를 올리면 의미가 표시됩니다.
+ {hasCompare && ' · 비교 모드 ON — ▲▼NEW/OUT으로 변화 표시'}
@@ -59,6 +111,8 @@ export default function ResultTable({ result }) {
# |
종목 |
총점 |
+ {hasCompare && 순위Δ | }
+ {hasCompare && 점수Δ | }
노드 |
진입(원) |
손절(원) |
@@ -67,18 +121,41 @@ export default function ResultTable({ result }) {
- {(result.results || []).map((r) => (
-
- | {r.rank} |
- {r.name} {r.ticker} |
- {r.total_score?.toFixed(1)} |
- |
- {r.entry_price?.toLocaleString?.()} |
- {r.stop_price?.toLocaleString?.()} |
- {r.target_price?.toLocaleString?.()} |
- {r.r_pct?.toFixed?.(1)} |
-
- ))}
+ {(result.results || []).map((r) => {
+ const prev = cmpIdx?.get(r.ticker);
+ return (
+
+ | {r.rank} |
+ {r.name} {r.ticker} |
+ {r.total_score?.toFixed(1)} |
+ {hasCompare && | }
+ {hasCompare && | }
+ |
+ {r.entry_price?.toLocaleString?.()} |
+ {r.stop_price?.toLocaleString?.()} |
+ {r.target_price?.toLocaleString?.()} |
+ {r.r_pct?.toFixed?.(1)} |
+
+ );
+ })}
+ {hasCompare && onlyInCompare.length > 0 && (
+ <>
+ |
+ ── 이번엔 빠진 종목 (비교 대상에만 존재) ──
+ |
+ {onlyInCompare.map((r) => (
+
+ | — |
+ {r.name} {r.ticker} |
+ {r.total_score?.toFixed(1)} |
+ OUT |
+ — |
+ |
+ — |
+
+ ))}
+ >
+ )}
diff --git a/src/pages/stock/screener/components/RunHistoryList.jsx b/src/pages/stock/screener/components/RunHistoryList.jsx
index d1b2ee9..e058371 100644
--- a/src/pages/stock/screener/components/RunHistoryList.jsx
+++ b/src/pages/stock/screener/components/RunHistoryList.jsx
@@ -1,17 +1,92 @@
-export default function RunHistoryList({ runs, loading, onSelect, selectedId }) {
- if (loading) return
;
+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 (
최근 실행
-
- {(runs || []).map((r) => (
- - onSelect(r.id)}>
- {r.asof} · {r.mode}
-
- ))}
-
+
+ 💡 클릭하면 결과 표에 로드. 우측 "비교"를 누르면 다른 실행과 함께 표시
+
+
+ {hasPreview && (
+
+
+ 이번 세션 미리보기 (새로고침 시 사라짐)
+
+
+ {previewHistory.map((p) => {
+ const isSelected = selectedPreviewId === p.id;
+ const isCompare = compareId === p.id;
+ return (
+ -
+ onSelectPreview?.(p.id)}
+ style={{ cursor: 'pointer', flex: 1, color: isSelected ? '#fbbf24' : '#e5e7eb' }}
+ >
+ {formatTime(p.timestamp)} · {p.mode}
+
+
+ 통과 {p.survivors_count ?? '-'} · Top1 {p.top_name ?? '-'}
+
+
+
+
+ );
+ })}
+
+
+ )}
+
+
+
+ 저장된 실행 (자동 잡 + 스냅샷 저장)
+
+ {loading ?
로딩…
: (
+
+ {(runs || []).length === 0 && (
+ - 저장된 실행 없음
+ )}
+ {(runs || []).map((r) => (
+ - onSelect?.(r.id)}>
+ {r.asof} · {r.mode}
+
+ ))}
+
+ )}
+
);
}
diff --git a/src/pages/stock/screener/hooks/useScreenerRun.js b/src/pages/stock/screener/hooks/useScreenerRun.js
index ac4e4ab..c44ad1f 100644
--- a/src/pages/stock/screener/hooks/useScreenerRun.js
+++ b/src/pages/stock/screener/hooks/useScreenerRun.js
@@ -1,9 +1,13 @@
import { useState } from 'react';
import { runScreener } from '../../../../api';
+const MAX_PREVIEW_HISTORY = 10;
+
export function useScreenerRun() {
const [result, setResult] = useState(null);
const [running, setRunning] = useState(false);
+ // 미리보기 결과를 세션 메모리에 누적 (새로고침 시 사라짐 — DB 부하 없음)
+ const [previewHistory, setPreviewHistory] = useState([]);
async function call(mode, settings) {
setRunning(true);
@@ -17,15 +21,34 @@ export function useScreenerRun() {
};
const r = await runScreener(body);
setResult(r);
+ const stamp = new Date().toISOString();
+ const item = {
+ id: `${mode}-${stamp}`,
+ mode,
+ timestamp: stamp,
+ asof: r?.asof,
+ survivors_count: r?.survivors_count,
+ top_ticker: r?.results?.[0]?.ticker,
+ top_name: r?.results?.[0]?.name,
+ top_score: r?.results?.[0]?.total_score,
+ result: r,
+ };
+ setPreviewHistory((prev) => [item, ...prev].slice(0, MAX_PREVIEW_HISTORY));
return r;
} finally {
setRunning(false);
}
}
+ function selectPreview(id) {
+ const item = previewHistory.find((p) => p.id === id);
+ if (item) setResult(item.result);
+ }
+
return {
- result, running,
+ result, running, previewHistory,
runPreview: (s) => call('preview', s),
runSave: (s) => call('manual_save', s),
+ selectPreview,
};
}