From acd71a88bb7ab9a7cbf7babdcbe9431feb202b5c Mon Sep 17 00:00:00 2001 From: Louis Knight-Webb Date: Sat, 21 Jun 2025 17:36:07 +0100 Subject: [PATCH] Panel refactor --- frontend/src/App.tsx | 14 +- frontend/src/components/layout/navbar.tsx | 2 +- .../src/components/tasks/TaskDetailsPanel.tsx | 743 +++++++++++------- .../src/components/tasks/TaskKanbanBoard.tsx | 73 +- frontend/src/pages/project-tasks.tsx | 196 +++-- 5 files changed, 612 insertions(+), 416 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 43571f4e..5708f659 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -20,7 +20,7 @@ function AppContent() { try { const response = await fetch("/api/config"); const data: ApiResponse = await response.json(); - + if (data.success && data.data) { setConfig(data.data); setShowDisclaimer(!data.data.disclaimer_acknowledged); @@ -37,9 +37,9 @@ function AppContent() { const handleDisclaimerAccept = async () => { if (!config) return; - + const updatedConfig = { ...config, disclaimer_acknowledged: true }; - + try { const response = await fetch("/api/config", { method: "POST", @@ -48,9 +48,9 @@ function AppContent() { }, body: JSON.stringify(updatedConfig), }); - + const data: ApiResponse = await response.json(); - + if (data.success) { setConfig(updatedConfig); setShowDisclaimer(false); @@ -72,13 +72,13 @@ function AppContent() { } return ( -
+
{showNavbar && } -
+
} /> } /> diff --git a/frontend/src/components/layout/navbar.tsx b/frontend/src/components/layout/navbar.tsx index 10de0907..5c558e7d 100644 --- a/frontend/src/components/layout/navbar.tsx +++ b/frontend/src/components/layout/navbar.tsx @@ -9,7 +9,7 @@ export function Navbar() { return (
-
+
diff --git a/frontend/src/components/tasks/TaskDetailsPanel.tsx b/frontend/src/components/tasks/TaskDetailsPanel.tsx index 03cbbdfb..32e3d6c3 100644 --- a/frontend/src/components/tasks/TaskDetailsPanel.tsx +++ b/frontend/src/components/tasks/TaskDetailsPanel.tsx @@ -1,5 +1,14 @@ import { useState, useEffect } from "react"; -import { X, History, Send, Clock, FileText, Code } from "lucide-react"; +import { + X, + History, + Send, + Clock, + FileText, + Code, + Maximize2, + Minimize2, +} from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; @@ -30,51 +39,99 @@ interface TaskDetailsPanelProps { projectId: string; isOpen: boolean; onClose: () => void; + viewMode: "overlay" | "sideBySide"; + onViewModeChange: (mode: "overlay" | "sideBySide") => void; } const statusLabels: Record = { todo: "To Do", - inprogress: "In Progress", + inprogress: "In Progress", inreview: "In Review", done: "Done", cancelled: "Cancelled", }; -const getAttemptStatusDisplay = (status: TaskAttemptStatus): { label: string; className: string } => { +const getAttemptStatusDisplay = ( + status: TaskAttemptStatus +): { label: string; className: string } => { switch (status) { case "init": - return { label: "Init", className: "bg-status-init text-status-init-foreground" }; + return { + label: "Init", + className: "bg-status-init text-status-init-foreground", + }; case "setuprunning": - return { label: "Setup Running", className: "bg-status-running text-status-running-foreground" }; + 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" }; + 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" }; + 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" }; + 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" }; + 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" }; + return { + label: "Executor Failed", + className: "bg-status-failed text-status-failed-foreground", + }; case "paused": - return { label: "Paused", className: "bg-status-paused text-status-paused-foreground" }; + return { + label: "Paused", + className: "bg-status-paused text-status-paused-foreground", + }; default: - return { label: "Unknown", className: "bg-status-init text-status-init-foreground" }; + return { + label: "Unknown", + className: "bg-status-init text-status-init-foreground", + }; } }; -const getProcessStatusDisplay = (status: ExecutionProcessStatus): { label: string; className: string } => { +const getProcessStatusDisplay = ( + status: ExecutionProcessStatus +): { label: string; className: string } => { switch (status) { case "running": - return { label: "Running", className: "bg-status-running text-status-running-foreground" }; + return { + label: "Running", + className: "bg-status-running text-status-running-foreground", + }; case "completed": - return { label: "Completed", className: "bg-status-complete text-status-complete-foreground" }; + return { + label: "Completed", + className: "bg-status-complete text-status-complete-foreground", + }; case "failed": - return { label: "Failed", className: "bg-status-failed text-status-failed-foreground" }; + return { + label: "Failed", + className: "bg-status-failed text-status-failed-foreground", + }; case "killed": - return { label: "Killed", className: "bg-status-failed text-status-failed-foreground" }; + return { + label: "Killed", + className: "bg-status-failed text-status-failed-foreground", + }; default: - return { label: "Unknown", className: "bg-status-init text-status-init-foreground" }; + return { + label: "Unknown", + className: "bg-status-init text-status-init-foreground", + }; } }; @@ -91,11 +148,24 @@ const getProcessTypeDisplay = (type: ExecutionProcessType): string => { } }; -export function TaskDetailsPanel({ task, projectId, isOpen, onClose }: TaskDetailsPanelProps) { +export function TaskDetailsPanel({ + task, + projectId, + isOpen, + onClose, + viewMode, + onViewModeChange, +}: TaskDetailsPanelProps) { const [taskAttempts, setTaskAttempts] = useState([]); - const [selectedAttempt, setSelectedAttempt] = useState(null); - const [attemptActivities, setAttemptActivities] = useState([]); - const [executionProcesses, setExecutionProcesses] = useState([]); + const [selectedAttempt, setSelectedAttempt] = useState( + null + ); + const [attemptActivities, setAttemptActivities] = useState< + TaskAttemptActivity[] + >([]); + const [executionProcesses, setExecutionProcesses] = useState< + ExecutionProcess[] + >([]); const [loading, setLoading] = useState(false); const [followUpMessage, setFollowUpMessage] = useState(""); const [showAttemptHistory, setShowAttemptHistory] = useState(false); @@ -142,7 +212,7 @@ export function TaskDetailsPanel({ task, projectId, isOpen, onClose }: TaskDetai const result: ApiResponse = await response.json(); if (result.success && result.data) { setTaskAttempts(result.data); - + // Auto-select latest attempt if (result.data.length > 0) { const latestAttempt = result.data.reduce((latest, current) => @@ -163,7 +233,10 @@ export function TaskDetailsPanel({ task, projectId, isOpen, onClose }: TaskDetai } }; - const fetchAttemptActivities = async (attemptId: string, _isBackgroundUpdate = false) => { + const fetchAttemptActivities = async ( + attemptId: string, + _isBackgroundUpdate = false + ) => { if (!task) return; try { @@ -172,7 +245,8 @@ export function TaskDetailsPanel({ task, projectId, isOpen, onClose }: TaskDetai ); if (response.ok) { - const result: ApiResponse = await response.json(); + const result: ApiResponse = + await response.json(); if (result.success && result.data) { setAttemptActivities(result.data); } @@ -182,7 +256,10 @@ export function TaskDetailsPanel({ task, projectId, isOpen, onClose }: TaskDetai } }; - const fetchExecutionProcesses = async (attemptId: string, _isBackgroundUpdate = false) => { + const fetchExecutionProcesses = async ( + attemptId: string, + _isBackgroundUpdate = false + ) => { if (!task) return; try { @@ -202,7 +279,7 @@ export function TaskDetailsPanel({ task, projectId, isOpen, onClose }: TaskDetai }; const handleAttemptChange = (attemptId: string) => { - const attempt = taskAttempts.find(a => a.id === attemptId); + const attempt = taskAttempts.find((a) => a.id === attemptId); if (attempt) { setSelectedAttempt(attempt); fetchAttemptActivities(attempt.id); @@ -265,280 +342,366 @@ export function TaskDetailsPanel({ task, projectId, isOpen, onClose }: TaskDetai <> {isOpen && ( <> - {/* Backdrop */} -
- + {/* Backdrop - only in overlay mode */} + {viewMode === "overlay" && ( +
+ )} + {/* Panel */} -
+
- {/* Header */} -
-
-
-

- {task.title} -

-
- - {statusLabels[task.status]} - -
-
- -
- - {/* Attempt Selection */} -
- {selectedAttempt && !showAttemptHistory ? ( -
- Current attempt: - - {new Date(selectedAttempt.created_at).toLocaleDateString()} {new Date(selectedAttempt.created_at).toLocaleTimeString()} - - {taskAttempts.length > 1 && ( - - )} -
- ) : ( -
- - -
- )} - - {selectedAttempt && ( -
- - -
- )} -
-
- - {/* Content */} -
- {loading ? ( -
-
-

Loading...

-
- ) : ( - <> - {/* Description */} -
- -
- {task.description ? ( -

{task.description}

- ) : ( -

- No description provided -

- )} -
-
- - {/* Execution Processes */} - {selectedAttempt && executionProcesses.length > 0 && ( -
- -
- {executionProcesses.map((process) => ( - - -
-
- - {getProcessStatusDisplay(process.status).label} - - - {getProcessTypeDisplay(process.process_type)} - -
-
- - {new Date(process.started_at).toLocaleTimeString()} - - {process.status === "running" && ( - - )} -
-
- - {(process.stdout || process.stderr) && ( -
- {process.stdout && ( -
- -
- {process.stdout} -
-
- )} - {process.stderr && ( -
- -
- {process.stderr} -
-
- )} -
- )} -
-
- ))} + {/* Header */} +
+
+
+

+ {task.title} +

+
+ + {statusLabels[task.status]} +
- )} +
+ + +
+
- {/* Activity History */} - {selectedAttempt && ( -
- - {attemptActivities.length === 0 ? ( -
- No activities found -
- ) : ( -
- {attemptActivities.map((activity) => ( - - -
- - {getAttemptStatusDisplay(activity.status).label} + {/* Attempt Selection */} +
+ {selectedAttempt && !showAttemptHistory ? ( +
+ + Current attempt: + + + {new Date( + selectedAttempt.created_at + ).toLocaleDateString()}{" "} + {new Date( + selectedAttempt.created_at + ).toLocaleTimeString()} + + {taskAttempts.length > 1 && ( + + )} +
+ ) : ( +
+ + +
+ )} + + {selectedAttempt && ( +
+ + +
+ )} +
+
+ + {/* Content */} +
+ {loading ? ( +
+
+

Loading...

+
+ ) : ( + <> + {/* Description */} +
+ +
+ {task.description ? ( +

+ {task.description} +

+ ) : ( +

+ No description provided +

+ )} +
+
+ + {/* Execution Processes */} + {selectedAttempt && executionProcesses.length > 0 && ( +
+ +
+ {executionProcesses.map((process) => ( + + +
+
+ + { + getProcessStatusDisplay(process.status) + .label + } + + + {getProcessTypeDisplay( + process.process_type + )} + +
+
+ + {new Date( + process.started_at + ).toLocaleTimeString()} + + {process.status === "running" && ( + + )} +
+
+ + {(process.stdout || process.stderr) && ( +
+ {process.stdout && ( +
+ +
+ {process.stdout} +
+
+ )} + {process.stderr && ( +
+ +
+ {process.stderr} +
+
+ )} +
+ )} +
+
+ ))} +
)} -
- )} - - )} -
- {/* Footer */} -
-
- -
-