// Shared UI primitives used across screens const { useState, useEffect, useMemo, useRef } = React; const Avatar = ({ user, size = 28 }) => (
{user.avatar}
); const Pill = ({ status, children, className = "" }) => { const c = status ? status.toLowerCase() : ""; return {children || status}; }; const KPI = ({ label, value, delta, deltaDir, sub }) => (
{label}
{value}
{delta &&
{delta}
} {sub &&
{sub}
}
); const PageHeader = ({ title, sub, right }) => (

{title}

{sub &&
{sub}
}
{right &&
{right}
}
); const Card = ({ title, sub, right, children, foot, style }) => (
{(title || right) && (
{title &&
{title}
} {sub &&
{sub}
}
{right &&
{right}
}
)} {children} {foot &&
{foot}
}
); const FilterChip = ({ keyLabel, value, set, onClear, onClick }) => ( ); const Stepper = ({ steps, current }) => (
{steps.map((s, i) => (
{i < current ? "✓" : i + 1}
{s}
{i < steps.length - 1 &&
} ))}
); const Tabs = ({ tabs, current, onChange }) => (
{tabs.map((t) => ( ))}
); const SlideOver = ({ open, onClose, title, sub, children, foot, wide }) => { if (!open) return null; return ( <>
{title}
{sub &&
{sub}
}
{children}
{foot &&
{foot}
}
); }; const Toast = ({ msg, onClose }) => { useEffect(() => { if (!msg) return; const t = setTimeout(onClose, 3000); return () => clearTimeout(t); }, [msg]); if (!msg) return null; return (
{msg}
); }; const MiniBars = ({ data, highlight }) => (
{data.map((v, i) => (
))}
); // Field with label const Field = ({ label, required, hint, children, span }) => (
{children} {hint &&
{hint}
}
); // Format helpers const fmt = { num(n, d = 2) { if (n == null || isNaN(n)) return "—"; return Number(n).toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d }); }, usd(n) { return "$" + fmt.num(n, 2); }, date(s) { if (!s) return "—"; return s.length > 10 ? s : s; }, }; // Sparkline SVG (rolling time series) const Sparkline = ({ data, w = 80, h = 22, color = "var(--teal)", fill = false, strokeWidth = 1.4 }) => { if (!data || data.length === 0) return null; const min = Math.min(...data), max = Math.max(...data); const range = max - min || 1; const points = data.map((d, i) => `${(i / (data.length - 1)) * w},${h - ((d - min) / range) * (h - 2) - 1}`).join(" "); return ( {fill && } ); }; // Horizontal pace gauge — value vs target const PaceGauge = ({ value, target, width = 160 }) => { const pct = Math.max(0, Math.min(1.2, value / target)); const color = pct < 0.95 ? "var(--red)" : pct < 1.05 ? "var(--amber)" : "var(--green)"; return (
{(pct * 100).toFixed(0)}% target {fmt.num(target / 1000, 0)}k
); }; // Donut / progress ring const Ring = ({ pct, size = 56, stroke = 6, color = "var(--teal)", track = "var(--paper-2)", label }) => { const r = (size - stroke) / 2; const c = 2 * Math.PI * r; const off = c * (1 - Math.max(0, Math.min(1, pct / 100))); return (
48 ? 12 : 10 }}>{label || `${Math.round(pct)}%`}
); }; Object.assign(window, { Avatar, Pill, KPI, PageHeader, Card, FilterChip, Stepper, Tabs, SlideOver, Toast, MiniBars, Field, fmt, Sparkline, PaceGauge, Ring });