Wrap next/link in a shared Link component that ships prefetch=false by default, so no route is ever prefetched (viewport or hover) anymore, and drop the DNS prefetch hint. Removes hidden background requests that were the source of intermittent issues.
81 lines
1.9 KiB
TypeScript
81 lines
1.9 KiB
TypeScript
import { Minus, TrendingDown, TrendingUp } from "lucide-react";
|
|
import Link from "@/components/link";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
|
|
interface StatsCardProps {
|
|
title: string;
|
|
value: string | number;
|
|
description?: string;
|
|
icon?: React.ReactNode;
|
|
href?: string;
|
|
/** Percentage change vs previous period. Positive = up, negative = down, undefined = no trend */
|
|
trend?: number;
|
|
}
|
|
|
|
export function StatsCard({
|
|
title,
|
|
value,
|
|
description,
|
|
icon,
|
|
href,
|
|
trend,
|
|
}: StatsCardProps) {
|
|
const content = (
|
|
<Card
|
|
className={
|
|
href
|
|
? "transition-all duration-200 hover:border-primary/50 hover:shadow-lg hover:shadow-primary/5 hover:-translate-y-0.5 cursor-pointer"
|
|
: ""
|
|
}
|
|
>
|
|
<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="flex items-baseline gap-2">
|
|
<div className="text-2xl font-bold">{value}</div>
|
|
{trend !== undefined && <TrendBadge value={trend} />}
|
|
</div>
|
|
{description && (
|
|
<p className="text-xs text-muted-foreground">{description}</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
|
|
if (href) {
|
|
return <Link href={href}>{content}</Link>;
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
function TrendBadge({ value }: { value: number }) {
|
|
if (value === 0) {
|
|
return (
|
|
<span className="inline-flex items-center gap-0.5 text-xs text-muted-foreground">
|
|
<Minus className="h-3 w-3" />
|
|
0%
|
|
</span>
|
|
);
|
|
}
|
|
|
|
const isUp = value > 0;
|
|
return (
|
|
<span
|
|
className={`inline-flex items-center gap-0.5 text-xs font-medium ${
|
|
isUp ? "text-[var(--admin-success)]" : "text-[var(--admin-error)]"
|
|
}`}
|
|
>
|
|
{isUp ? (
|
|
<TrendingUp className="h-3 w-3" />
|
|
) : (
|
|
<TrendingDown className="h-3 w-3" />
|
|
)}
|
|
{isUp ? "+" : ""}
|
|
{value}%
|
|
</span>
|
|
);
|
|
}
|