/* global React */
const { useState, useMemo, useEffect, useRef } = React;
// ============ Icons ============
const Icon = ({ d, size = 14, sw = 1.6, fill = "none" }) => (
);
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 };