90 lines · 4.0 KB
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<string | null>(null);
  if (!field) return <EmptyState title="Add a single-select field to use the board view" icon="project" />;
  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 (
    <div className="space-y-2">
      {update.error && <p className="text-xs text-kumo-danger">{update.error}</p>}
      <div className="flex gap-3 overflow-x-auto p-3">
        {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
            <section
              key={column || "none"}