/** * @file StockDetailPreview.tsx * @description 대시보드 우측 영역의 선택 종목 상세 정보 및 실시간 시세 반영 컴포넌트 * @remarks * - [레이어] Components / UI * - [사용자 행동] 종목 리스트에서 항목 선택 -> 상세 정보 조회 -> 실시간 시세 변동 확인 * - [데이터 흐름] DashboardContainer(realtimeSelectedHolding) -> StockDetailPreview -> Metric(UI) * - [연관 파일] DashboardContainer.tsx, dashboard.types.ts, use-price-flash.ts * @author jihoon87.lee */ import { BarChartBig, ExternalLink, MousePointerClick } from "lucide-react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { useRouter } from "next/navigation"; import { usePriceFlash } from "@/features/dashboard/hooks/use-price-flash"; import type { DashboardHoldingItem } from "@/features/dashboard/types/dashboard.types"; import { useTradeNavigationStore } from "@/features/trade/store/use-trade-navigation-store"; import { formatCurrency, formatPercent, getChangeToneClass, } from "@/features/dashboard/utils/dashboard-format"; import { cn } from "@/lib/utils"; interface StockDetailPreviewProps { /** 선택된 종목 정보 (없으면 null) */ holding: DashboardHoldingItem | null; /** 현재 총 자산 (비중 계산용) */ totalAmount: number; } /** * [컴포넌트] 선택 종목 상세 요약 카드 * 대시보드에서 선택된 특정 종목의 매입가, 현재가, 수익률 등 상세 지표를 실시간으로 보여줍니다. * * @param props StockDetailPreviewProps * @see DashboardContainer.tsx - HoldingsList 선택 결과를 실시간 데이터로 전달받아 렌더링 */ export function StockDetailPreview({ holding, totalAmount, }: StockDetailPreviewProps) { const router = useRouter(); const setPendingTarget = useTradeNavigationStore( (state) => state.setPendingTarget, ); // [State/Hook] 실시간 가격 변동 애니메이션 상태 관리 // @remarks 종목이 선택되지 않았을 때를 대비해 safe value(0)를 전달하며, 종목 변경 시 효과를 초기화하도록 symbol 전달 const currentPrice = holding?.currentPrice ?? 0; const priceFlash = usePriceFlash(currentPrice, holding?.symbol); // [Step 1] 종목이 선택되지 않은 경우 초기 안내 화면 렌더링 if (!holding) { return ( 선택 종목 정보 보유 종목을 선택하면 자세한 정보가 표시됩니다.

왼쪽 보유 종목 리스트에서 종목을 선택해 주세요.

); } // [Step 2] 수익/손실 여부에 따른 UI 톤(색상) 결정 const profitToneClass = getChangeToneClass(holding.profitLoss); // [Step 3] 총 자산 대비 비중 계산 const allocationRate = totalAmount > 0 ? Math.min((holding.evaluationAmount / totalAmount) * 100, 100) : 0; return ( {/* ========== 카드 헤더: 종목명 및 기본 정보 ========== */} 선택 종목 정보 · {holding.market} {/* ========== 실시간 주요 지표 영역 (Grid) ========== */}
{/* ========== 자산 비중 그래프 영역 ========== */}
총 자산 대비 비중 {formatPercent(allocationRate)}
{/* ========== 추가 기능 예고 영역 (Placeholder) ========== */}

빠른 주문(준비 중)

향후 이 영역에서 선택 종목의 빠른 매수/매도 기능을 제공합니다.

); } interface MetricProps { /** 지표 레이블 */ label: string; /** 표시될 값 */ value: string; /** 값 텍스트 추가 스타일 */ valueClassName?: string; /** 가격 변동 애니메이션 상태 */ flash?: { type: "up" | "down"; val: number; id: number } | null; } /** * [컴포넌트] 상세 카드용 개별 지표 아이템 * 레이블과 값을 박스 형태로 렌더링하며, 필요한 경우 시세 변동 Flash 애니메이션을 처리합니다. * * @param props MetricProps * @see StockDetailPreview.tsx - 내부 그리드 영역에서 여러 개 호출 */ function Metric({ label, value, valueClassName, flash }: MetricProps) { return (
{/* 시세 변동 시 나타나는 일시적인 수치 표시 (Flash) */} {flash && ( {flash.type === "up" ? "+" : ""} {flash.val.toLocaleString()} )} {/* 지표 레이블 및 본체 값 */}

{label}

{value}

); }