"use client"; // 학습 대상 SEED 10종목 quick-access 타일. // // 토스 홈의 '인기 종목' 카드 톤. 각 타일이 자체적으로 /api/chart 를 2일치 받아 // 현재가/전일대비 를 계산해서 표시. 10개 병렬 호출 — 백엔드는 캐시 친화적이므로 OK. // // 백엔드 신규 엔드포인트가 추가되면 (예: /api/markets/top-volume) 그쪽으로 갈아끼움. import Link from "next/link"; import { useEffect, useState } from "react"; import { api } from "../lib/api"; type Tile = { code: string; name: string; market: string; current: number | null; prev: number | null; }; const SEED_TICKERS: Array> = [ { code: "005930", name: "삼성전자", market: "KOSPI" }, { code: "000660", name: "SK하이닉스", market: "KOSPI" }, { code: "247540", name: "에코프로비엠", market: "KOSDAQ" }, { code: "042700", name: "한미반도체", market: "KOSPI" }, { code: "034020", name: "두산에너빌리티", market: "KOSPI" }, { code: "012450", name: "한화에어로스페이스", market: "KOSPI" }, { code: "329180", name: "HD현대중공업", market: "KOSPI" }, { code: "035420", name: "NAVER", market: "KOSPI" }, { code: "033780", name: "KT&G", market: "KOSPI" }, { code: "036460", name: "한국가스공사", market: "KOSPI" }, ]; export function SeedTiles() { const [tiles, setTiles] = useState( SEED_TICKERS.map((t) => ({ ...t, current: null, prev: null })), ); useEffect(() => { let alive = true; // 10 병렬 fetch. 한 종목 실패해도 다른 타일은 살린다. Promise.allSettled( SEED_TICKERS.map(async (t) => { const c = await api.getChart(t.code, 5, "1d"); const valid = c.ohlcv.filter((p) => p.close != null); const last = valid[valid.length - 1] ?? null; const prev = valid.length >= 2 ? valid[valid.length - 2] : null; return { code: t.code, name: t.name, market: t.market, current: last?.close ?? null, prev: prev?.close ?? null, }; }), ).then((settled) => { if (!alive) return; const next: Tile[] = settled.map((r, i) => { if (r.status === "fulfilled") return r.value; return { ...SEED_TICKERS[i], current: null, prev: null }; }); setTiles(next); }); return () => { alive = false; }; }, []); return (
학습 대상 10종목
현재가 · 전일대비
{tiles.map((t) => ( ))}
); } function TileCard({ t }: { t: Tile }) { const change = t.current != null && t.prev != null ? t.current - t.prev : null; const pct = change != null && t.prev != null && t.prev !== 0 ? (change / t.prev) * 100 : null; const tone = change == null || change === 0 ? "text-zinc-400" : change > 0 ? "text-rose-400" : "text-sky-400"; return (
{t.name}
{t.code} · {t.market}
{t.current != null ? t.current.toLocaleString(undefined, { maximumFractionDigits: 0 }) : "—"}
{change == null ? " " : `${change > 0 ? "+" : ""}${change.toLocaleString(undefined, { maximumFractionDigits: 0, })}${pct != null ? ` (${pct > 0 ? "+" : ""}${pct.toFixed(2)}%)` : ""}`}
); }