"use client"; import { useState, useEffect, useCallback } from "react"; import { useParams } from "next/navigation"; import { Plus, ArrowLeft, GripVertical, MoreHorizontal, Pencil, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; 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); const [editingSection, setEditingSection] = useState
(null); const [editSectionName, setEditSectionName] = useState(""); const [editSectionKind, setEditSectionKind] = useState<"section" | "milestone">("section"); const [editSectionStatus, setEditSectionStatus] = useState<"planned" | "in_progress" | "complete">("planned"); const [editSectionSaving, setEditSectionSaving] = useState(false); const [deleteSectionId, setDeleteSectionId] = useState(null); const [deleteSectionDeleting, setDeleteSectionDeleting] = useState(false); const fetchProject = useCallback(async () => { setLoading(true); try { 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'); } }; // Edit section const handleEditSection = async () => { if (!editingSection || !editSectionName.trim() || !domainId) return; setEditSectionSaving(true); try { const res = await fetch(`/api/domains/${domainId}/projects/${projectId}/sections/${editingSection.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: editSectionName.trim(), kind: editSectionKind, status: editSectionStatus, }), }); if (!res.ok) throw new Error('Failed to update section'); toast.success('Section updated'); setEditingSection(null); fetchProject(); } catch { toast.error('Failed to update section'); } finally { setEditSectionSaving(false); } }; // Delete section const handleDeleteSection = async () => { if (!deleteSectionId || !domainId) return; setDeleteSectionDeleting(true); try { const res = await fetch(`/api/domains/${domainId}/projects/${projectId}/sections/${deleteSectionId}`, { method: 'DELETE', }); if (!res.ok) throw new Error('Failed to delete section'); toast.success('Section deleted'); setDeleteSectionId(null); fetchProject(); } catch { toast.error('Failed to delete section'); } finally { setDeleteSectionDeleting(false); } }; // Open edit section dialog const openEditSection = (section: Section) => { setEditSectionName(section.name); setEditSectionKind(section.kind); setEditSectionStatus(section.status); setEditingSection(section); }; // 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
); } 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 */}
); }