142 lines
4.8 KiB
TypeScript
142 lines
4.8 KiB
TypeScript
import { BarChartBig, MousePointerClick } from "lucide-react";
|
|
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";
|
|
|
|
interface StockDetailPreviewProps {
|
|
holding: DashboardHoldingItem | null;
|
|
totalAmount: number;
|
|
}
|
|
|
|
/**
|
|
* @description 선택 종목 상세 요약 카드입니다.
|
|
* @see features/dashboard/components/DashboardContainer.tsx HoldingsList 선택 결과를 전달받아 렌더링합니다.
|
|
*/
|
|
export function StockDetailPreview({
|
|
holding,
|
|
totalAmount,
|
|
}: StockDetailPreviewProps) {
|
|
if (!holding) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<BarChartBig className="h-4 w-4 text-brand-600 dark:text-brand-400" />
|
|
종목 상세 미리보기
|
|
</CardTitle>
|
|
<CardDescription>
|
|
보유 종목을 선택하면 상세 요약이 표시됩니다.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-sm text-muted-foreground">
|
|
왼쪽 보유 종목 리스트에서 종목을 선택해 주세요.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const profitToneClass = getChangeToneClass(holding.profitLoss);
|
|
const allocationRate =
|
|
totalAmount > 0 ? Math.min((holding.evaluationAmount / totalAmount) * 100, 100) : 0;
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
{/* ========== TITLE ========== */}
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<BarChartBig className="h-4 w-4 text-brand-600 dark:text-brand-400" />
|
|
종목 상세 미리보기
|
|
</CardTitle>
|
|
<CardDescription>
|
|
{holding.name} ({holding.symbol}) · {holding.market}
|
|
</CardDescription>
|
|
</CardHeader>
|
|
|
|
<CardContent className="space-y-4">
|
|
{/* ========== PRIMARY METRICS ========== */}
|
|
<div className="grid grid-cols-2 gap-2 text-sm">
|
|
<Metric label="보유 수량" value={`${holding.quantity.toLocaleString("ko-KR")}주`} />
|
|
<Metric label="매입 평균가" value={`${formatCurrency(holding.averagePrice)}원`} />
|
|
<Metric label="현재가" value={`${formatCurrency(holding.currentPrice)}원`} />
|
|
<Metric
|
|
label="수익률"
|
|
value={formatPercent(holding.profitRate)}
|
|
valueClassName={profitToneClass}
|
|
/>
|
|
<Metric
|
|
label="평가손익"
|
|
value={`${formatCurrency(holding.profitLoss)}원`}
|
|
valueClassName={profitToneClass}
|
|
/>
|
|
<Metric
|
|
label="평가금액"
|
|
value={`${formatCurrency(holding.evaluationAmount)}원`}
|
|
/>
|
|
</div>
|
|
|
|
{/* ========== ALLOCATION BAR ========== */}
|
|
<div className="rounded-xl border border-border/70 bg-background/70 p-3">
|
|
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
|
<span>총 자산 대비 비중</span>
|
|
<span>{formatPercent(allocationRate)}</span>
|
|
</div>
|
|
<div className="mt-2 h-2 rounded-full bg-muted">
|
|
<div
|
|
className="h-2 rounded-full bg-linear-to-r from-brand-500 to-brand-700"
|
|
style={{ width: `${allocationRate}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ========== QUICK ORDER PLACEHOLDER ========== */}
|
|
<div className="rounded-xl border border-dashed border-border/80 bg-muted/30 p-3">
|
|
<p className="flex items-center gap-1.5 text-sm font-medium text-foreground/80">
|
|
<MousePointerClick className="h-4 w-4 text-brand-500" />
|
|
간편 주문(준비 중)
|
|
</p>
|
|
<p className="mt-1 text-xs text-muted-foreground">
|
|
향후 이 영역에서 선택 종목의 빠른 매수/매도 기능을 제공합니다.
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
interface MetricProps {
|
|
label: string;
|
|
value: string;
|
|
valueClassName?: string;
|
|
}
|
|
|
|
/**
|
|
* @description 상세 카드에서 공통으로 사용하는 지표 행입니다.
|
|
* @param label 지표명
|
|
* @param value 지표값
|
|
* @param valueClassName 값 텍스트 색상 클래스
|
|
* @see features/dashboard/components/StockDetailPreview.tsx 종목 상세 지표 표시
|
|
*/
|
|
function Metric({ label, value, valueClassName }: MetricProps) {
|
|
return (
|
|
<div className="rounded-xl border border-border/70 bg-background/70 p-3">
|
|
<p className="text-xs text-muted-foreground">{label}</p>
|
|
<p className={cn("mt-1 text-sm font-semibold text-foreground", valueClassName)}>
|
|
{value}
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|