import { AlertCircle, BarChart3, Flame, Newspaper, RefreshCcw, TrendingDown, TrendingUp, } from "lucide-react"; import type { ComponentType } from "react"; import { useRouter } from "next/navigation"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import type { DashboardMarketHubResponse, DashboardMarketRankItem, } from "@/features/dashboard/types/dashboard.types"; import { formatCurrency, formatSignedCurrency, formatSignedPercent, getChangeToneClass, } from "@/features/dashboard/utils/dashboard-format"; import { useTradeNavigationStore } from "@/features/trade/store/use-trade-navigation-store"; import { cn } from "@/lib/utils"; interface MarketHubSectionProps { marketHub: DashboardMarketHubResponse | null; isLoading: boolean; error: string | null; onRetry?: () => void; } /** * @description 시장 탭의 급등/인기/뉴스 요약 섹션입니다. * @remarks UI 흐름: DashboardContainer -> MarketHubSection -> 급등/인기/뉴스 카드 렌더링 */ export function MarketHubSection({ marketHub, isLoading, error, onRetry, }: MarketHubSectionProps) { const router = useRouter(); const setPendingTarget = useTradeNavigationStore( (state) => state.setPendingTarget, ); const gainers = marketHub?.gainers ?? []; const losers = marketHub?.losers ?? []; const popularByVolume = marketHub?.popularByVolume ?? []; const popularByValue = marketHub?.popularByValue ?? []; const news = marketHub?.news ?? []; const warnings = marketHub?.warnings ?? []; const pulse = marketHub?.pulse; const navigateToTrade = (item: DashboardMarketRankItem) => { setPendingTarget({ symbol: item.symbol, name: item.name, market: item.market, }); router.push("/trade"); }; return (
시장 허브 급등/급락, 인기 종목, 주요 뉴스를 한 화면에서 확인합니다.
{warnings.length > 0 && (
{warnings.map((warning) => ( {warning} ))}
)} {isLoading && !marketHub && (

시장 허브를 불러오는 중입니다.

)} {error && (

{error}

{onRetry ? ( ) : null}
)}
`${formatCurrency(item.volume)}주`} /> `${formatCurrency(item.volume)}주`} /> `${formatCurrency(item.volume)}주`} /> `${formatCurrency(item.tradingValue)}원`} />
주요 뉴스 국내 시장 시황/공시 제목을 최신순으로 보여줍니다. {news.length === 0 ? (

표시할 뉴스가 없습니다.

) : (
{news.map((item) => (

{item.title}

{item.source} · {item.publishedAt}

{item.symbols.length > 0 && (
{item.symbols.slice(0, 3).map((symbol) => ( {symbol} ))}
)}
))}
)}
); } function PulseMetric({ label, value, tone, }: { label: string; value: string; tone: "up" | "down" | "neutral" | "brand"; }) { const toneClass = tone === "up" ? "border-red-200/70 bg-red-50/70 dark:border-red-900/40 dark:bg-red-950/20" : tone === "down" ? "border-blue-200/70 bg-blue-50/70 dark:border-blue-900/40 dark:bg-blue-950/20" : tone === "brand" ? "border-brand-200/70 bg-brand-50/70 dark:border-brand-700/60 dark:bg-brand-900/30" : "border-border/70 bg-background/80"; return (

{label}

{value}

); } function RankListCard({ title, description, icon: Icon, items, tone, onSelectItem, secondaryLabel, secondaryValue, }: { title: string; description: string; icon: ComponentType<{ className?: string }>; items: DashboardMarketRankItem[]; tone: "up" | "down" | "neutral" | "brand"; onSelectItem: (item: DashboardMarketRankItem) => void; secondaryLabel: string; secondaryValue: (item: DashboardMarketRankItem) => string; }) { const toneClass = tone === "up" ? "border-red-200/70 bg-red-50/35 dark:border-red-900/35 dark:bg-red-950/15" : tone === "down" ? "border-blue-200/70 bg-blue-50/35 dark:border-blue-900/35 dark:bg-blue-950/15" : tone === "brand" ? "border-brand-200/70 bg-brand-50/35 dark:border-brand-800/50 dark:bg-brand-900/20" : "border-border/70 bg-background/90"; return ( {title} {description} {items.length === 0 ? (

표시할 데이터가 없습니다.

) : (
{items.map((item) => { const toneClass = getChangeToneClass(item.change); return (
); })}
)}
); }