56 lines
2.4 KiB
TypeScript
56 lines
2.4 KiB
TypeScript
import { TrendingUp, TrendingDown } from 'lucide-react';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Card, CardHeader, CardTitle, CardDescription, CardAction, CardFooter } from '@/components/ui/card';
|
|
import { formatCurrency, formatNumber } from '@/lib/formatters';
|
|
import { cn } from '@/lib/utils';
|
|
import type { StatItem } from '@/types';
|
|
|
|
export interface StatCardProps {
|
|
title: string;
|
|
stat: StatItem;
|
|
isCurrency?: boolean;
|
|
isPercentage?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
export function StatCard({
|
|
title,
|
|
stat,
|
|
isCurrency = true,
|
|
isPercentage = false,
|
|
className
|
|
}: StatCardProps) {
|
|
const hasComparison = stat.yesterday !== null;
|
|
const isPositive = stat.change !== null && stat.change >= 0;
|
|
|
|
return (
|
|
<Card className={cn("@container/card", className)}>
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<CardDescription>{title}</CardDescription>
|
|
</div>
|
|
<CardTitle className="text-2xl font-bold tabular-nums @[250px]/card:text-3xl">
|
|
{isPercentage ? `${stat.value}%` : (isCurrency ? formatCurrency(stat.value) : formatNumber(stat.value))}
|
|
</CardTitle>
|
|
{hasComparison && (
|
|
<CardAction>
|
|
<Badge variant="default" className={`flex gap-1 px-1.5 py-0.5 text-xs font-bold ${isPositive ? 'bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300' : 'bg-red-50 text-red-700 dark:bg-red-950 dark:text-red-300'}`}>
|
|
{isPositive ? <TrendingUp className="size-3" /> : <TrendingDown className="size-3" />}
|
|
{stat.change === null
|
|
? '∞'
|
|
: `${Math.abs(stat.change)}%`}
|
|
</Badge>
|
|
</CardAction>
|
|
)}
|
|
</CardHeader>
|
|
{hasComparison && (
|
|
<CardFooter className="flex-col items-start gap-1.5 text-xs">
|
|
<div className="text-muted-foreground">
|
|
Kemarin: <span className="font-semibold">{isPercentage ? `${stat.yesterday}%` : (isCurrency ? formatCurrency(stat.yesterday) : formatNumber(stat.yesterday))}</span>
|
|
</div>
|
|
</CardFooter>
|
|
)}
|
|
</Card>
|
|
);
|
|
}
|