diff --git a/frontend/src/components/tasks/TaskDetailsDialog.tsx b/frontend/src/components/tasks/TaskDetailsDialog.tsx index 4d764138..8b137891 100644 --- a/frontend/src/components/tasks/TaskDetailsDialog.tsx +++ b/frontend/src/components/tasks/TaskDetailsDialog.tsx @@ -1,636 +1 @@ -import { useState, useEffect } from "react"; -import { Card, CardContent } from "@/components/ui/card"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Label } from "@/components/ui/label"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Textarea } from "@/components/ui/textarea"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Separator } from "@/components/ui/separator"; -import { makeRequest } from "@/lib/api"; -import type { - TaskStatus, - TaskAttempt, - TaskAttemptActivity, - TaskAttemptStatus, -} from "shared/types"; -interface Task { - id: string; - project_id: string; - title: string; - description: string | null; - status: TaskStatus; - created_at: string; - updated_at: string; -} - -interface ApiResponse { - success: boolean; - data: T | null; - message: string | null; -} - -interface TaskDetailsDialogProps { - isOpen: boolean; - onOpenChange: (open: boolean) => void; - task: Task | null; - projectId: string; - onError: (error: string) => void; -} - -const statusLabels: Record = { - todo: "To Do", - inprogress: "In Progress", - inreview: "In Review", - done: "Done", - cancelled: "Cancelled", -}; - -const getAttemptStatusDisplay = (status: TaskAttemptStatus): { label: string; className: string } => { - switch (status) { - case "setuprunning": - return { label: "Setup Running", className: "bg-status-running text-status-running-foreground" }; - case "setupcomplete": - return { label: "Setup Complete", className: "bg-status-complete text-status-complete-foreground" }; - case "setupfailed": - return { label: "Setup Failed", className: "bg-status-failed text-status-failed-foreground" }; - case "executorrunning": - return { label: "Executor Running", className: "bg-status-running text-status-running-foreground" }; - case "executorcomplete": - return { label: "Executor Complete", className: "bg-status-complete text-status-complete-foreground" }; - case "executorfailed": - return { label: "Executor Failed", className: "bg-status-failed text-status-failed-foreground" }; - default: - return { label: "Unknown", className: "bg-status-init text-status-init-foreground" }; - } -}; - -export function TaskDetailsDialog({ - isOpen, - onOpenChange, - task, - projectId, - onError, -}: TaskDetailsDialogProps) { - const [taskAttempts, setTaskAttempts] = useState([]); - const [taskAttemptsLoading, setTaskAttemptsLoading] = useState(false); - const [selectedAttempt, setSelectedAttempt] = useState( - null - ); - const [attemptActivities, setAttemptActivities] = useState< - TaskAttemptActivity[] - >([]); - const [activitiesLoading, setActivitiesLoading] = useState(false); - const [selectedExecutor, setSelectedExecutor] = useState("claude"); - const [creatingAttempt, setCreatingAttempt] = useState(false); - const [stoppingAttempt, setStoppingAttempt] = useState(false); - - // Edit mode state - const [isEditMode, setIsEditMode] = useState(false); - const [editedTitle, setEditedTitle] = useState(""); - const [editedDescription, setEditedDescription] = useState(""); - const [editedStatus, setEditedStatus] = useState("todo"); - const [savingTask, setSavingTask] = useState(false); - - // Check if the selected attempt is active (not in a final state) - const isAttemptRunning = - selectedAttempt && - attemptActivities.length > 0 && - (attemptActivities[0].status === "setuprunning" || - attemptActivities[0].status === "setupcomplete" || - attemptActivities[0].status === "executorrunning"); - - useEffect(() => { - if (isOpen && task) { - // Reset attempt-related state when switching tasks - setSelectedAttempt(null); - setAttemptActivities([]); - setActivitiesLoading(false); - - fetchTaskAttempts(task.id); - // Initialize edit state with current task values - setEditedTitle(task.title); - setEditedDescription(task.description || ""); - setEditedStatus(task.status); - setIsEditMode(false); - } - }, [isOpen, task]); - - const fetchTaskAttempts = async (taskId: string) => { - try { - setTaskAttemptsLoading(true); - const response = await makeRequest( - `/api/projects/${projectId}/tasks/${taskId}/attempts` - ); - - if (response.ok) { - const result: ApiResponse = await response.json(); - if (result.success && result.data) { - setTaskAttempts(result.data); - // Automatically select the latest attempt if available - if (result.data.length > 0) { - const latestAttempt = result.data.reduce((latest, current) => - new Date(current.created_at) > new Date(latest.created_at) - ? current - : latest - ); - setSelectedAttempt(latestAttempt); - fetchAttemptActivities(latestAttempt.id); - } - } - } else { - onError("Failed to load task attempts"); - } - } catch (err) { - onError("Failed to load task attempts"); - } finally { - setTaskAttemptsLoading(false); - } - }; - - const fetchAttemptActivities = async (attemptId: string) => { - if (!task) return; - - try { - setActivitiesLoading(true); - const response = await makeRequest( - `/api/projects/${projectId}/tasks/${task.id}/attempts/${attemptId}/activities` - ); - - if (response.ok) { - const result: ApiResponse = - await response.json(); - if (result.success && result.data) { - setAttemptActivities(result.data); - } - } else { - onError("Failed to load attempt activities"); - } - } catch (err) { - onError("Failed to load attempt activities"); - } finally { - setActivitiesLoading(false); - } - }; - - const handleAttemptClick = (attempt: TaskAttempt) => { - setSelectedAttempt(attempt); - fetchAttemptActivities(attempt.id); - }; - - const saveTaskChanges = async () => { - if (!task) return; - - try { - setSavingTask(true); - const response = await makeRequest( - `/api/projects/${projectId}/tasks/${task.id}`, - { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - title: editedTitle, - description: editedDescription || null, - status: editedStatus, - }), - } - ); - - if (response.ok) { - setIsEditMode(false); - // Update the local task state would require parent component to refresh - // For now, just exit edit mode - } else { - onError("Failed to save task changes"); - } - } catch (err) { - onError("Failed to save task changes"); - } finally { - setSavingTask(false); - } - }; - - const cancelEdit = () => { - if (task) { - setEditedTitle(task.title); - setEditedDescription(task.description || ""); - setEditedStatus(task.status); - } - setIsEditMode(false); - }; - - const createNewAttempt = async () => { - if (!task) return; - - try { - setCreatingAttempt(true); - const worktreePath = `/tmp/task-${task.id}-attempt-${Date.now()}`; - - const response = await makeRequest( - `/api/projects/${projectId}/tasks/${task.id}/attempts`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - task_id: task.id, - worktree_path: worktreePath, - base_commit: null, - merge_commit: null, - executor: selectedExecutor, - }), - } - ); - - if (response.ok) { - // Refresh the attempts list - await fetchTaskAttempts(task.id); - } else { - onError("Failed to create task attempt"); - } - } catch (err) { - onError("Failed to create task attempt"); - } finally { - setCreatingAttempt(false); - } - }; - - const stopTaskAttempt = async () => { - if (!task || !selectedAttempt) return; - - try { - setStoppingAttempt(true); - const response = await makeRequest( - `/api/projects/${projectId}/tasks/${task.id}/attempts/${selectedAttempt.id}/stop`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - } - ); - - if (response.ok) { - // Refresh the activities list to show the stopped status - fetchAttemptActivities(selectedAttempt.id); - } else { - onError("Failed to stop task attempt"); - } - } catch (err) { - onError("Failed to stop task attempt"); - } finally { - setStoppingAttempt(false); - } - }; - - return ( - - - -
- - {isEditMode ? "Edit Task" : "Task Details"} - -
- {isEditMode ? ( - <> - - - - ) : ( - - )} -
-
-
- -
- {/* Main Content */} -
- {/* Task Details */} - - -
-
- - {isEditMode ? ( - setEditedTitle(e.target.value)} - className="mt-1" - placeholder="Enter task title..." - /> - ) : ( -

- {task?.title} -

- )} -
- -
- - {isEditMode ? ( -