metric-card.tsx 1.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. "use client"
  2. import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"
  3. import CountUp from "react-countup"
  4. import {cn} from "@/lib/utils"
  5. import type {ReactNode} from "react"
  6. type MetricCardProps = {
  7. title: string
  8. value: number
  9. suffix?: string
  10. icon?: ReactNode
  11. className?: string
  12. trend?: { delta: number; label?: string } // delta in percent
  13. }
  14. export default function MetricCard({
  15. title,
  16. value,
  17. suffix,
  18. icon,
  19. className,
  20. trend,
  21. }: MetricCardProps) {
  22. const trendColor =
  23. trend && trend.delta !== 0
  24. ? trend.delta > 0
  25. ? "text-emerald-600"
  26. : "text-rose-600"
  27. : "text-muted-foreground"
  28. return (
  29. <Card className={cn("h-full", className)}>
  30. <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
  31. <CardTitle className="text-sm font-medium">{title}</CardTitle>
  32. {icon}
  33. </CardHeader>
  34. <CardContent>
  35. <div className="text-2xl font-bold">
  36. <CountUp end={value} duration={1.2} separator="," />
  37. {suffix ? <span className="ml-1 text-base font-medium text-muted-foreground">{suffix}</span> : null}
  38. </div>
  39. {trend ? (
  40. <p className={cn("text-xs mt-1", trendColor)}>
  41. {trend.delta > 0 ? "↑" : trend.delta < 0 ? "↓" : "—"} {Math.abs(trend.delta)}%{" "}
  42. {trend.label ? <span className="text-muted-foreground">· {trend.label}</span> : null}
  43. </p>
  44. ) : null}
  45. </CardContent>
  46. </Card>
  47. )
  48. }