All files / components/dashboard/kpi-card kpi-card.tsx

100% Statements 7/7
80% Branches 8/10
100% Functions 1/1
100% Lines 7/7

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69                        1x                 2x 2x 2x 2x   2x     2x                                                                              
import {
    Activity,
    ArrowDownRight,
    ArrowUpRight,
    BadgePercent,
    CircleDollarSign,
    UsersRound,
} from "lucide-react";
import {uiStyles} from "@/components/ui/style-primitives";
import {cn, formatDelta, formatMetricValue} from "@/lib/utils/utils";
import type {Metric} from "@/types/dashboard";
 
const iconByMetric: Record<string, typeof CircleDollarSign> = {
    "active-accounts": UsersRound,
    churn: Activity,
    conversion: BadgePercent,
    mrr: CircleDollarSign,
};
 
/** KPI card for one executive metric, including trend direction and outcome color. */
export function KpiCard({metric}: { metric: Metric }) {
    const Icon = iconByMetric[metric.id] ?? Activity;
    const TrendIcon = metric.trend === "down" ? ArrowDownRight : ArrowUpRight;
    const captionId = `kpi-${metric.id}-caption`;
    const headingId = `kpi-${metric.id}-heading`;
    const isPositiveOutcome =
        (metric.trend === "up" && metric.delta >= 0) ||
        (metric.trend === "down" && metric.delta < 0);
 
    return (
        <article
            aria-describedby={captionId}
            aria-labelledby={headingId}
            className={cn("p-4", uiStyles.surface)}>
            <div className="flex items-start justify-between gap-3">
                <div className="min-w-0">
                    <h3 className="text-sm font-medium text-slate-500 dark:text-slate-400" id={headingId}>
                        {metric.label}
                    </h3>
                    <p className="mt-3 text-3xl font-semibold text-slate-950 dark:text-white">
                        {formatMetricValue(metric)}
                    </p>
                </div>
                <div className={uiStyles.iconFrame}>
                    <Icon aria-hidden="true" size={20}/>
                </div>
            </div>
            <div className="mt-4 flex items-center gap-2">
        <span
            aria-label={`${formatDelta(metric.delta)} versus last month`}
            className={cn(
                "inline-flex h-7 items-center gap-1 rounded-md px-2 text-sm font-semibold",
                isPositiveOutcome
                    ? "bg-emerald-50 text-emerald-700 dark:bg-emerald-950/30 dark:text-emerald-200"
                    : "bg-rose-50 text-rose-700 dark:bg-rose-950/30 dark:text-rose-200",
            )}
        >
          <TrendIcon aria-hidden="true" size={15}/>
            {formatDelta(metric.delta)}
        </span>
                <span className="text-sm text-slate-500 dark:text-slate-400">vs last month</span>
            </div>
            <p className="mt-3 min-h-10 text-sm leading-5 text-slate-600 dark:text-slate-300" id={captionId}>
                {metric.caption}
            </p>
        </article>
    );
}