2025-12-04 12:48:41 +09:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
|
|
|
import { LucideIcon } from "lucide-react";
|
|
|
|
|
|
|
|
|
|
interface StatCardData {
|
|
|
|
|
label: string;
|
2026-02-15 23:18:45 +09:00
|
|
|
sublabel?: string;
|
2025-12-04 12:48:41 +09:00
|
|
|
value: string | number;
|
|
|
|
|
icon?: LucideIcon;
|
|
|
|
|
iconColor?: string;
|
|
|
|
|
trend?: {
|
|
|
|
|
value: string;
|
|
|
|
|
isPositive: boolean;
|
|
|
|
|
};
|
2025-12-30 21:56:01 +09:00
|
|
|
onClick?: () => void;
|
|
|
|
|
isActive?: boolean;
|
2025-12-04 12:48:41 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface StatCardsProps {
|
|
|
|
|
stats: StatCardData[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function StatCards({ stats }: StatCardsProps) {
|
2026-02-15 23:18:45 +09:00
|
|
|
const count = stats.length;
|
|
|
|
|
const gridClass =
|
|
|
|
|
count >= 5
|
|
|
|
|
? 'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-2'
|
|
|
|
|
: 'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2';
|
|
|
|
|
|
2025-12-04 12:48:41 +09:00
|
|
|
return (
|
2026-02-15 23:18:45 +09:00
|
|
|
<div className={gridClass}>
|
2025-12-04 12:48:41 +09:00
|
|
|
{stats.map((stat, index) => {
|
|
|
|
|
const Icon = stat.icon;
|
2025-12-30 21:56:01 +09:00
|
|
|
const isClickable = !!stat.onClick;
|
|
|
|
|
|
2025-12-04 12:48:41 +09:00
|
|
|
return (
|
2025-12-30 21:56:01 +09:00
|
|
|
<Card
|
|
|
|
|
key={index}
|
2026-01-05 18:59:04 +09:00
|
|
|
className={`flex-1 min-w-0 transition-colors ${
|
2025-12-30 21:56:01 +09:00
|
|
|
isClickable ? 'cursor-pointer hover:border-primary/50' : ''
|
|
|
|
|
} ${
|
|
|
|
|
stat.isActive ? 'border-primary bg-primary/5' : ''
|
|
|
|
|
}`}
|
|
|
|
|
onClick={stat.onClick}
|
|
|
|
|
>
|
2026-01-26 22:04:36 +09:00
|
|
|
<CardContent className="p-2 md:p-3">
|
2025-12-04 12:48:41 +09:00
|
|
|
<div className="flex items-center justify-between">
|
2026-01-26 22:04:36 +09:00
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<p className="text-[10px] md:text-xs text-muted-foreground mb-0.5 uppercase tracking-wide truncate">
|
2025-12-04 12:48:41 +09:00
|
|
|
{stat.label}
|
2026-02-15 23:18:45 +09:00
|
|
|
{stat.sublabel && (
|
|
|
|
|
<span className="ml-2 normal-case tracking-normal">{stat.sublabel}</span>
|
|
|
|
|
)}
|
2025-12-04 12:48:41 +09:00
|
|
|
</p>
|
2026-01-26 22:04:36 +09:00
|
|
|
<p className="font-bold text-base md:text-lg truncate">
|
2025-12-04 12:48:41 +09:00
|
|
|
{stat.value}
|
|
|
|
|
</p>
|
|
|
|
|
{stat.trend && (
|
2026-01-26 22:04:36 +09:00
|
|
|
<p className={`text-[9px] md:text-[10px] mt-0.5 font-medium ${stat.trend.isPositive ? 'text-green-600' : 'text-red-600'}`}>
|
2025-12-04 12:48:41 +09:00
|
|
|
{stat.trend.value}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{Icon && (
|
|
|
|
|
<Icon
|
2026-01-26 22:04:36 +09:00
|
|
|
className={`w-6 h-6 md:w-8 md:h-8 opacity-15 flex-shrink-0 ${stat.iconColor || 'text-blue-600'}`}
|
2025-12-04 12:48:41 +09:00
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|