SPMS_WEB/react/src/features/service/components/ServiceStatsCards.tsx
SEAN 9db9d87dea feat: 서비스 관리 페이지 구현 (#14)
- 서비스 목록 페이지 (검색/필터/페이지네이션, 행 클릭 → 상세)
- 서비스 상세 페이지 (헤더카드/통계/플랫폼 관리 모달)
- 서비스 등록 페이지 (서비스명/플랫폼 선택/설명/관련링크)
- 서비스 수정 페이지 (상태 토글/메타정보/저장 확인 모달)
- 공통 훅 추출 (useShake, useBreadcrumbBack)
- 브레드크럼 동적 경로 지원 (/services/:id, /services/:id/edit)
- 인증 페이지 useShake 공통 훅 리팩터링

Closes #14
2026-02-27 13:53:56 +09:00

114 lines
3.5 KiB
TypeScript

interface StatCard {
label: string;
value: string;
sub: { type: "trend" | "stable" | "live"; text: string; color?: string };
icon: string;
iconBg: string;
iconColor: string;
}
interface ServiceStatsCardsProps {
totalSent: number;
successRate: number;
deviceCount: number;
todaySent: number;
}
export default function ServiceStatsCards({
totalSent,
successRate,
deviceCount,
todaySent,
}: ServiceStatsCardsProps) {
const cards: StatCard[] = [
{
label: "총 발송 수",
value: totalSent.toLocaleString(),
sub: { type: "trend", text: "+12.5%", color: "text-indigo-600" },
icon: "equalizer",
iconBg: "bg-indigo-50",
iconColor: "text-indigo-600",
},
{
label: "성공률",
value: `${successRate}%`,
sub: { type: "stable", text: "Stable" },
icon: "check_circle",
iconBg: "bg-emerald-50",
iconColor: "text-emerald-600",
},
{
label: "등록 기기 수",
value: deviceCount.toLocaleString(),
sub: { type: "trend", text: "+82 today", color: "text-amber-600" },
icon: "devices",
iconBg: "bg-amber-50",
iconColor: "text-amber-600",
},
{
label: "오늘 발송",
value: todaySent.toLocaleString(),
sub: { type: "live", text: "Live" },
icon: "today",
iconBg: "bg-[#2563EB]/5",
iconColor: "text-[#2563EB]",
},
];
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
{cards.map((card) => (
<div
key={card.label}
className="bg-white border border-gray-200 rounded-lg shadow-sm p-6"
>
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="text-xs text-[#64748b] font-semibold uppercase tracking-wide">
{card.label}
</p>
<p className="text-2xl font-bold text-[#0f172a] mt-2">
{card.value}
</p>
<div className="mt-2">
{card.sub.type === "trend" && (
<p className={`text-xs ${card.sub.color ?? "text-green-600"} font-medium flex items-center gap-1`}>
<span
className="material-symbols-outlined"
style={{ fontSize: "14px" }}
>
trending_up
</span>
<span>{card.sub.text}</span>
</p>
)}
{card.sub.type === "stable" && (
<p className="text-xs text-gray-600 font-medium flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-gray-400" />
<span>{card.sub.text}</span>
</p>
)}
{card.sub.type === "live" && (
<p className="text-xs text-[#2563EB] font-medium flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-[#2563EB] animate-pulse" />
<span>{card.sub.text}</span>
</p>
)}
</div>
</div>
<div
className={`size-12 rounded-lg ${card.iconBg} flex items-center justify-center flex-shrink-0`}
>
<span
className={`material-symbols-outlined ${card.iconColor} text-2xl`}
>
{card.icon}
</span>
</div>
</div>
</div>
))}
</div>
);
}