"use client"; // 시장 overview: 등락 상위 / 등락 하위 / 거래량 상위 3 컬럼. // /api/markets/movers 응답을 그대로 표 형태로 렌더. import Link from "next/link"; import { useEffect, useState } from "react"; import { api, type Mover, type MoversResponse } from "../lib/api"; export function MarketsOverview() { const [data, setData] = useState(null); const [err, setErr] = useState(null); useEffect(() => { let alive = true; api .movers("ALL", 10) .then((r) => { if (alive) setData(r); }) .catch((e) => { if (alive) setErr(e instanceof Error ? e.message : String(e)); }); return () => { alive = false; }; }, []); if (err) return (
시장 overview 로딩 실패: {err}
); if (!data) return (
시장 overview 로딩 중…
); // 아직 ohlcv_daily 가 비어 있으면 모든 리스트가 빈 배열로 옴 — 안내만. const empty = !data.gainers.length && !data.losers.length && !data.by_volume.length; if (empty) return (
시장 데이터 준비 중. ohlcv_daily 가 채워지면 자동으로 표시됩니다.
); return (
); } function MoverList({ title, items, showVolume = false, }: { title: string; items: Mover[]; showVolume?: boolean; }) { return (
{title}
{items.length === 0 ? (
데이터 없음
) : (
    {items.map((m, i) => ( ))}
)}
); } function MoverRow({ rank, m, showVolume, }: { rank: number; m: Mover; showVolume: boolean; }) { const pct = m.pct_change; const tone = pct == null || pct === 0 ? "text-zinc-400" : pct > 0 ? "text-rose-400" : "text-sky-400"; return (
  • {rank} {m.name}
    {showVolume ? ( <>
    {fmtVol(m.volume)}
    {pct != null ? `${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%` : ""}
    ) : ( <>
    {m.close != null ? m.close.toLocaleString(undefined, { maximumFractionDigits: 0 }) : "—"}
    {pct != null ? `${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%` : ""}
    )}
  • ); } function fmtVol(n: number | null): string { if (n == null) return "—"; if (n >= 1e8) return `${(n / 1e8).toFixed(1)}억`; if (n >= 1e4) return `${(n / 1e4).toFixed(1)}만`; return n.toLocaleString(); }