Files
dyolink/frontend/src/components/today/KpiCard.tsx

63 lines
1.9 KiB
TypeScript
Raw Normal View History

import { Link } from '@/i18n/navigation';
import { Card } from '@/components/ui/shared/Card';
import type { KpiCardColor } from '@/components/today/widget-registry';
import type { LucideIcon } from 'lucide-react';
const colorClasses: Record<KpiCardColor, string> = {
blue: '!bg-purpose-visit-bg !text-purpose-visit-fg !border-purpose-visit-border',
yellow: '!bg-badge-warning-bg !text-badge-warning-fg !border-badge-warning-border',
green: '!bg-badge-success-bg !text-badge-success-fg !border-badge-success-border',
red: '!bg-badge-danger-bg !text-badge-danger-fg !border-badge-danger-border',
purple: '!bg-purpose-consultation-bg !text-purpose-consultation-fg !border-purpose-consultation-border',
default: '',
};
interface KpiCardProps {
title: string;
value: string;
subtitle?: string | null;
icon?: LucideIcon;
color?: KpiCardColor;
loading?: boolean;
href?: string;
}
export function KpiCard({
title,
value,
subtitle,
icon: Icon,
color = 'default',
loading = false,
href,
}: KpiCardProps) {
const card = (
<Card
className={`${colorClasses[color]} ${href && !loading ? 'transition-opacity hover:opacity-90' : ''}`}
>
<div className="flex items-start justify-between gap-3">
<p className="text-sm font-medium">{title}</p>
{Icon ? <Icon className="h-4 w-4 shrink-0 opacity-80" aria-hidden /> : null}
</div>
{loading ? (
<div className="mt-2 h-8 w-16 animate-pulse rounded bg-current/10" />
) : (
<p className="text-2xl font-bold mt-2">{value}</p>
)}
{subtitle ? (
<p className="text-xs opacity-80 mt-1">{subtitle}</p>
) : null}
</Card>
);
if (href && !loading) {
return (
<Link href={href} className="block focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60 rounded-[var(--radius-lg)]">
{card}
</Link>
);
}
return card;
}