"use client"; // 토스증권 뉴스 카드 스타일을 흉내낸 리스트. // // 백엔드가 OG 썸네일은 아직 안 주므로 텍스트 카드 + 감성 칩 + 출처 배지만. // 썸네일은 백엔드에 og_image 컬럼이 들어오면 추가. import { useEffect, useState } from "react"; import { api, type NewsItem, type NewsResponse } from "../lib/api"; export function NewsList({ code }: { code: string }) { const [data, setData] = useState(null); const [err, setErr] = useState(null); useEffect(() => { let alive = true; api .news(code, 20) .then((r) => { if (alive) setData(r); }) .catch((e) => { if (alive) setErr(e instanceof Error ? e.message : String(e)); }); return () => { alive = false; }; }, [code]); if (err) return
뉴스 로딩 실패: {err}
; if (!data) return
뉴스 로딩 중…
; if (!data.items.length) return
최근 뉴스 없음
; return (
뉴스 · 공시
최근 {data.items.length}건
    {data.items.map((n, i) => ( ))}
); } function NewsCard({ n }: { n: NewsItem }) { return (
  • {n.title}
    {n.published_at && ( {formatRelative(n.published_at)} )} {n.sentiment_label && ( )}
  • ); } function SourceBadge({ source }: { source: string }) { return ( {source} ); } function SentimentChip({ label, score, }: { label: string; score: number | null; }) { // KR 관행: 긍정=빨강, 부정=파랑 (StockChart 와 톤 일치) const tone = label === "positive" ? "bg-rose-500/15 text-rose-300" : label === "negative" ? "bg-sky-500/15 text-sky-300" : "bg-zinc-700/40 text-zinc-400"; const text = label === "positive" ? "긍정" : label === "negative" ? "부정" : "중립"; return ( {text} {score != null && ` ${score >= 0 ? "+" : ""}${score.toFixed(2)}`} ); } function formatRelative(iso: string): string { try { const d = new Date(iso); const now = Date.now(); const diffMin = Math.round((now - d.getTime()) / 60000); if (diffMin < 1) return "방금"; if (diffMin < 60) return `${diffMin}분 전`; if (diffMin < 60 * 24) return `${Math.round(diffMin / 60)}시간 전`; if (diffMin < 60 * 24 * 7) return `${Math.round(diffMin / (60 * 24))}일 전`; return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; } catch { return iso; } } function pad(n: number): string { return n < 10 ? `0${n}` : `${n}`; }