"use client"; import { useState, useEffect, useCallback } from "react"; import { useParams } from "next/navigation"; import { Plus, ArrowLeft, GripVertical, MoreHorizontal } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import { SectionDialog } from "@/components/projects/section-dialog"; import Link from "next/link"; import { toast } from "sonner"; interface Section { id: string; name: string; projectId: string; kind: 'section' | 'milestone'; status: 'planned' | 'in_progress' | 'complete'; targetDate: string | null; sortOrder: number; } interface Task { id: string; title: string; status: string; priority: string; sectionId: string | null; order: number; } interface ProjectDetail { id: string; name: string; description: string | null; status: string; color: string | null; icon: string | null; targetDate: string | null; sections: Section[]; tasks: Task[]; taskCount: number; completedCount: number; progress: number; } const statusColors: Record = { active: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200", paused: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200", completed: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200", archived: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200", }; const taskStatusColors: Record = { todo: "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300", in_progress: "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-200", done: "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-200", cancelled: "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-200", }; export default function ProjectDetailPage() { const params = useParams(); const projectId = params.id as string; const [project, setProject] = useState(null); const [domainId, setDomainId] = useState(null); const [loading, setLoading] = useState(true); const [sectionDialogOpen, setSectionDialogOpen] = useState(false); const [draggedTaskId, setDraggedTaskId] = useState(null); // Extract domainId from the project data const fetchProject = useCallback(async () => { setLoading(true); try { // We need to find the domain first — use the first domain const domainsRes = await fetch('/api/domains?sort=sort_order'); const domainsData = await domainsRes.json(); const firstDomain = domainsData.items?.[0]; if (!firstDomain) { setLoading(false); return; } setDomainId(firstDomain.id); const res = await fetch(`/api/domains/${firstDomain.id}/projects/${projectId}`); if (!res.ok) throw new Error('Not found'); const data = await res.json(); setProject(data); } catch { toast.error('Failed to load project'); } finally { setLoading(false); } }, [projectId]); useEffect(() => { fetchProject(); }, [fetchProject]); const handleMoveTask = async (taskId: string, sectionId: string | null) => { try { const res = await fetch(`/api/domains/${domainId}/tasks/${taskId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sectionId }), }); if (!res.ok) throw new Error('Failed to move task'); toast.success('Task moved'); fetchProject(); } catch { toast.error('Failed to move task'); } }; // Listen for custom event to open section dialog useEffect(() => { const handler = () => setSectionDialogOpen(true); document.addEventListener('open-create-section', handler); return () => document.removeEventListener('open-create-section', handler); }, []); if (loading) { return
Loading project...
; } if (!project) { return (

Project not found.

← Back to projects
); } // Group tasks by section const tasksBySection = new Map(); tasksBySection.set('unsectioned', []); for (const section of project.sections) { tasksBySection.set(section.id, []); } for (const task of project.tasks) { const key = task.sectionId || 'unsectioned'; if (!tasksBySection.has(key)) tasksBySection.set(key, []); tasksBySection.get(key)!.push(task); } return (
{/* Header */}
); }