주식 주요 지수 가져오기 추가
This commit is contained in:
@@ -69,3 +69,7 @@ export function triggerStockScrap() {
|
||||
export function getStockHealth() {
|
||||
return apiGet("/api/stock/health");
|
||||
}
|
||||
|
||||
export function getStockIndices() {
|
||||
return apiGet("/api/stock/indices");
|
||||
}
|
||||
|
||||
@@ -176,6 +176,12 @@
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.stock-snapshot__card.is-highlight {
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.stock-snapshot__card p {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
@@ -193,6 +199,19 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.stock-snapshot__change {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.stock-snapshot__change.is-up {
|
||||
color: #f3a7a7;
|
||||
}
|
||||
|
||||
.stock-snapshot__change.is-down {
|
||||
color: #9fc5ff;
|
||||
}
|
||||
|
||||
.stock-schedule {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
getStockHealth,
|
||||
getStockIndices,
|
||||
getStockNews,
|
||||
triggerStockScrap,
|
||||
} from '../../api';
|
||||
@@ -34,6 +35,10 @@ const Stock = () => {
|
||||
status: 'unknown',
|
||||
message: '',
|
||||
});
|
||||
const [indices, setIndices] = useState([]);
|
||||
const [indicesLoading, setIndicesLoading] = useState(false);
|
||||
const [indicesAt, setIndicesAt] = useState('');
|
||||
const [autoRefreshMs] = useState(180000);
|
||||
|
||||
const latestCrawled = useMemo(
|
||||
() => getLatestBy(news, 'crawled_at'),
|
||||
@@ -72,6 +77,20 @@ const Stock = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const loadIndices = async () => {
|
||||
setIndicesLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const data = await getStockIndices();
|
||||
setIndices(Array.isArray(data?.indices) ? data.indices : []);
|
||||
setIndicesAt(data?.crawled_at ?? '');
|
||||
} catch (err) {
|
||||
setError(err?.message ?? String(err));
|
||||
} finally {
|
||||
setIndicesLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onScrap = async () => {
|
||||
setScraping(true);
|
||||
setError('');
|
||||
@@ -94,7 +113,12 @@ const Stock = () => {
|
||||
|
||||
useEffect(() => {
|
||||
loadHealth();
|
||||
}, []);
|
||||
loadIndices();
|
||||
const timer = window.setInterval(() => {
|
||||
loadIndices();
|
||||
}, autoRefreshMs);
|
||||
return () => window.clearInterval(timer);
|
||||
}, [autoRefreshMs]);
|
||||
|
||||
return (
|
||||
<div className="stock">
|
||||
@@ -160,18 +184,71 @@ const Stock = () => {
|
||||
<p className="stock-panel__eyebrow">Snapshot</p>
|
||||
<h3>시장 스냅샷</h3>
|
||||
<p className="stock-panel__sub">
|
||||
지수/가격 API 연동을 위한 준비 구간입니다.
|
||||
주요 지표의 현재가와 등락을 빠르게 확인합니다.
|
||||
</p>
|
||||
</div>
|
||||
<div className="stock-panel__actions">
|
||||
{indicesLoading ? (
|
||||
<span className="stock-chip">갱신 중</span>
|
||||
) : null}
|
||||
{indicesAt ? (
|
||||
<span className="stock-chip">{indicesAt}</span>
|
||||
) : null}
|
||||
<button
|
||||
className="button ghost small"
|
||||
onClick={loadIndices}
|
||||
disabled={indicesLoading}
|
||||
>
|
||||
지표 새로고침
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="stock-snapshot">
|
||||
{['KOSPI', 'KOSDAQ', 'NASDAQ'].map((label) => (
|
||||
<div key={label} className="stock-snapshot__card">
|
||||
<p>{label}</p>
|
||||
<strong>--</strong>
|
||||
<span>연동 예정</span>
|
||||
{indices.length === 0 ? (
|
||||
<p className="stock-empty">지표 데이터를 불러오지 못했습니다.</p>
|
||||
) : (
|
||||
[...indices]
|
||||
.sort((a, b) => {
|
||||
const order = ['KOSPI', 'KOSDAQ', 'KOSPI200'];
|
||||
const aIdx = order.indexOf(a.name);
|
||||
const bIdx = order.indexOf(b.name);
|
||||
if (aIdx === -1 && bIdx === -1) return 0;
|
||||
if (aIdx === -1) return 1;
|
||||
if (bIdx === -1) return -1;
|
||||
return aIdx - bIdx;
|
||||
})
|
||||
.map((item) => (
|
||||
<div
|
||||
key={item.name}
|
||||
className={`stock-snapshot__card ${
|
||||
item.name?.toUpperCase?.() ===
|
||||
'KOSPI200'
|
||||
? 'is-highlight'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<p>{item.name}</p>
|
||||
<strong>{item.value ?? '--'}</strong>
|
||||
<span
|
||||
className={`stock-snapshot__change ${
|
||||
item.direction === 'red'
|
||||
? 'is-up'
|
||||
: item.direction === 'blue'
|
||||
? 'is-down'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
{item.direction === 'red'
|
||||
? '▲'
|
||||
: item.direction === 'blue'
|
||||
? '▼'
|
||||
: '■'}{' '}
|
||||
{item.change_value ?? '--'}{' '}
|
||||
{item.change_percent ?? ''}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user