/* global React, PR, PRCover */
const { useState, useMemo } = React;
const { I, Avatar, StagePill, DecisionPill, RiskDot, daysAgo, metricDelta } = PR;
const { Cover } = PRCover;
// ============ Table view ============
function TableView({ data, onOpen }) {
const authorMap = Object.fromEntries(data.authors.map(a => [a.id, a]));
const rows = data.hypotheses;
return (
| ID |
Hypothesis |
Stage |
Genre |
Author |
Key Metric |
Risk |
Updated |
Decision |
{rows.map(h => {
const a = authorMap[h.author];
const m = metricDelta(h);
const archived = h.decision === "kill";
return (
onOpen && onOpen(h.id)}>
| {h.id} |
{h.title}
{h.pivotedFrom && ↳ pivot from {h.pivotedFrom} }
{h.pivotedTo && → pivoted to {h.pivotedTo} }
|
|
{h.tags.slice(0, 2).map(t => {t})}
|
|
{h.keyMetric}
{m.display}
/ {h.target}
|
|
{daysAgo(h.updated)} |
|
);
})}
);
}
// ============ Kanban view ============
function KanbanView({ data, onOpen, onNew, onMove }) {
const authorMap = Object.fromEntries(data.authors.map(a => [a.id, a]));
const [dragOver, setDragOver] = useState(null);
const byStage = useMemo(() => {
const out = {};
data.stages.forEach(s => out[s.id] = []);
data.hypotheses.forEach(h => {
if (h.decision === "kill") return;
out[h.stage]?.push(h);
});
return out;
}, [data]);
const handleDragStart = (e, rndId) => {
e.dataTransfer.setData("text/plain", rndId);
e.dataTransfer.effectAllowed = "move";
};
const handleDragOver = (e, stageId) => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
setDragOver(stageId);
};
const handleDrop = (e, stageId) => {
e.preventDefault();
setDragOver(null);
const rndId = e.dataTransfer.getData("text/plain");
if (rndId && onMove) onMove(rndId, stageId);
};
return (
{data.stages.map(s => {
const items = byStage[s.id] || [];
const isDropTarget = dragOver === s.id;
return (
handleDragOver(e, s.id)}
onDragLeave={() => setDragOver(null)}
onDrop={(e) => handleDrop(e, s.id)}
>
{items.map(h => {
const a = authorMap[h.author];
const m = metricDelta(h);
return (
handleDragStart(e, h.id)}
onClick={() => onOpen && onOpen(h.id)}
>
{h.coverUrl && (
)}
{h.id}
{h.title}
{h.tags.map(t => {t})}
{h.keyMetric}: {m.display}
);
})}
{items.length === 0 && (
{s.desc}
)}
);
})}
);
}
// ============ Cards view ============
function CardsView({ data, onOpen }) {
const authorMap = Object.fromEntries(data.authors.map(a => [a.id, a]));
const rows = data.hypotheses.filter(h => h.decision !== "kill");
return (
{rows.map(h => {
const a = authorMap[h.author];
const m = metricDelta(h);
return (
onOpen && onOpen(h.id)}>
{h.id} · {daysAgo(h.updated)}
{h.title}
{h.coreLoop}
{h.tags.map(t => {t})}
{h.keyMetric}{" · "}
{m.display}
/ {h.target}
);
})}
);
}
window.PRViews = { TableView, KanbanView, CardsView };