import { BarChart3, TrendingDown, TrendingUp } from "lucide-react"; import { RefreshCcw } from "lucide-react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import type { DashboardMarketIndexItem } from "@/features/dashboard/types/dashboard.types"; import { Button } from "@/components/ui/button"; import { formatCurrency, formatSignedCurrency, formatSignedPercent, } from "@/features/dashboard/utils/dashboard-format"; import { cn } from "@/lib/utils"; import { usePriceFlash } from "@/features/dashboard/hooks/use-price-flash"; interface MarketSummaryProps { items: DashboardMarketIndexItem[]; isLoading: boolean; error: string | null; warning?: string | null; isRealtimePending?: boolean; onRetry?: () => void; } /** * @description 코스피/코스닥 지수 요약 카드입니다. * @see features/dashboard/components/DashboardContainer.tsx 우측 상단 영역에서 호출합니다. */ export function MarketSummary({ items, isLoading, error, warning = null, isRealtimePending = false, onRetry, }: MarketSummaryProps) { return (
시장 지수
실시간 코스피/코스닥 지수 현황입니다.
{/* ========== LOADING STATE ========== */} {isLoading && items.length === 0 && (
지수 데이터를 불러오는 중입니다...
)} {/* ========== REALTIME PENDING STATE ========== */} {isRealtimePending && items.length === 0 && !isLoading && !error && (
실시간 시세 연결은 완료되었고 첫 지수 데이터를 기다리는 중입니다.
)} {/* ========== ERROR/WARNING STATE ========== */} {error && (

지수 정보를 가져오는데 실패했습니다.

{toCompactErrorMessage(error)}

토큰이 정상이어도 한국투자증권 API 점검/지연 시 일시적으로 실패할 수 있습니다.

{onRetry ? ( ) : null}
)} {!error && warning && (
{warning}
)} {/* ========== INDEX CARDS ========== */} {items.map((item) => ( ))} {!isLoading && items.length === 0 && !error && (
표시할 데이터가 없습니다.
)}
); } /** * @description 길고 복잡한 서버 오류를 대시보드 카드에 맞는 짧은 문구로 축약합니다. * @param error 원본 오류 문자열 * @returns 화면 노출용 오류 메시지 * @see features/dashboard/components/MarketSummary.tsx 지수 오류 배너 상세 문구 */ function toCompactErrorMessage(error: string) { const normalized = error.replaceAll(/\s+/g, " ").trim(); if (!normalized) return "잠시 후 다시 시도해 주세요."; if (normalized.length <= 120) return normalized; return `${normalized.slice(0, 120)}...`; } function IndexItem({ item }: { item: DashboardMarketIndexItem }) { const isUp = item.change > 0; const isDown = item.change < 0; const toneClass = isUp ? "text-red-600 dark:text-red-400" : isDown ? "text-blue-600 dark:text-blue-400" : "text-muted-foreground"; const bgClass = isUp ? "bg-red-50/50 dark:bg-red-950/10 border-red-100 dark:border-red-900/30" : isDown ? "bg-blue-50/50 dark:bg-blue-950/10 border-blue-100 dark:border-blue-900/30" : "bg-muted/50 border-border/50"; const flash = usePriceFlash(item.price, item.code); return (
{item.market} {isUp ? ( ) : isDown ? ( ) : null}
{formatCurrency(item.price)} {/* Flash Indicator */} {flash && (
{flash.type === "up" ? "+" : ""} {flash.val.toFixed(2)}
)}
{formatSignedCurrency(item.change)} {formatSignedPercent(item.changeRate)}
); }