// Shared small SVG components for Raudhah KPI dashboard const { useState, useEffect, useMemo, useRef } = React; function Sparkline({ data, color = "var(--raudhah-pink)", fill = true, height = 34, strokeWidth = 1.6 }) { const ref = useRef(null); const [w, setW] = useState(120); useEffect(() => { if (!ref.current) return; const ro = new ResizeObserver(() => setW(ref.current.clientWidth)); ro.observe(ref.current); setW(ref.current.clientWidth || 120); return () => ro.disconnect(); }, []); const clean = data.filter(v => v != null); if (!clean.length) return
; const min = Math.min(...clean), max = Math.max(...clean); const range = max - min || 1; const n = data.length; const pts = data.map((v, i) => { if (v == null) return null; const x = (i / (n - 1)) * (w - 4) + 2; const y = height - 2 - ((v - min) / range) * (height - 4); return [x, y]; }); const firstIdx = pts.findIndex(p => p); let d = ""; let lastIdx = -1; pts.forEach((p, i) => { if (!p) return; d += (lastIdx < 0 ? `M${p[0]},${p[1]}` : ` L${p[0]},${p[1]}`); lastIdx = i; }); const last = pts[lastIdx]; const fillPath = firstIdx >= 0 && last ? `${d} L${last[0]},${height} L${pts[firstIdx][0]},${height} Z` : ""; return (
{fill && } {last && }
); } function BranchChip({ branch, size = "sm" }) { if (!branch) return null; const pad = size === "xs" ? "2px 5px" : "3px 6px"; const fs = size === "xs" ? 10 : 11; return ( {branch.code} ); } function Ring({ value, target, size = 70, stroke = 7, color = "var(--raudhah-pink)", track = "var(--panel-sunken)", label = true }) { const pct = Math.min(1.2, value / target); const pctDisplay = Math.round((value / target) * 100); const r = (size - stroke) / 2; const c = 2 * Math.PI * r; const offset = c * (1 - Math.min(1, pct)); return (
{label && (
{pctDisplay}%
)}
); } function Bullet({ label, value, target, target2, color = "var(--raudhah-pink)", unit = "", onClick }) { const max = Math.max(target2 || 0, target, value) * 1.05; const pct = (value / max) * 100; const tickPct = (target / max) * 100; const tick2Pct = target2 ? (target2 / max) * 100 : null; const hit = value >= target; return (
{label} {value.toLocaleString()} / {target.toLocaleString()}{unit} {" "} {hit ? "HIT" : Math.round((value/target)*100) + "%"}
{tick2Pct &&
}
); } function Delta({ value, pct, unit = "", inverse = false }) { const up = value > 0; const flat = value === 0; const cls = flat ? "flat" : (up ? (inverse ? "down" : "up") : (inverse ? "up" : "down")); const arrow = flat ? "—" : (up ? "▲" : "▼"); return ( {arrow} {up ? "+" : ""}{value.toLocaleString()}{unit} {pct != null && {pct > 0 ? "+" : ""}{pct}%} ); } function StatusPill({ kind, children }) { return ( {children} ); } Object.assign(window, { Sparkline, BranchChip, Ring, Bullet, Delta, StatusPill });