feat(stock): /stock/screener 페이지 골격 + hooks 4개 + 컴포넌트 stub 6개

This commit is contained in:
2026-05-12 14:15:36 +09:00
parent cd6072727f
commit bc2c020f71
12 changed files with 284 additions and 5 deletions

View File

@@ -0,0 +1,3 @@
export default function GatePanel({ meta, value, onChange }) {
return <section className="screener-card"><h3>{meta?.label ?? '게이트'}</h3><p style={{fontSize: 12, color:'#9ca3af'}}>TODO: 게이트 파라미터 (Task 4.5)</p></section>;
}

View File

@@ -0,0 +1,10 @@
export default function GlobalControls({ settings, setSettings, onRun, onSave, onPersist, dirty, running }) {
return (
<section className="screener-card">
<h3>실행 옵션</h3>
<button onClick={onRun} disabled={running}>{running ? '실행 중…' : '지금 실행 (미리보기)'}</button>
<button onClick={onSave} disabled={running} style={{ marginTop: 8 }}>스냅샷 저장</button>
<button onClick={onPersist} disabled={!dirty} style={{ marginTop: 8 }}>설정 저장</button>
</section>
);
}

View File

@@ -0,0 +1,3 @@
export default function NodePanel({ meta, weights, params, onWeights, onParams }) {
return <section className="screener-card"><h3>점수 노드 ({meta.length})</h3><p style={{fontSize: 12, color:'#9ca3af'}}>TODO: 노드별 카드 (Task 4.4)</p></section>;
}

View File

@@ -0,0 +1,25 @@
export default function ResultTable({ result }) {
if (!result) return <section className="screener-card"><p style={{color:'#9ca3af'}}>아직 결과 없음. "지금 실행" 눌러보세요.</p></section>;
return (
<section className="screener-card">
<h3>Top {result.top_n} · 통과 {result.survivors_count}</h3>
<table style={{ width: '100%', fontSize: 13 }}>
<thead>
<tr><th>#</th><th>종목</th><th>총점</th><th>진입</th><th>손절</th><th>익절</th><th>R%</th></tr>
</thead>
<tbody>
{(result.results || []).map((r) => (
<tr key={r.ticker}>
<td>{r.rank}</td><td>{r.name} ({r.ticker})</td>
<td>{r.total_score?.toFixed?.(1)}</td>
<td>{r.entry_price?.toLocaleString?.()}</td>
<td>{r.stop_price?.toLocaleString?.()}</td>
<td>{r.target_price?.toLocaleString?.()}</td>
<td>{r.r_pct?.toFixed?.(1)}</td>
</tr>
))}
</tbody>
</table>
</section>
);
}

View File

@@ -0,0 +1,17 @@
export default function RunHistoryList({ runs, loading, onSelect, selectedId }) {
if (loading) return <section className="screener-card"><p>로딩</p></section>;
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>
</section>
);
}

View File

@@ -0,0 +1,9 @@
export default function TelegramPreview({ payload }) {
if (!payload) return null;
return (
<section className="screener-card">
<h3>텔레그램 미리보기</h3>
<pre style={{whiteSpace:'pre-wrap', fontFamily:'monospace', fontSize:12}}>{payload.text}</pre>
</section>
);
}