86 lines
2.9 KiB
TypeScript
86 lines
2.9 KiB
TypeScript
|
|
import { AlertCircle, BarChart3 } from "lucide-react";
|
||
|
|
import {
|
||
|
|
Card,
|
||
|
|
CardContent,
|
||
|
|
CardDescription,
|
||
|
|
CardHeader,
|
||
|
|
CardTitle,
|
||
|
|
} from "@/components/ui/card";
|
||
|
|
import type { DashboardMarketIndexItem } from "@/features/dashboard/types/dashboard.types";
|
||
|
|
import {
|
||
|
|
formatCurrency,
|
||
|
|
formatSignedCurrency,
|
||
|
|
formatSignedPercent,
|
||
|
|
getChangeToneClass,
|
||
|
|
} from "@/features/dashboard/utils/dashboard-format";
|
||
|
|
import { cn } from "@/lib/utils";
|
||
|
|
|
||
|
|
interface MarketSummaryProps {
|
||
|
|
items: DashboardMarketIndexItem[];
|
||
|
|
isLoading: boolean;
|
||
|
|
error: string | null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @description 코스피/코스닥 지수 요약 카드입니다.
|
||
|
|
* @see features/dashboard/components/DashboardContainer.tsx 우측 상단 영역에서 호출합니다.
|
||
|
|
*/
|
||
|
|
export function MarketSummary({ items, isLoading, error }: MarketSummaryProps) {
|
||
|
|
return (
|
||
|
|
<Card className="border-brand-200/80 dark:border-brand-800/45">
|
||
|
|
<CardHeader className="pb-3">
|
||
|
|
{/* ========== TITLE ========== */}
|
||
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
||
|
|
<BarChart3 className="h-4 w-4 text-brand-600 dark:text-brand-400" />
|
||
|
|
시장 요약
|
||
|
|
</CardTitle>
|
||
|
|
<CardDescription>
|
||
|
|
KOSPI/KOSDAQ 주요 지수 변동을 보여줍니다.
|
||
|
|
</CardDescription>
|
||
|
|
</CardHeader>
|
||
|
|
|
||
|
|
<CardContent className="space-y-2">
|
||
|
|
{isLoading && items.length === 0 && (
|
||
|
|
<p className="text-sm text-muted-foreground">지수 데이터를 불러오는 중입니다.</p>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{error && (
|
||
|
|
<p className="flex items-center gap-1.5 text-sm text-red-600 dark:text-red-400">
|
||
|
|
<AlertCircle className="h-4 w-4" />
|
||
|
|
{error}
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{items.map((item) => {
|
||
|
|
const toneClass = getChangeToneClass(item.change);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
key={item.code}
|
||
|
|
className="rounded-xl border border-border/70 bg-background/70 px-3 py-2"
|
||
|
|
>
|
||
|
|
<div className="flex items-end justify-between">
|
||
|
|
<div>
|
||
|
|
<p className="text-xs text-muted-foreground">{item.market}</p>
|
||
|
|
<p className="text-sm font-semibold text-foreground">{item.name}</p>
|
||
|
|
</div>
|
||
|
|
<p className="text-lg font-semibold tracking-tight">
|
||
|
|
{formatCurrency(item.price)}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<div className={cn("mt-1 flex items-center gap-2 text-xs font-medium", toneClass)}>
|
||
|
|
<span>{formatSignedCurrency(item.change)}</span>
|
||
|
|
<span>{formatSignedPercent(item.changeRate)}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
|
||
|
|
{!isLoading && items.length === 0 && !error && (
|
||
|
|
<p className="text-sm text-muted-foreground">표시할 지수 데이터가 없습니다.</p>
|
||
|
|
)}
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
);
|
||
|
|
}
|