SPMS_WEB/react/src/features/dashboard/components/StatsCards.tsx
SEAN 59c206e0c2 feat: 대시보드 조회 로딩 상태 및 필터 disabled 처리
- 조회 클릭 시 전체 필터 비활성화 (날짜/드롭다운/초기화/조회)
- 각 위젯 로딩 오버레이 및 스켈레톤 추가
- 조회 완료 시 랜덤 Mock 데이터로 갱신
- 공통 컴포넌트(FilterDropdown, DateRangeInput, FilterResetButton)에 disabled prop 추가
- StatsCards 뱃지 아이콘 크기 및 중앙 정렬 개선

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 09:59:59 +09:00

85 lines
2.8 KiB
TypeScript

import { Link } from "react-router-dom";
interface StatCard {
label: string;
value: string;
/** 값 뒤에 붙는 단위 (예: "%") */
unit?: string;
/** 우상단 뱃지 */
badge?: { type: "trend"; value: string } | { type: "icon"; icon: string; color: string };
link: string;
}
interface StatsCardsProps {
cards?: StatCard[];
loading?: boolean;
}
/** 하드코딩 기본 데이터 */
const DEFAULT_CARDS: StatCard[] = [
{
label: "오늘 발송 수",
value: "12,847",
badge: { type: "trend", value: "15%" },
link: "/statistics",
},
{
label: "성공률",
value: "98.7",
unit: "%",
badge: { type: "icon", icon: "check_circle", color: "bg-green-100 text-green-600" },
link: "/statistics",
},
{
label: "등록 기기 수",
value: "45,230",
badge: { type: "icon", icon: "devices", color: "bg-blue-50 text-primary" },
link: "/devices",
},
{
label: "활성 서비스",
value: "8",
badge: { type: "icon", icon: "grid_view", color: "bg-purple-50 text-purple-600" },
link: "/services",
},
];
export default function StatsCards({ cards = DEFAULT_CARDS, loading }: StatsCardsProps) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
{cards.map((card) => (
<Link
key={card.label}
to={card.link}
className="bg-white rounded-xl shadow-sm border border-gray-200 p-5 flex flex-col justify-between h-28 hover:shadow-md hover:border-primary/30 transition-all cursor-pointer"
>
<div className="flex items-center justify-between">
<h4 className="text-xs font-medium text-gray-500">{card.label}</h4>
{card.badge?.type === "trend" && (
<div className="bg-green-50 text-green-700 text-[10px] font-bold px-1.5 py-0.5 rounded-full inline-flex items-center gap-0.5">
<span className="material-symbols-outlined flex-shrink-0" style={{ fontSize: "10px" }}>trending_up</span>
{card.badge.value}
</div>
)}
{card.badge?.type === "icon" && (
<div className={`${card.badge.color} rounded-full size-6 inline-flex items-center justify-center`}>
<span className="material-symbols-outlined flex-shrink-0" style={{ fontSize: "14px" }}>{card.badge.icon}</span>
</div>
)}
</div>
{loading ? (
<div className="h-8 w-24 rounded bg-gray-100 animate-pulse" />
) : (
<div className="text-2xl font-bold text-gray-900">
{card.value}
{card.unit && (
<span className="text-base text-gray-400 ml-0.5 font-normal">{card.unit}</span>
)}
</div>
)}
</Link>
))}
</div>
);
}