// ======================================== // StatCard Component // ======================================== // Reusable stat card for dashboard metrics import * as React from 'react'; import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils'; import { Card, CardContent } from '@/components/ui/Card'; import { TrendingUp, TrendingDown, Minus, type LucideIcon } from 'lucide-react'; import { Sparkline } from '@/components/charts/Sparkline'; const statCardVariants = cva( 'transition-all duration-200 hover:shadow-md hover-glow', { variants: { variant: { default: 'border-border', primary: 'border-primary/30 bg-primary/5', success: 'border-success/30 bg-success/5', warning: 'border-warning/30 bg-warning/5', danger: 'border-destructive/30 bg-destructive/5', info: 'border-info/30 bg-info/5', }, }, defaultVariants: { variant: 'default', }, } ); const iconContainerVariants = cva( 'flex h-10 w-10 items-center justify-center rounded-lg', { variants: { variant: { default: 'bg-muted text-muted-foreground', primary: 'bg-primary/10 text-primary', success: 'bg-success/10 text-success', warning: 'bg-warning/10 text-warning', danger: 'bg-destructive/10 text-destructive', info: 'bg-info/10 text-info', }, }, defaultVariants: { variant: 'default', }, } ); export interface StatCardProps extends React.HTMLAttributes, VariantProps { /** Card title */ title: string; /** Stat value to display */ value: number | string; /** Optional icon component */ icon?: LucideIcon; /** Optional trend direction */ trend?: 'up' | 'down' | 'neutral'; /** Optional trend value (e.g., "+12%") */ trendValue?: string; /** Loading state */ isLoading?: boolean; /** Optional description */ description?: string; /** Optional sparkline data (e.g., last 7 days) */ sparklineData?: number[]; /** Whether to show sparkline */ showSparkline?: boolean; } /** * StatCard component for displaying dashboard metrics * * @example * ```tsx * * ``` */ export function StatCard({ className, variant, title, value, icon: Icon, trend, trendValue, isLoading = false, description, sparklineData, showSparkline = false, ...props }: StatCardProps) { const TrendIcon = trend === 'up' ? TrendingUp : trend === 'down' ? TrendingDown : Minus; const trendColor = trend === 'up' ? 'text-success' : trend === 'down' ? 'text-destructive' : 'text-muted-foreground'; return (

{title}

{isLoading ? (
) : (

{typeof value === 'number' ? value.toLocaleString() : value}

)} {trend && trendValue && !isLoading && ( {trendValue} )}
{description && (

{description}

)} {showSparkline && sparklineData && sparklineData.length > 0 && (
)}
{Icon && (
)}
); } /** * Skeleton loader for StatCard */ export function StatCardSkeleton({ className }: { className?: string }) { return (
); }