import { useMemo } from 'react'; import { TrendingUp, TrendingDown, Minus } from 'lucide-react'; interface VitalGaugeProps { label: string; value: number; unit: string; optimal: number; min?: number; max?: number; history?: number[]; // Last N readings for sparkline decimals?: number; showTrend?: boolean; } // Generate mock history if not provided const generateMockHistory = (current: number, optimal: number, points: number = 12): number[] => { const history: number[] = []; const variance = optimal * 0.15; for (let i = 0; i < points; i++) { // Trend towards current value const progress = i / (points - 1); const base = optimal + (current - optimal) * progress; const noise = (Math.random() - 0.5) * variance; history.push(base + noise); } return history; }; export function VitalGauge({ label, value, unit, optimal, min, max, history, decimals = 0, showTrend = true }: VitalGaugeProps) { // Generate history if not provided const dataPoints = useMemo(() => { return history || generateMockHistory(value, optimal); }, [history, value, optimal]); // Calculate how close to optimal (0 = far, 1 = optimal) const diff = Math.abs(value - optimal); const maxDiff = optimal * 0.3; const score = Math.max(0, 1 - diff / maxDiff); // Determine status color const status = score > 0.7 ? 'good' : score > 0.4 ? 'warning' : 'critical'; const colorClasses = { good: { text: 'text-green-400', bg: 'bg-green-500', stroke: '#22c55e' }, warning: { text: 'text-yellow-400', bg: 'bg-yellow-500', stroke: '#eab308' }, critical: { text: 'text-red-400', bg: 'bg-red-500', stroke: '#ef4444' }, }; const colors = colorClasses[status]; // Calculate trend from history const trend = useMemo(() => { if (dataPoints.length < 2) return 'stable'; const recentHalf = dataPoints.slice(-Math.floor(dataPoints.length / 2)); const olderHalf = dataPoints.slice(0, Math.floor(dataPoints.length / 2)); const recentAvg = recentHalf.reduce((a, b) => a + b, 0) / recentHalf.length; const olderAvg = olderHalf.reduce((a, b) => a + b, 0) / olderHalf.length; const change = (recentAvg - olderAvg) / olderAvg; if (change > 0.05) return 'up'; if (change < -0.05) return 'down'; return 'stable'; }, [dataPoints]); // SVG sparkline dimensions const sparkWidth = 50; const sparkHeight = 16; const padding = 1; // Generate sparkline path const sparklinePath = useMemo(() => { if (dataPoints.length < 2) return ''; const minVal = min ?? Math.min(...dataPoints) * 0.9; const maxVal = max ?? Math.max(...dataPoints) * 1.1; const range = maxVal - minVal || 1; const points = dataPoints.map((val, i) => { const x = (i / (dataPoints.length - 1)) * (sparkWidth - padding * 2) + padding; const y = sparkHeight - padding - ((val - minVal) / range) * (sparkHeight - padding * 2); return `${x},${y}`; }); return `M${points.join(' L')}`; }, [dataPoints, min, max]); // Gradient fill path (area under line) const areaPath = useMemo(() => { if (!sparklinePath) return ''; return `${sparklinePath} L${sparkWidth - padding},${sparkHeight - padding} L${padding},${sparkHeight - padding} Z`; }, [sparklinePath]); return (