import React from 'react'; import { Card } from '../ui/card'; import { ArrowUp, ArrowDown } from 'lucide-react'; interface StatCardProps { label: string; value: string | number; icon: React.ReactNode; trend?: string | number; // String like "+12%" or raw number color?: 'cyan' | 'magenta' | 'lime' | 'gold' | 'red'; sparklineData?: number[]; } export const StatCard: React.FC = ({ label, value, icon, trend, color = 'cyan', sparklineData, }) => { /* Semantic tokens (audit P3: unify kodo-* → primary/muted/success/warning/destructive) */ const colorMap = { cyan: 'text-primary', magenta: 'text-secondary', lime: 'text-success', gold: 'text-warning', red: 'text-destructive', }; const bgMap = { cyan: 'bg-primary/10', magenta: 'bg-secondary/10', lime: 'bg-success/10', gold: 'bg-warning/10', red: 'bg-destructive/10', }; const renderSparkline = (data: number[]) => { if (!data || data.length < 2) return null; const min = Math.min(...data); const max = Math.max(...data); const range = max - min || 1; const width = 100; const height = 40; const getPathData = (d: number[]) => { const points = d.map((val, i) => ({ x: (i / (d.length - 1)) * width, y: height - ((val - min) / range) * height, })); let pathStr = `M ${points[0].x},${points[0].y}`; for (let i = 0; i < points.length - 1; i++) { const curr = points[i]; const next = points[i + 1]; const mx = (curr.x + next.x) / 2; pathStr += ` C ${mx},${curr.y} ${mx},${next.y} ${next.x},${next.y}`; } return pathStr; }; return ( ); }; const isPositive = typeof trend === 'string' ? !trend.startsWith('-') : (trend || 0) >= 0; const trendValue = typeof trend === 'number' ? `${Math.abs(trend)}%` : trend; return (

{label}

{value}

{icon}
{trend && (
{isPositive ? ( ) : ( )} {trendValue} vs last period
)}
{sparklineData && (
{renderSparkline(sparklineData)}
)}
); };