import { AlertCircle, ClipboardList, FileText, RefreshCcw } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import type { DashboardActivityResponse, DashboardTradeSide, } from "@/features/dashboard/types/dashboard.types"; import { formatCurrency, formatPercent, getChangeToneClass, } from "@/features/dashboard/utils/dashboard-format"; import { cn } from "@/lib/utils"; interface ActivitySectionProps { activity: DashboardActivityResponse | null; isLoading: boolean; error: string | null; onRetry?: () => void; } /** * @description 대시보드 하단 주문내역/매매일지 섹션입니다. * @remarks UI 흐름: DashboardContainer -> ActivitySection -> tabs(주문내역/매매일지) -> 리스트 렌더링 * @see features/dashboard/components/DashboardContainer.tsx 하단 영역에서 호출합니다. * @see app/api/kis/domestic/activity/route.ts 주문내역/매매일지 데이터 소스 */ export function ActivitySection({ activity, isLoading, error, onRetry, }: ActivitySectionProps) { const orders = activity?.orders ?? []; const journalRows = activity?.tradeJournal ?? []; const summary = activity?.journalSummary; const warnings = activity?.warnings ?? []; return ( {/* ========== TITLE ========== */} 주문내역 · 매매일지 최근 주문 체결 내역과 실현손익 기록을 확인합니다. {isLoading && !activity && (

주문내역/매매일지를 불러오는 중입니다.

)} {error && (

{error}

주문/매매일지 API는 장중 혼잡 시간에 간헐적 실패가 발생할 수 있습니다.

{onRetry ? ( ) : null}
)} {warnings.length > 0 && (
{warnings.map((warning) => ( {warning} ))}
)} {/* ========== TABS ========== */} 주문내역 {orders.length}건 매매일지 {journalRows.length}건
일시 종목 주문 체결 평균체결가 상태
{orders.length === 0 ? (

표시할 주문내역이 없습니다.

) : (
{orders.map((order) => (
{/* ========== ORDER DATETIME ========== */}

{order.orderDate}

{order.orderTime}

{/* ========== STOCK INFO ========== */}

{order.name}

{order.symbol} · {getSideLabel(order.side)}

{/* ========== ORDER INFO ========== */}

수량 {order.orderQuantity.toLocaleString("ko-KR")}주

{order.orderTypeName} · {formatCurrency(order.orderPrice)}원

{/* ========== FILLED INFO ========== */}

체결 {order.filledQuantity.toLocaleString("ko-KR")}주

금액 {formatCurrency(order.filledAmount)}원

{/* ========== AVG PRICE ========== */}
{formatCurrency(order.averageFilledPrice)}원
{/* ========== STATUS ========== */}
0 ? "border-amber-300 text-amber-700 dark:border-amber-700 dark:text-amber-300" : "border-emerald-300 text-emerald-700 dark:border-emerald-700 dark:text-emerald-300", )} > {order.isCanceled ? "취소" : order.remainingQuantity > 0 ? "미체결" : "체결완료"}
))}
)}
{/* ========== JOURNAL SUMMARY ========== */}
일자 종목 매매구분 매수/매도금액 실현손익(률) 비용
{journalRows.length === 0 ? (

표시할 매매일지가 없습니다.

) : (
{journalRows.map((row) => { const toneClass = getChangeToneClass(row.realizedProfit); return (

{row.tradeDate}

{row.name}

{row.symbol}

{getSideLabel(row.side)}

매수 {formatCurrency(row.buyAmount)}원 / 매도 {formatCurrency(row.sellAmount)}원

{formatCurrency(row.realizedProfit)}원 ({formatPercent(row.realizedRate)})

수수료 {formatCurrency(row.fee)}원
세금 {formatCurrency(row.tax)}원

); })}
)}
{!isLoading && !error && !activity && (

활동 데이터가 없습니다.

)}
); } interface SummaryMetricProps { label: string; value: string; toneClass?: string; } /** * @description 매매일지 요약 지표 카드입니다. * @param label 지표명 * @param value 지표값 * @param toneClass 값 색상 클래스 * @see features/dashboard/components/ActivitySection.tsx 매매일지 상단 요약 표시 */ function SummaryMetric({ label, value, toneClass }: SummaryMetricProps) { return (

{label}

{value}

); } /** * @description 매수/매도 라벨 텍스트를 반환합니다. * @param side 매수/매도 구분값 * @returns 라벨 문자열 * @see features/dashboard/components/ActivitySection.tsx 주문내역/매매일지 표시 */ function getSideLabel(side: DashboardTradeSide) { if (side === "buy") return "매수"; if (side === "sell") return "매도"; return "기타"; } /** * @description 매수/매도 라벨 색상 클래스를 반환합니다. * @param side 매수/매도 구분값 * @returns Tailwind 텍스트 클래스 * @see features/dashboard/components/ActivitySection.tsx 매매구분 표시 */ function getSideToneClass(side: DashboardTradeSide) { if (side === "buy") return "text-red-600 dark:text-red-400"; if (side === "sell") return "text-blue-600 dark:text-blue-400"; return "text-muted-foreground"; }