/* global React */ const { useState, useMemo, useEffect, useRef } = React; // ============ Icons ============ const Icon = ({ d, size = 14, sw = 1.6, fill = "none" }) => ( {typeof d === "string" ? : d} ); const I = { list: , grid: } />, kanban: } />, chart: } />, inbox: } />, archive: } />, search: } />, plus: , filter: , sort: } />, sun: } size={12} />, moon: , star: , arrow: , link: , check: , cmd: } />, clock: } />, user: } />, doc: } />, flag: } />, users: } />, avatar: } />, }; // ============ Utilities ============ const Avatar = ({ author }) => { if (!author) return null; const initials = author.name.split(/[\s.]+/).filter(Boolean).map(s => s[0]).join("").slice(0, 2).toUpperCase(); return {initials}; }; const StagePill = ({ stage, stages }) => { const s = stages.find(x => x.id === stage); if (!s) return null; return ( {s.label} ); }; const DecisionPill = ({ decision }) => { if (!decision) return ; const labels = { kill: "Killed", pivot: "Pivoted", scale: "Scaled" }; return {labels[decision]}; }; const RiskDot = ({ risk }) => { const labels = { low: "Low", med: "Medium", high: "High" }; return {labels[risk]}; }; const formatDate = (s) => { const d = new Date(s); return d.toLocaleDateString("en-US", { month: "short", day: "numeric" }); }; const daysAgo = (s) => { const d = new Date(s); const diff = Math.floor((Date.now() - d.getTime()) / 86400000); if (diff <= 0) return "today"; if (diff === 1) return "1d ago"; if (diff < 30) return diff + "d ago"; return Math.floor(diff / 30) + "mo ago"; }; // Compute delta good/bad const metricDelta = (h) => { if (!h.current || h.current === "—") return { display: h.current || "—", className: "" }; // try to parse % vs %, $ vs $, etc. const tNum = parseFloat((h.target || "").replace(/[^0-9.\-]/g, "")); const cNum = parseFloat((h.current || "").replace(/[^0-9.\-]/g, "")); if (isNaN(tNum) || isNaN(cNum)) return { display: h.current, className: "" }; // For CPI: lower is better (≤). For retention/session: higher is better (≥). const isLowerBetter = (h.target || "").includes("≤") || /CPI/i.test(h.keyMetric || ""); const good = isLowerBetter ? cNum <= tNum : cNum >= tNum; return { display: h.current, className: good ? "delta-good" : "delta-bad" }; }; window.PR = { Icon, I, Avatar, StagePill, DecisionPill, RiskDot, formatDate, daysAgo, metricDelta };