- KpiCardMobile: phone-sized KPI card with label/value/caption; tone='attention' adds destructive left border - NeedsAttentionStrip: horizontal-scroll strip of compact attention cards; renders null when items=[] - WorkerStatusRow: 3-cell status row with emerald/amber/destructive status dots, each linking to desktop admin - All three are pure presentational client components; no fetch, no recharts, no new state libraries
39 lines
1.3 KiB
TypeScript
39 lines
1.3 KiB
TypeScript
'use client';
|
||
|
||
/* KpiCardMobile — phase 03 (DASH-01).
|
||
*
|
||
* Phone-sized KPI card for the 2×2 dashboard grid. Renders a label,
|
||
* a large numeric value, and an optional caption. tone="attention"
|
||
* adds a left-edge destructive border for SLA breaches > 0.
|
||
*
|
||
* Pure presentational — no fetch, no state. Parent provides values. */
|
||
|
||
import { Card, CardContent } from '@/components/ui/card';
|
||
import { cn } from '@/lib/utils';
|
||
|
||
export type KpiTone = 'default' | 'attention';
|
||
|
||
interface KpiCardMobileProps {
|
||
label: string;
|
||
value: number | string;
|
||
caption?: string;
|
||
tone?: KpiTone;
|
||
}
|
||
|
||
const TONE_BORDER: Record<KpiTone, string> = {
|
||
default: 'border-l-transparent',
|
||
attention: 'border-l-destructive',
|
||
};
|
||
|
||
export function KpiCardMobile({ label, value, caption, tone = 'default' }: KpiCardMobileProps) {
|
||
const display = typeof value === 'number' ? value.toLocaleString() : value;
|
||
return (
|
||
<Card className={cn('h-full border-l-2', TONE_BORDER[tone])}>
|
||
<CardContent className="p-4 flex flex-col gap-1">
|
||
<p className="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider">{label}</p>
|
||
<p className="text-3xl font-bold tabular-nums">{display}</p>
|
||
{caption && <p className="text-xs text-muted-foreground">{caption}</p>}
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
}
|