87 lines
3.2 KiB
TypeScript
87 lines
3.2 KiB
TypeScript
import { Badge } from '@/components/ui/badge';
|
|
import {
|
|
Card,
|
|
CardDescription,
|
|
CardFooter,
|
|
CardHeader,
|
|
CardTitle,
|
|
CardAction,
|
|
} from '@/components/ui/card';
|
|
import { formatCurrency, formatNumber } from '@/lib/formatters';
|
|
import { cn } from '@/lib/utils';
|
|
import type { StatItem } from '@/types';
|
|
import { TrendingDown, TrendingUp } from 'lucide-react';
|
|
|
|
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 relative', className)}>
|
|
<CardHeader className="gap-3">
|
|
<div className="flex w-full items-start justify-between gap-3">
|
|
<CardDescription className="min-w-0 flex-1 break-words leading-tight">
|
|
{title}
|
|
</CardDescription>
|
|
{hasComparison && (
|
|
<Badge
|
|
variant="default"
|
|
className={`shrink-0 flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-bold sm:text-xs ${
|
|
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>
|
|
)}
|
|
</div>
|
|
|
|
<CardTitle className="break-words text-xl sm:text-2xl font-bold tabular-nums tracking-tight">
|
|
{isPercentage
|
|
? `${stat.value}%`
|
|
: isCurrency
|
|
? formatCurrency(stat.value)
|
|
: formatNumber(stat.value)}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
|
|
{hasComparison && (
|
|
<CardFooter className="flex-col items-start gap-1.5 text-xs">
|
|
<div className="w-full break-words text-muted-foreground leading-relaxed">
|
|
Kemarin:{' '}
|
|
<span className="font-semibold break-words inline-block">
|
|
{isPercentage
|
|
? `${stat.yesterday}%`
|
|
: isCurrency
|
|
? formatCurrency(stat.yesterday)
|
|
: formatNumber(stat.yesterday)}
|
|
</span>
|
|
</div>
|
|
</CardFooter>
|
|
)}
|
|
</Card>
|
|
);
|
|
}
|