summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/workflow/WorkflowBoard.tsx
blob: e36ca21e2289f664ac9b818587e9e05399f6c225 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import { useMemo, useState } from "react";
import type { ContractSummary, ContractPhase } from "../../lib/api";
import { PhaseColumn } from "./PhaseColumn";
import { ContractContextMenu } from "../contracts/ContractContextMenu";

interface WorkflowBoardProps {
  contracts: ContractSummary[];
  onContractClick: (contractId: string) => void;
  onPhaseChange: (contractId: string, newPhase: ContractPhase) => void;
  onMarkComplete?: (contract: ContractSummary) => void;
  onMarkActive?: (contract: ContractSummary) => void;
  onArchive?: (contract: ContractSummary) => void;
  onDelete?: (contract: ContractSummary) => void;
  onGoToSupervisor?: (contract: ContractSummary) => void;
}

const phases: ContractPhase[] = ["research", "specify", "plan", "execute", "review"];

export function WorkflowBoard({
  contracts,
  onContractClick,
  onPhaseChange,
  onMarkComplete,
  onMarkActive,
  onArchive,
  onDelete,
  onGoToSupervisor,
}: WorkflowBoardProps) {
  const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | null>(null);
  const [contextMenuContract, setContextMenuContract] = useState<ContractSummary | null>(null);

  const handleContextMenu = (e: React.MouseEvent, contract: ContractSummary) => {
    e.preventDefault();
    e.stopPropagation(); // Prevent interference with drag-and-drop
    setContextMenuPosition({ x: e.clientX, y: e.clientY });
    setContextMenuContract(contract);
  };

  const closeContextMenu = () => {
    setContextMenuPosition(null);
    setContextMenuContract(null);
  };

  // Group contracts by phase
  const contractsByPhase = useMemo(() => {
    const grouped: Record<ContractPhase, ContractSummary[]> = {
      research: [],
      specify: [],
      plan: [],
      execute: [],
      review: [],
    };

    for (const contract of contracts) {
      const phase = contract.phase as ContractPhase;
      if (grouped[phase]) {
        grouped[phase].push(contract);
      } else {
        // Default to research if unknown phase
        grouped.research.push(contract);
      }
    }

    return grouped;
  }, [contracts]);

  return (
    <>
      <div className="flex gap-2 h-full overflow-x-auto">
        {phases.map((phase) => (
          <PhaseColumn
            key={phase}
            phase={phase}
            contracts={contractsByPhase[phase]}
            onContractClick={onContractClick}
            onDrop={onPhaseChange}
            onContextMenu={handleContextMenu}
          />
        ))}
      </div>

      {/* Context Menu */}
      {contextMenuPosition && contextMenuContract && (
        <ContractContextMenu
          x={contextMenuPosition.x}
          y={contextMenuPosition.y}
          contract={contextMenuContract}
          onClose={closeContextMenu}
          onMarkComplete={() => onMarkComplete?.(contextMenuContract)}
          onMarkActive={() => onMarkActive?.(contextMenuContract)}
          onArchive={() => onArchive?.(contextMenuContract)}
          onDelete={() => onDelete?.(contextMenuContract)}
          onGoToSupervisor={() => onGoToSupervisor?.(contextMenuContract)}
        />
      )}
    </>
  );
}