/**
* @file HoldingsList.tsx
* @description 대시보드 좌측 영역의 보유 종목 리스트 컴포넌트
* @remarks
* - [레이어] Components / UI
* - [사용자 행동] 종목 리스트 스크롤 -> 특정 종목 클릭(선택) -> 우측 상세 프레뷰 갱신
* - [데이터 흐름] DashboardContainer(mergedHoldings) -> HoldingsList -> HoldingItemRow -> onSelect(Callback)
* - [연관 파일] DashboardContainer.tsx, dashboard.types.ts, use-price-flash.ts
* @author jihoon87.lee
*/
import { AlertCircle, Wallet2 } from "lucide-react";
import { RefreshCcw } from "lucide-react";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import type { DashboardHoldingItem } from "@/features/dashboard/types/dashboard.types";
import {
formatCurrency,
formatPercent,
getChangeToneClass,
} from "@/features/dashboard/utils/dashboard-format";
import { cn } from "@/lib/utils";
import { usePriceFlash } from "@/features/dashboard/hooks/use-price-flash";
interface HoldingsListProps {
/** 보유 종목 데이터 리스트 (실시간 시세 병합됨) */
holdings: DashboardHoldingItem[];
/** 현재 선택된 종목의 심볼 (없으면 null) */
selectedSymbol: string | null;
/** 데이터 로딩 상태 */
isLoading: boolean;
/** 에러 메시지 (없으면 null) */
error: string | null;
/** 섹션 재조회 핸들러 */
onRetry?: () => void;
/** 종목 선택 시 호출되는 핸들러 */
onSelect: (symbol: string) => void;
}
/**
* [컴포넌트] 보유 종목 리스트
* 사용자의 잔고 정보를 바탕으로 실시간 시세가 반영된 종목 카드 목록을 렌더링합니다.
*
* @param props HoldingsListProps
* @see DashboardContainer.tsx - 좌측 메인 영역에서 실시간 병합 데이터를 전달받아 호출
* @see DashboardContainer.tsx - setSelectedSymbol 핸들러를 onSelect로 전달
*/
export function HoldingsList({
holdings,
selectedSymbol,
isLoading,
error,
onRetry,
onSelect,
}: HoldingsListProps) {
return (
{/* ========== 카드 헤더: 타이틀 및 설명 ========== */}
보유 종목
현재 보유 중인 종목을 선택하면 우측 상세가 갱신됩니다.
{/* ========== 카드 본문: 상태별 메시지 및 리스트 ========== */}
{/* 로딩 중 상태 (데이터가 아직 없는 경우) */}
{isLoading && holdings.length === 0 && (
보유 종목을 불러오는 중입니다.
)}
{/* 에러 발생 상태 */}
{error && (
{error}
한국투자증권 API가 일시적으로 불안정할 수 있습니다. 잠시 후 다시 시도해 주세요.
{onRetry ? (
) : null}
)}
{/* 데이터 없음 상태 */}
{!isLoading && holdings.length === 0 && !error && (
보유 종목이 없습니다.
)}
{/* 종목 리스트 렌더링 영역 */}
{holdings.length > 0 && (
{holdings.map((holding) => (
))}
)}
);
}
interface HoldingItemRowProps {
/** 개별 종목 정보 */
holding: DashboardHoldingItem;
/** 선택 여부 */
isSelected: boolean;
/** 클릭 핸들러 */
onSelect: (symbol: string) => void;
}
/**
* [컴포넌트] 보유 종목 개별 행 (아이템)
* 종목의 기본 정보와 실시간 시세, 현재 손익 상태를 표시합니다.
*
* @param props HoldingItemRowProps
* @see HoldingsList.tsx - holdings.map 내에서 호출
* @see use-price-flash.ts - 현재가 변경 감지 및 애니메이션 효과 트리거
*/
function HoldingItemRow({
holding,
isSelected,
onSelect,
}: HoldingItemRowProps) {
// [State/Hook] 현재가 기반 가격 반짝임 효과 상태 관리
// @see use-price-flash.ts - 현재가 변경 감지 시 symbol을 기준으로 이펙트 발생 여부 결정
const flash = usePriceFlash(holding.currentPrice, holding.symbol);
// [UI] 손익 상태에 따른 텍스트 색상 클래스 결정 (상승: red, 하락: blue)
const toneClass = getChangeToneClass(holding.profitLoss);
return (
);
}