import { Link } from "react-router";
import type { AgendaMove } from "../../../hooks/lib/agenda-store";
import type { HandoffCard } from "../data";
import type { MatrixItem } from "../matrix";
import { Badge } from "./components/badge";
import { NativeSelect } from "./components/input";
import { age } from "./format";
import { Empty } from "./frame";
import { cn } from "./lib/cn";
import { setPlacement, setServes } from "./lib/write";
const SERVES_KINDS = ["goal", "revenue", "fun"] as const;
function ServesSelect({ item, onSaved }: { item: MatrixItem; onSaved: () => void }) {
if (item.kind !== "project") return null;
return (
{
void setServes(item.id, e.target.value).then(onSaved);
}}
className={cn(
"h-auto py-0.5 text-[10px]",
item.servesBy === "user" && "border-accent bg-accent-100 text-accent-900"
)}
>
unranked
{SERVES_KINDS.map((kind) => (
{kind}
))}
);
}
const PLACEMENTS = [
{ value: "", label: "let PAL place it" },
{ value: "now", label: "Do now" },
{ value: "plan", label: "Give it a slot" },
{ value: "noise", label: "Loud, not load-bearing" },
{ value: "later", label: "Let it sit" },
] as const;
/** Overrules the grid's own reading. The correction survives the next guess. */
function PlacementSelect({ item, onSaved }: { item: MatrixItem; onSaved: () => void }) {
if (item.kind !== "project") return null;
return (
{
void setPlacement(item.id, e.target.value || null).then(onSaved);
}}
className={cn(
"h-auto py-0.5 text-[10px]",
item.placed && "border-accent bg-accent-100 text-accent-900"
)}
>
{PLACEMENTS.map((p) => (
{p.label}
))}
);
}
export function ReasonTags({ item }: { item: MatrixItem }) {
return (
{item.urgentBecause.map((reason) => (
{reason}
))}
{item.importantBecause}
);
}
export function ItemCard({ item, onSaved }: { item: MatrixItem; onSaved: () => void }) {
const isProject = item.kind === "project";
return (
{isProject ? (
{item.label}
) : (
{item.label}
)}
{isProject ? (
) : (
goal
)}
{item.due ?? ""}
{item.waitingOn && (
waiting on you · {item.waitingOn}
)}
{item.detail && {item.detail}
}
{isProject && (
)}
);
}
export function MovesList({
moves,
size = "compact",
}: {
moves: AgendaMove[];
size?: "compact" | "large";
}) {
if (moves.length === 0) {
return (
No moves yet. They are written when a session ends — finish one and come back.
);
}
return (
{moves.map((move, i) => (
{String(i + 1).padStart(2, "0")}
{move.move}
{move.because}
))}
);
}
export function HandoffList({ cards }: { cards: HandoffCard[] }) {
if (cards.length === 0) {
return Nothing in progress. Every handoff is closed. ;
}
return (
{cards.map((h) => (
{h.slug ? (
{h.label}
) : (
{h.label}
)}
{h.source} · {age(h.ageDays)}
{h.sentence}
))}
);
}
export function GoalsList({ goals }: { goals: MatrixItem[] }) {
if (goals.length === 0) return No goals written down yet. ;
return (
{goals.map((goal) => (
{goal.label}
{goal.due ?? goal.detail}
{goal.progress
? `${goal.progress.closed} of ${goal.progress.written} criteria closed · served by ${goal.progress.projects.join(", ")}`
: "no project serves this yet"}
{goal.urgentBecause.length > 0 && ` · ${goal.urgentBecause.join(" · ")}`}
))}
);
}