From 832e73b5eee680670542aeb5395830e2cb1f4b97 Mon Sep 17 00:00:00 2001 From: claude Date: Thu, 28 May 2026 01:22:43 +0900 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=EB=89=B4=EC=8A=A4=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B9=B4=EB=93=9C=20=EB=A6=AC=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20(=EA=B0=90=EC=84=B1=20=EC=B9=A9=20+=20?= =?UTF-8?q?=EC=B6=9C=EC=B2=98=20=EB=B0=B0=EC=A7=80=20+=20=EC=83=81?= =?UTF-8?q?=EB=8C=80=EC=8B=9C=EA=B0=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 토스 뉴스 카드 톤. 변경: - 감성 라벨을 chip 형식으로 (긍정=빨강, 부정=파랑, KR 관행 유지) - 출처를 작은 배지로 분리, 본문과 시각 구분 - published_at 을 'N분 전 / N시간 전 / N일 전' 상대시간으로 - 카드 hover 시 border-zinc-700 강조 썸네일은 백엔드가 og_image 미제공이라 텍스트 카드로 한정. 추후 백엔드에 컬럼 추가되면 SourceBadge 위에 16:9 썸네일 슬롯 추가. verify: tsc/next lint clean. --- web/components/NewsList.tsx | 100 ++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/web/components/NewsList.tsx b/web/components/NewsList.tsx index fc70693..5373af7 100644 --- a/web/components/NewsList.tsx +++ b/web/components/NewsList.tsx @@ -1,7 +1,12 @@ "use client"; +// 토스증권 뉴스 카드 스타일을 흉내낸 리스트. +// +// 백엔드가 OG 썸네일은 아직 안 주므로 텍스트 카드 + 감성 칩 + 출처 배지만. +// 썸네일은 백엔드에 og_image 컬럼이 들어오면 추가. + import { useEffect, useState } from "react"; -import { api, type NewsResponse } from "../lib/api"; +import { api, type NewsItem, type NewsResponse } from "../lib/api"; export function NewsList({ code }: { code: string }) { const [data, setData] = useState(null); @@ -29,44 +34,83 @@ export function NewsList({ code }: { code: string }) { return (
-
최근 뉴스/공시
-
); } -function sentimentColor(l: string): string { - if (l === "positive") return "text-emerald-400"; - if (l === "negative") return "text-red-400"; - return "text-zinc-400"; +function NewsCard({ n }: { n: NewsItem }) { + return ( +
  • + +
    {n.title}
    +
    + + {n.published_at && ( + {formatRelative(n.published_at)} + )} + {n.sentiment_label && ( + + )} +
    +
    +
  • + ); } -function formatDate(iso: string): string { +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); - return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; + 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; }