summaryrefslogtreecommitdiff
path: root/makima/frontend/src/routes
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-19 17:55:22 +0000
committerGitHub <noreply@github.com>2026-01-19 17:55:22 +0000
commit52d121269195f0e799d0ab4241e4facc3c7c0596 (patch)
tree13d3dcdd743cf15f31d6d87097bf51ebfd01a305 /makima/frontend/src/routes
parent164941cbd591b46f69a034bb9b86521fd7700ddb (diff)
downloadsoryu-52d121269195f0e799d0ab4241e4facc3c7c0596.tar.gz
soryu-52d121269195f0e799d0ab4241e4facc3c7c0596.zip
Add right-click context menu for contracts on contracts and board pages (#8)
Implement a reusable ContractContextMenu component that provides: - Mark as Complete/Active/Archive status actions (conditionally shown) - Go to Supervisor Task link (when supervisor exists) - Delete action with confirmation Integrate context menu into: - ContractList.tsx on the contracts page - WorkflowBoard on the workflow/board page via PhaseColumn and WorkflowContractCard Features match ElementContextMenu patterns: - Fixed positioning with z-50 - Click outside and Escape key close handlers - Viewport overflow prevention - Dark theme colors (#0a1628, #0d1b2d, #75aafc, #9bc3ff) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'makima/frontend/src/routes')
-rw-r--r--makima/frontend/src/routes/contracts.tsx51
-rw-r--r--makima/frontend/src/routes/workflow.tsx49
2 files changed, 97 insertions, 3 deletions
diff --git a/makima/frontend/src/routes/contracts.tsx b/makima/frontend/src/routes/contracts.tsx
index d2b6b1b..0893ff6 100644
--- a/makima/frontend/src/routes/contracts.tsx
+++ b/makima/frontend/src/routes/contracts.tsx
@@ -1,4 +1,4 @@
-import { useState, useCallback, useEffect } from "react";
+import { useState, useCallback, useEffect, useMemo } from "react";
import { useParams, useNavigate } from "react-router";
import { Masthead } from "../components/Masthead";
import { ContractList } from "../components/contracts/ContractList";
@@ -9,6 +9,7 @@ import { useAuth } from "../contexts/AuthContext";
import { createTask, getDaemonDirectories, getRepositorySuggestions } from "../lib/api";
import type {
ContractWithRelations,
+ ContractSummary,
ContractPhase,
ContractStatus,
ContractType,
@@ -411,6 +412,49 @@ function ContractsPageContent() {
[contractDetail, fetchContract, navigate]
);
+ // Context menu handlers for ContractList
+ const handleContextMarkComplete = useCallback(
+ async (contract: ContractSummary) => {
+ await editContract(contract.id, { status: "completed", version: contract.version });
+ },
+ [editContract]
+ );
+
+ const handleContextMarkActive = useCallback(
+ async (contract: ContractSummary) => {
+ await editContract(contract.id, { status: "active", version: contract.version });
+ },
+ [editContract]
+ );
+
+ const handleContextArchive = useCallback(
+ async (contract: ContractSummary) => {
+ await editContract(contract.id, { status: "archived", version: contract.version });
+ },
+ [editContract]
+ );
+
+ const handleContextDelete = useCallback(
+ async (contract: ContractSummary) => {
+ if (confirm(`Are you sure you want to delete "${contract.name}"?`)) {
+ const success = await removeContract(contract.id);
+ if (success && contract.id === id) {
+ navigate("/contracts");
+ }
+ }
+ },
+ [removeContract, id, navigate]
+ );
+
+ const handleContextGoToSupervisor = useCallback(
+ (contract: ContractSummary) => {
+ if (contract.supervisorTaskId) {
+ navigate(`/mesh/${contract.supervisorTaskId}`);
+ }
+ },
+ [navigate]
+ );
+
return (
<div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
<Masthead showNav />
@@ -684,6 +728,11 @@ function ContractsPageContent() {
onSelect={handleSelect}
onCreate={handleCreate}
selectedId={id}
+ onMarkComplete={handleContextMarkComplete}
+ onMarkActive={handleContextMarkActive}
+ onArchive={handleContextArchive}
+ onDelete={handleContextDelete}
+ onGoToSupervisor={handleContextGoToSupervisor}
/>
{/* Contract detail or empty state */}
diff --git a/makima/frontend/src/routes/workflow.tsx b/makima/frontend/src/routes/workflow.tsx
index cb72e9e..e122092 100644
--- a/makima/frontend/src/routes/workflow.tsx
+++ b/makima/frontend/src/routes/workflow.tsx
@@ -4,7 +4,7 @@ import { Masthead } from "../components/Masthead";
import { WorkflowBoard } from "../components/workflow/WorkflowBoard";
import { useContracts } from "../hooks/useContracts";
import { useAuth } from "../contexts/AuthContext";
-import type { ContractPhase, ContractStatus } from "../lib/api";
+import type { ContractPhase, ContractStatus, ContractSummary } from "../lib/api";
type StatusFilter = "all" | ContractStatus;
@@ -41,7 +41,7 @@ export default function WorkflowPage() {
function WorkflowPageContent() {
const navigate = useNavigate();
- const { contracts, loading, error, changePhase, saveContract } = useContracts();
+ const { contracts, loading, error, changePhase, saveContract, editContract, removeContract } = useContracts();
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");
const [isCreating, setIsCreating] = useState(false);
const [newContractName, setNewContractName] = useState("");
@@ -68,6 +68,46 @@ function WorkflowPageContent() {
[changePhase]
);
+ // Context menu handlers
+ const handleContextMarkComplete = useCallback(
+ async (contract: ContractSummary) => {
+ await editContract(contract.id, { status: "completed", version: contract.version });
+ },
+ [editContract]
+ );
+
+ const handleContextMarkActive = useCallback(
+ async (contract: ContractSummary) => {
+ await editContract(contract.id, { status: "active", version: contract.version });
+ },
+ [editContract]
+ );
+
+ const handleContextArchive = useCallback(
+ async (contract: ContractSummary) => {
+ await editContract(contract.id, { status: "archived", version: contract.version });
+ },
+ [editContract]
+ );
+
+ const handleContextDelete = useCallback(
+ async (contract: ContractSummary) => {
+ if (confirm(`Are you sure you want to delete "${contract.name}"?`)) {
+ await removeContract(contract.id);
+ }
+ },
+ [removeContract]
+ );
+
+ const handleContextGoToSupervisor = useCallback(
+ (contract: ContractSummary) => {
+ if (contract.supervisorTaskId) {
+ navigate(`/mesh/${contract.supervisorTaskId}`);
+ }
+ },
+ [navigate]
+ );
+
const handleCreateContract = useCallback(async () => {
if (!newContractName.trim()) return;
const contract = await saveContract({
@@ -196,6 +236,11 @@ function WorkflowPageContent() {
contracts={filteredContracts}
onContractClick={handleContractClick}
onPhaseChange={handlePhaseChange}
+ onMarkComplete={handleContextMarkComplete}
+ onMarkActive={handleContextMarkActive}
+ onArchive={handleContextArchive}
+ onDelete={handleContextDelete}
+ onGoToSupervisor={handleContextGoToSupervisor}
/>
)}
</div>