import { BarChart3, TrendingDown, TrendingUp } from "lucide-react";
import { RefreshCcw } from "lucide-react";
import { Badge } from "@/components/ui/badge";
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;
isWebSocketReady?: boolean;
isRealtimePending?: boolean;
onRetry?: () => void;
}
/**
* @description 코스피/코스닥 지수 요약 카드입니다.
* @see features/dashboard/components/DashboardContainer.tsx 우측 상단 영역에서 호출합니다.
*/
export function MarketSummary({
items,
isLoading,
error,
warning = null,
isWebSocketReady = false,
isRealtimePending = false,
onRetry,
}: MarketSummaryProps) {
const realtimeBadgeText = isRealtimePending
? "실시간 대기중"
: isWebSocketReady
? "실시간 수신중"
: items.length > 0
? "REST 데이터"
: "데이터 준비중";
return (
시장 지수
{realtimeBadgeText}
코스피/코스닥 핵심 지수와 전일 대비 흐름을 빠르게 확인합니다.
{/* ========== 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-linear-to-br from-red-50/90 to-background dark:from-red-950/20 dark:to-background border-red-100/80 dark:border-red-900/40"
: isDown
? "bg-linear-to-br from-blue-50/90 to-background dark:from-blue-950/20 dark:to-background border-blue-100/80 dark:border-blue-900/40"
: "bg-linear-to-br from-muted/60 to-background border-border/50";
const flash = usePriceFlash(item.price, item.code);
return (
{item.name} ({item.market})
{isUp ? (
) : isDown ? (
) : null}
{formatCurrency(item.price)}
{/* Flash Indicator */}
{flash && (
{flash.type === "up" ? "+" : ""}
{flash.val.toFixed(2)}
)}
{formatSignedCurrency(item.change)}
{formatSignedPercent(item.changeRate)}
);
}