This commit is contained in:
Louis Knight-Webb
2025-06-21 22:55:12 +01:00
parent d341543bb2
commit 70e08853aa
3 changed files with 99 additions and 85 deletions

View File

@@ -40,16 +40,6 @@ export function Navbar() {
</Button> </Button>
</div> </div>
</div> </div>
<div className="flex items-center space-x-4">
{!isHome && (
<Button asChild variant="ghost">
<Link to="/">
<ArrowLeft className="mr-2 h-4 w-4" />
Home
</Link>
</Button>
)}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,80 +1,100 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from "react";
import { useNavigate } from 'react-router-dom' import { useNavigate } from "react-router-dom";
import { Button } from '@/components/ui/button' import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import {
import { Badge } from '@/components/ui/badge' Card,
import { Alert, AlertDescription } from '@/components/ui/alert' CardContent,
import { Project, ApiResponse } from 'shared/types' CardDescription,
import { ProjectForm } from './project-form' CardHeader,
import { makeRequest } from '@/lib/api' CardTitle,
import { Plus, Edit, Trash2, Calendar, AlertCircle, Loader2, MoreHorizontal, ExternalLink } from 'lucide-react' } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Project, ApiResponse } from "shared/types";
import { ProjectForm } from "./project-form";
import { makeRequest } from "@/lib/api";
import {
Plus,
Edit,
Trash2,
Calendar,
AlertCircle,
Loader2,
MoreHorizontal,
ExternalLink,
} from "lucide-react";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu' } from "@/components/ui/dropdown-menu";
export function ProjectList() { export function ProjectList() {
const navigate = useNavigate() const navigate = useNavigate();
const [projects, setProjects] = useState<Project[]>([]) const [projects, setProjects] = useState<Project[]>([]);
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false);
const [showForm, setShowForm] = useState(false) const [showForm, setShowForm] = useState(false);
const [editingProject, setEditingProject] = useState<Project | null>(null) const [editingProject, setEditingProject] = useState<Project | null>(null);
const [error, setError] = useState('') const [error, setError] = useState("");
const fetchProjects = async () => { const fetchProjects = async () => {
setLoading(true) setLoading(true);
setError('') setError("");
try { try {
const response = await makeRequest('/api/projects') const response = await makeRequest("/api/projects");
const data: ApiResponse<Project[]> = await response.json() const data: ApiResponse<Project[]> = await response.json();
if (data.success && data.data) { if (data.success && data.data) {
setProjects(data.data) setProjects(data.data);
} else { } else {
setError('Failed to load projects') setError("Failed to load projects");
} }
} catch (error) { } catch (error) {
console.error('Failed to fetch projects:', error) console.error("Failed to fetch projects:", error);
setError('Failed to connect to server') setError("Failed to connect to server");
} finally { } finally {
setLoading(false) setLoading(false);
} }
} };
const handleDelete = async (id: string, name: string) => { const handleDelete = async (id: string, name: string) => {
if (!confirm(`Are you sure you want to delete "${name}"? This action cannot be undone.`)) return if (
!confirm(
`Are you sure you want to delete "${name}"? This action cannot be undone.`
)
)
return;
try { try {
const response = await makeRequest(`/api/projects/${id}`, { const response = await makeRequest(`/api/projects/${id}`, {
method: 'DELETE', method: "DELETE",
}) });
if (response.ok) { if (response.ok) {
fetchProjects() fetchProjects();
} }
} catch (error) { } catch (error) {
console.error('Failed to delete project:', error) console.error("Failed to delete project:", error);
setError('Failed to delete project') setError("Failed to delete project");
} }
} };
const handleEdit = (project: Project) => { const handleEdit = (project: Project) => {
setEditingProject(project) setEditingProject(project);
setShowForm(true) setShowForm(true);
} };
const handleFormSuccess = () => { const handleFormSuccess = () => {
setShowForm(false) setShowForm(false);
setEditingProject(null) setEditingProject(null);
fetchProjects() fetchProjects();
} };
useEffect(() => { useEffect(() => {
fetchProjects() fetchProjects();
}, []) }, []);
return ( return (
<div className="space-y-6"> <div className="space-y-6 p-8">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div> <div>
<h1 className="text-3xl font-bold tracking-tight">Projects</h1> <h1 className="text-3xl font-bold tracking-tight">Projects</h1>
@@ -91,9 +111,7 @@ export function ProjectList() {
{error && ( {error && (
<Alert variant="destructive"> <Alert variant="destructive">
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertDescription> <AlertDescription>{error}</AlertDescription>
{error}
</AlertDescription>
</Alert> </Alert>
)} )}
@@ -112,10 +130,7 @@ export function ProjectList() {
<p className="mt-2 text-sm text-muted-foreground"> <p className="mt-2 text-sm text-muted-foreground">
Get started by creating your first project. Get started by creating your first project.
</p> </p>
<Button <Button className="mt-4" onClick={() => setShowForm(true)}>
className="mt-4"
onClick={() => setShowForm(true)}
>
<Plus className="mr-2 h-4 w-4" /> <Plus className="mr-2 h-4 w-4" />
Create your first project Create your first project
</Button> </Button>
@@ -124,22 +139,21 @@ export function ProjectList() {
) : ( ) : (
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{projects.map((project) => ( {projects.map((project) => (
<Card <Card
key={project.id} key={project.id}
className="hover:shadow-md transition-shadow cursor-pointer" className="hover:shadow-md transition-shadow cursor-pointer"
onClick={() => navigate(`/projects/${project.id}/tasks`)} onClick={() => navigate(`/projects/${project.id}/tasks`)}
> >
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<CardTitle className="text-lg"> <CardTitle className="text-lg">{project.name}</CardTitle>
{project.name}
</CardTitle>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Badge variant="secondary"> <Badge variant="secondary">Active</Badge>
Active
</Badge>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}> <DropdownMenuTrigger
asChild
onClick={(e) => e.stopPropagation()}
>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@@ -149,24 +163,28 @@ export function ProjectList() {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={(e) => { <DropdownMenuItem
e.stopPropagation() onClick={(e) => {
navigate(`/projects/${project.id}`) e.stopPropagation();
}}> navigate(`/projects/${project.id}`);
}}
>
<ExternalLink className="mr-2 h-4 w-4" /> <ExternalLink className="mr-2 h-4 w-4" />
View Project View Project
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={(e) => { <DropdownMenuItem
e.stopPropagation() onClick={(e) => {
handleEdit(project) e.stopPropagation();
}}> handleEdit(project);
}}
>
<Edit className="mr-2 h-4 w-4" /> <Edit className="mr-2 h-4 w-4" />
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation();
handleDelete(project.id, project.name) handleDelete(project.id, project.name);
}} }}
className="text-destructive" className="text-destructive"
> >
@@ -190,12 +208,12 @@ export function ProjectList() {
<ProjectForm <ProjectForm
open={showForm} open={showForm}
onClose={() => { onClose={() => {
setShowForm(false) setShowForm(false);
setEditingProject(null) setEditingProject(null);
}} }}
onSuccess={handleFormSuccess} onSuccess={handleFormSuccess}
project={editingProject} project={editingProject}
/> />
</div> </div>
) );
} }

View File

@@ -203,6 +203,11 @@ export function TaskDetailsPanel({
); );
setSelectedAttempt(latestAttempt); setSelectedAttempt(latestAttempt);
fetchAttemptActivities(latestAttempt.id); fetchAttemptActivities(latestAttempt.id);
} else {
// Clear state when no attempts exist
setSelectedAttempt(null);
setAttemptActivities([]);
setExecutionProcesses({});
} }
} }
} }
@@ -477,7 +482,8 @@ export function TaskDetailsPanel({
onClick={() => createNewAttempt()} onClick={() => createNewAttempt()}
className="rounded-r-none border-r-0" className="rounded-r-none border-r-0"
> >
Attempt with{" "} {selectedAttempt ? "Retry " : "Attempt "}
with{" "}
{ {
availableExecutors.find( availableExecutors.find(
(e) => e.id === selectedExecutor (e) => e.id === selectedExecutor