77 lines
2.0 KiB
TypeScript
77 lines
2.0 KiB
TypeScript
|
|
"use client"
|
||
|
|
|
||
|
|
import type { ReactNode } from "react"
|
||
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/app/components/ui/card"
|
||
|
|
import { TrendingUp, TrendingDown, Minus } from "lucide-react"
|
||
|
|
|
||
|
|
interface StatCardProps {
|
||
|
|
title: string
|
||
|
|
value: string | number
|
||
|
|
icon?: ReactNode
|
||
|
|
change?: {
|
||
|
|
value: string | number
|
||
|
|
type: "increase" | "decrease" | "neutral"
|
||
|
|
label?: string
|
||
|
|
}
|
||
|
|
description?: string
|
||
|
|
}
|
||
|
|
|
||
|
|
export function StatCard({ title, value, icon, change, description }: StatCardProps) {
|
||
|
|
const getChangeIcon = () => {
|
||
|
|
switch (change?.type) {
|
||
|
|
case "increase":
|
||
|
|
return <TrendingUp className="h-3 w-3" />
|
||
|
|
case "decrease":
|
||
|
|
return <TrendingDown className="h-3 w-3" />
|
||
|
|
default:
|
||
|
|
return <Minus className="h-3 w-3" />
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const getChangeColor = () => {
|
||
|
|
switch (change?.type) {
|
||
|
|
case "increase":
|
||
|
|
return "text-green-600"
|
||
|
|
case "decrease":
|
||
|
|
return "text-red-600"
|
||
|
|
default:
|
||
|
|
return "text-muted-foreground"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Card>
|
||
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||
|
|
<CardTitle className="text-sm font-medium">{title}</CardTitle>
|
||
|
|
{icon && <div className="text-muted-foreground">{icon}</div>}
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<div className="text-2xl font-bold">{value}</div>
|
||
|
|
{change && (
|
||
|
|
<div className={`flex items-center text-xs ${getChangeColor()}`}>
|
||
|
|
{getChangeIcon()}
|
||
|
|
<span className="ml-1">
|
||
|
|
{change.value} {change.label}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
{description && <p className="text-xs text-muted-foreground mt-1">{description}</p>}
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
interface StatCardGroupProps {
|
||
|
|
cards: StatCardProps[]
|
||
|
|
}
|
||
|
|
|
||
|
|
export function StatCardGroup({ cards }: StatCardGroupProps) {
|
||
|
|
return (
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||
|
|
{cards.map((card, index) => (
|
||
|
|
<StatCard key={index} {...card} />
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|