import { cn } from "@/lib/utils"; import { LucideIcon } from "lucide-react"; import { useEffect, useState } from "react"; interface StatCardProps { title: string; value: number; suffix?: string; icon: LucideIcon; trend?: { value: number; isPositive: boolean; }; variant?: "default" | "positive" | "negative" | "neutral"; delay?: number; } export function StatCard({ title, value, suffix = "", icon: Icon, trend, variant = "default", delay = 0, }: StatCardProps) { const [displayValue, setDisplayValue] = useState(0); const [isVisible, setIsVisible] = useState(false); useEffect(() => { const timer = setTimeout(() => { setIsVisible(true); }, delay); return () => clearTimeout(timer); }, [delay]); useEffect(() => { if (!isVisible) return; const duration = 1200; const steps = 40; const stepValue = value / steps; let current = 0; let step = 0; const timer = setInterval(() => { step++; // Easing function for smooth animation const progress = step / steps; const eased = 1 - Math.pow(1 - progress, 3); current = value * eased; if (step >= steps) { setDisplayValue(value); clearInterval(timer); } else { setDisplayValue(Math.floor(current)); } }, duration / steps); return () => clearInterval(timer); }, [value, isVisible]); const variantStyles = { default: "bg-card border-border", positive: "bg-sentiment-positive-light border-sentiment-positive/20", negative: "bg-sentiment-negative-light border-sentiment-negative/20", neutral: "bg-sentiment-neutral-light border-sentiment-neutral/20", }; const iconStyles = { default: "bg-primary/10 text-primary", positive: "bg-sentiment-positive/10 text-sentiment-positive", negative: "bg-sentiment-negative/10 text-sentiment-negative", neutral: "bg-sentiment-neutral/10 text-sentiment-neutral", }; return (

{title}

{displayValue.toLocaleString()} {suffix && ( {suffix} )}
{trend && (
{trend.isPositive ? "+" : "-"}{Math.abs(trend.value)}% dari periode lalu
)}
); }