import Link from "next/link"; import type { ReactNode } from "react"; import { Activity, RefreshCcw, Settings2, Wifi } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import type { DashboardBalanceSummary } from "@/features/dashboard/types/dashboard.types"; import { formatCurrency, formatSignedCurrency, formatSignedPercent, getChangeToneClass, } from "@/features/dashboard/utils/dashboard-format"; import { cn } from "@/lib/utils"; interface StatusHeaderProps { summary: DashboardBalanceSummary | null; isKisRestConnected: boolean; isWebSocketReady: boolean; isRealtimePending: boolean; isProfileVerified: boolean; verifiedAccountNo: string | null; isRefreshing: boolean; lastUpdatedAt: string | null; onRefresh: () => void; } /** * @description 대시보드 상단 상태 헤더(총자산/손익/연결상태/빠른액션) 컴포넌트입니다. * @see features/dashboard/components/DashboardContainer.tsx 대시보드 루트에서 상태 값을 전달받아 렌더링합니다. */ export function StatusHeader({ summary, isKisRestConnected, isWebSocketReady, isRealtimePending, isProfileVerified, verifiedAccountNo, isRefreshing, lastUpdatedAt, onRefresh, }: StatusHeaderProps) { const toneClass = getChangeToneClass(summary?.totalProfitLoss ?? 0); const updatedLabel = lastUpdatedAt ? new Date(lastUpdatedAt).toLocaleTimeString("ko-KR", { hour12: false, }) : "--:--:--"; const hasApiTotalAmount = Boolean(summary) && (summary?.apiReportedTotalAmount ?? 0) > 0; const hasApiNetAssetAmount = Boolean(summary) && (summary?.apiReportedNetAssetAmount ?? 0) > 0; const isApiTotalAmountDifferent = Boolean(summary) && Math.abs( (summary?.apiReportedTotalAmount ?? 0) - (summary?.totalAmount ?? 0), ) >= 1; const realtimeStatusLabel = isWebSocketReady ? isRealtimePending ? "수신 대기중" : "연결됨" : "미연결"; const displayGrossTotalAmount = hasApiTotalAmount ? summary?.apiReportedTotalAmount ?? 0 : summary?.totalAmount ?? 0; return (

TOTAL ASSET

{summary ? `${formatCurrency(displayGrossTotalAmount)}원` : "-"}

순자산 {summary ? `${formatCurrency(summary.netAssetAmount)}원` : "-"}

현금 {summary ? `${formatCurrency(summary.cashBalance)}원` : "-"} · 평가금{" "} {summary ? `${formatCurrency(summary.evaluationAmount)}원` : "-"}

TODAY P/L

{summary ? `${formatSignedCurrency(summary.totalProfitLoss)}원` : "-"}

{summary ? formatSignedPercent(summary.totalProfitRate) : "-"}

매수금 {summary ? `${formatCurrency(summary.purchaseAmount)}원` : "-"} · 대출금{" "} {summary ? `${formatCurrency(summary.loanAmount)}원` : "-"}

업데이트 {updatedLabel}

계좌 {maskAccountNo(verifiedAccountNo)}

} label="REST 연결" value={isKisRestConnected ? "정상" : "끊김"} toneClass={ isKisRestConnected ? "border-emerald-300/70 bg-emerald-50/60 text-emerald-700 dark:border-emerald-700/50 dark:bg-emerald-950/30 dark:text-emerald-300" : "border-red-300/70 bg-red-50/60 text-red-700 dark:border-red-700/50 dark:bg-red-950/30 dark:text-red-300" } /> } label="실시간 시세" value={realtimeStatusLabel} toneClass={ isWebSocketReady ? isRealtimePending ? "border-amber-300/70 bg-amber-50/60 text-amber-700 dark:border-amber-700/50 dark:bg-amber-950/30 dark:text-amber-300" : "border-emerald-300/70 bg-emerald-50/60 text-emerald-700 dark:border-emerald-700/50 dark:bg-emerald-950/30 dark:text-emerald-300" : "border-slate-300/70 bg-slate-50/60 text-slate-700 dark:border-slate-700/50 dark:bg-slate-900/30 dark:text-slate-300" } /> } label="계좌 인증" value={isProfileVerified ? "완료" : "미완료"} toneClass={ isProfileVerified ? "border-emerald-300/70 bg-emerald-50/60 text-emerald-700 dark:border-emerald-700/50 dark:bg-emerald-950/30 dark:text-emerald-300" : "border-amber-300/70 bg-amber-50/60 text-amber-700 dark:border-amber-700/50 dark:bg-amber-950/30 dark:text-amber-300" } /> } label="총예수금(KIS)" value={summary ? `${formatCurrency(summary.totalDepositAmount)}원` : "-"} toneClass="border-brand-200/80 bg-brand-50/70 text-brand-700 dark:border-brand-700/60 dark:bg-brand-900/35 dark:text-brand-300" />
{hasApiTotalAmount && isApiTotalAmountDifferent ? (

내부 계산 총자산 {formatCurrency(summary?.totalAmount ?? 0)}원 · KIS 총자산{" "} {formatCurrency(summary?.apiReportedTotalAmount ?? 0)}원

) : null} {hasApiNetAssetAmount ? (

KIS 집계 순자산 {formatCurrency(summary?.apiReportedNetAssetAmount ?? 0)}원

) : null}
); } function OverviewMetric({ icon, label, value, toneClass, }: { icon: ReactNode; label: string; value: string; toneClass: string; }) { return (

{icon} {label}

{value}

); } /** * @description 계좌번호를 마스킹해 표시합니다. * @param value 계좌번호(8-2) * @returns 마스킹 문자열 * @see features/dashboard/components/StatusHeader.tsx 시스템 상태 영역 계좌 표시 */ function maskAccountNo(value: string | null) { if (!value) return "-"; const digits = value.replace(/\D/g, ""); if (digits.length !== 10) return "********"; return "********-**"; }