import { useState } from "react"; import { InlineSelect } from "~/ui/Field"; import { updateProjectItem } from "~/fns/projects"; import { useAction } from "~/hooks/use-action"; import { ItemTitle } from "./ItemTitle"; import type { ProjectData, ProjectField } from "./ItemTitle"; import { EmptyState } from "~/ui/EmptyState"; import { cn } from "~/utils/cn"; import { CountBadge } from "~/ui/CountBadge"; const NO_VALUE = ""; /** Kanban columns over a single-select field; cards move between columns with native drag-and-drop. */ export function ProjectBoard({ data, field }: { data: ProjectData; field: ProjectField | undefined }) { const update = useAction(updateProjectItem); const [over, setOver] = useState(null); if (!field) return ; const columns = [NO_VALUE, ...field.options]; const projectRef = { owner: data.project.owner, number: data.project.number }; function drop(column: string, itemId: string) { setOver(null); const item = data.items.find((candidate) => candidate.id === itemId); if (!item || !field || (item.values[field.id] ?? NO_VALUE) === column) return; const last = Math.max(0, ...data.items.map((candidate) => candidate.position)); void update.run({ data: { ...projectRef, itemId, fieldId: field.id, value: column || null, position: last + 1 }, }); } return (
{update.error &&

{update.error}

}
{columns.map((column) => { const cards = data.items.filter((item) => (item.values[field.id] ?? NO_VALUE) === column); return ( // oxlint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- drag is a pointer shortcut; each card's select is the keyboard path
{ if (!data.canWrite) return; event.preventDefault(); setOver(column); }} onDragLeave={() => setOver(null)} onDrop={(event) => drop(column, event.dataTransfer.getData("text/plain"))} className={cn( "w-72 shrink-0 rounded-lg border bg-kumo-base/40 p-2", over === column ? "border-accent-500" : "border-kumo-hairline", )} >

{column || `No ${field.name}`}

    {cards.map((item) => ( // oxlint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- drag is a pointer shortcut; the select below is the keyboard path
  • event.dataTransfer.setData("text/plain", item.id)} className={cn( "rounded-md border border-kumo-hairline bg-kumo-canvas px-3 py-2 text-sm", data.canWrite && "cursor-grab active:cursor-grabbing", )} > {data.canWrite && ( drop(value, item.id)} className="mt-2 w-full" options={columns.map((option) => ({ value: option, label: option || `No ${field.name}` }))} /> )}
  • ))}
); })}
); }