"use client" import { useEffect, useState } from "react" import { motion } from "framer-motion" import { cn } from "@/lib/utils" import { Card, CardContent } from "@/components/ui/card" import { LucideIcon } from "lucide-react" interface StatCardProps { title: string value: string | number icon: LucideIcon description?: string index?: number trend?: { pct: number; up: boolean } sparklineField?: "total" | "open" | "contacted" | "pending" | "closed" | "ignored" monthlyBreakdown?: { label: string; total: number; open: number; contacted: number; pending: number; closed: number; ignored: number }[] conversionRate?: number } const cardColors: Record = { "Total Leads": { bg: "bg-cyan-500/10", text: "text-cyan-600 dark:text-cyan-400", glow: "via-[rgba(6,182,212,0.25)]", accent: "#06b6d4" }, "Open Leads": { bg: "bg-blue-500/10", text: "text-blue-600 dark:text-blue-400", glow: "via-[rgba(59,130,246,0.25)]", accent: "#3b82f6" }, "Contacted": { bg: "bg-amber-500/10", text: "text-amber-600 dark:text-amber-400", glow: "via-[rgba(245,158,11,0.25)]", accent: "#f59e0b" }, "Pending": { bg: "bg-violet-500/10", text: "text-violet-600 dark:text-violet-400", glow: "via-[rgba(139,92,246,0.25)]", accent: "#8b5cf6" }, "Closed": { bg: "bg-yellow-500/10", text: "text-yellow-600 dark:text-yellow-400", glow: "via-[rgba(234,179,8,0.25)]", accent: "#eab308" }, "Conversion Rate": { bg: "bg-rose-500/10", text: "text-rose-600 dark:text-rose-400", glow: "via-[rgba(244,63,94,0.25)]", accent: "#f43f5e" }, } function computeGoal(max: number): number { if (max <= 0) return 100 return Math.ceil(max / 100) * 100 || 100 } function smoothPath(points: { x: number; y: number }[]): string { if (points.length === 0) return "" if (points.length === 1) return `M${points[0].x.toFixed(1)},${points[0].y.toFixed(1)}` let d = `M${points[0].x.toFixed(1)},${points[0].y.toFixed(1)}` for (let i = 1; i < points.length - 1; i++) { const p0 = points[i - 1] const p1 = points[i] const p2 = points[i + 1] const cp1x = p1.x - (p2.x - p0.x) * 0.15 const cp1y = p1.y - (p2.y - p0.y) * 0.15 const cp2x = p1.x + (p2.x - p0.x) * 0.15 const cp2y = p1.y + (p2.y - p0.y) * 0.15 d += `C${cp1x.toFixed(1)},${cp1y.toFixed(1)} ${cp2x.toFixed(1)},${cp2y.toFixed(1)} ${p2.x.toFixed(1)},${p2.y.toFixed(1)}` } return d } export function StatCard({ title, value, icon: Icon, description, index = 0, trend, sparklineField, monthlyBreakdown, conversionRate }: StatCardProps) { const color = cardColors[title] ?? cardColors["Total Leads"] const isNumeric = typeof value === "number" const [display, setDisplay] = useState(0) const target = typeof value === "number" ? value : 0 useEffect(() => { if (!isNumeric) return const duration = 1000 const steps = 40 const increment = target / steps let current = 0 const timer = setInterval(() => { current += increment if (current >= target) { setDisplay(target) clearInterval(timer) } else { setDisplay(Math.floor(current)) } }, duration / steps) return () => clearInterval(timer) }, [target, isNumeric]) function buildSparklineSvg(): { path: string; area: string; goal: number; gridLines: { y: number }[] } { if (!monthlyBreakdown || !sparklineField) return { path: "", area: "", goal: 100, gridLines: [] } const values = monthlyBreakdown.map((m) => m[sparklineField]).reverse() const max = Math.max(...values, 0) const goal = computeGoal(max) const dataRange = Math.max(max, 1) const range = goal <= 10 ? Math.max(dataRange * 2, 10) : Math.min(goal, Math.max(dataRange * 3, 10)) const w = values.length - 1 || 1 const pad = 4 const graphW = 60 - pad * 2 const graphH = 28 const points = values.map((v, i) => ({ x: pad + (i / w) * graphW, y: graphH - (v / range) * (graphH - 3) - 1, })) const smooth = smoothPath(points) const area = points.length > 0 ? `${smooth} L${points[points.length - 1].x.toFixed(1)},${graphH} L${points[0].x.toFixed(1)},${graphH} Z` : "" const steps = 4 const gridLines = Array.from({ length: steps - 1 }, (_, i) => ({ y: graphH - ((i + 1) / steps) * (graphH - 3) - 1, })) return { path: smooth, area, goal, gridLines } } const { path: sparkPath, area: sparkArea, goal, gridLines } = buildSparklineSvg() const sparkColor = color.accent return (

{title}

{isNumeric ? display : value}

{description && (

{description}

)} {trend && ( {trend.up ? "↑" : "↓"} {trend.pct}% )}
{sparkPath && (
{gridLines.map((gl, i) => ( ))} /{goal}
)} {title === "Conversion Rate" && conversionRate !== undefined && (

{conversionRate}% conversion rate

)}
) }