/* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import type { ReactNode } from 'react' import { type LucideIcon } from 'lucide-react' import { cn } from '@/lib/utils' import { Skeleton } from '@/components/ui/skeleton' type StatCardTone = 'rose' | 'teal' | 'gray' interface StatCardProps { title: string value: string | number description: string icon: LucideIcon sparkline?: number[] tone?: StatCardTone loading?: boolean error?: boolean action?: ReactNode } const TONE_CLASSES: Record = { rose: 'from-rose-500/80 via-rose-300/70 to-rose-200/20 dark:from-rose-400/70 dark:via-rose-500/30 dark:to-rose-500/5', teal: 'from-teal-500/80 via-teal-300/70 to-teal-200/20 dark:from-teal-400/70 dark:via-teal-500/30 dark:to-teal-500/5', gray: 'from-muted-foreground/50 via-muted-foreground/20 to-transparent dark:from-muted-foreground/40 dark:via-muted-foreground/20', } function normalizeSparkline(values?: number[]): number[] { if (!values?.length) return [] const sanitized = values.map((value) => Math.max(0, Number(value) || 0)) const max = Math.max(...sanitized) if (max <= 0) return sanitized.map(() => 0) return sanitized.map((value) => Math.max(8, (value / max) * 100)) } export function StatCard(props: StatCardProps) { const Icon = props.icon const tone = props.tone ?? 'gray' const sparkline = normalizeSparkline(props.sparkline) return (
{props.action &&
{props.action}
}
{props.loading ? (
) : props.error ? (
--

{props.description}

) : (
{props.value}

{props.description}

)}
) }