- 조회 클릭 시 전체 필터 비활성화 (날짜/드롭다운/초기화/조회) - 각 위젯 로딩 오버레이 및 스켈레톤 추가 - 조회 완료 시 랜덤 Mock 데이터로 갱신 - 공통 컴포넌트(FilterDropdown, DateRangeInput, FilterResetButton)에 disabled prop 추가 - StatsCards 뱃지 아이콘 크기 및 중앙 정렬 개선 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
85 lines
2.8 KiB
TypeScript
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>
|
|
);
|
|
}
|