"use client"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; import { TrendingUp, TrendingDown, Minus } from "lucide-react"; interface MetricCardProps { title: string; value: string | number | null | undefined; description?: string; icon?: React.ReactNode; trend?: { value: number; label?: string; isPositive?: boolean; }; variant?: "default" | "primary" | "success" | "warning" | "danger"; isLoading?: boolean; className?: string; } export default function MetricCard({ title, value, description, icon, trend, variant = "default", isLoading = false, className, }: MetricCardProps) { if (isLoading) { return (
); } const getVariantClasses = () => { switch (variant) { case "primary": return "border border-blue-100 bg-white shadow-sm hover:shadow-md"; case "success": return "border border-green-100 bg-white shadow-sm hover:shadow-md"; case "warning": return "border border-pink-100 bg-white shadow-sm hover:shadow-md"; case "danger": return "border border-red-100 bg-white shadow-sm hover:shadow-md"; default: return "border border-gray-100 bg-white shadow-sm hover:shadow-md"; } }; const getIconClasses = () => { return "bg-gray-50 text-gray-900 border-gray-100"; }; const getTrendIcon = () => { if (!trend) return null; if (trend.value === 0) { return ; } return trend.isPositive !== false ? ( ) : ( ); }; const getTrendColor = () => { if (!trend || trend.value === 0) return "text-muted-foreground"; return trend.isPositive !== false ? "text-green-600 dark:text-green-400" : "text-red-600 dark:text-red-400"; }; return (

{title}

{description && (

{description}

)}
{icon && (
{icon}
)}

{value ?? "—"}

{trend && ( {getTrendIcon()} {Math.abs(trend.value).toFixed(1)}% {trend.label && ` ${trend.label}`} )}
); }