"use client"; import { useState, useEffect, useCallback } from "react"; import { Plus, FolderKanban, ExternalLink } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { ProjectCreateDialog } from "@/components/projects/project-create-dialog"; import Link from "next/link"; import { toast } from "sonner"; interface Project { id: string; name: string; description: string | null; status: 'active' | 'paused' | 'completed' | 'archived'; domainId: string; color: string | null; icon: string | null; targetDate: string | null; taskCount: number; completedCount: number; progress: number; tags: { id: string; name: string; color: string | null }[]; } 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", }; export default function ProjectsPage() { const [projects, setProjects] = useState([]); const [domainId, setDomainId] = useState(null); const [domains, setDomains] = useState<{ id: string; name: string }[]>([]); const [createOpen, setCreateOpen] = useState(false); const [loading, setLoading] = useState(true); // Fetch domains useEffect(() => { fetch('/api/domains?sort=sort_order') .then((res) => res.json()) .then((data) => { const items = data.items || []; setDomains(items); if (items.length > 0 && !domainId) { setDomainId(items[0].id); } }) .catch(() => {}); }, []); // eslint-disable-line react-hooks/exhaustive-deps // Fetch projects const fetchProjects = useCallback(async () => { if (!domainId) return; setLoading(true); try { const res = await fetch(`/api/domains/${domainId}/projects`); const data = await res.json(); setProjects(data.items || []); } catch { toast.error('Failed to load projects'); } finally { setLoading(false); } }, [domainId]); useEffect(() => { fetchProjects(); }, [fetchProjects]); // Listen for custom event to open create dialog useEffect(() => { const handler = () => setCreateOpen(true); document.addEventListener('open-create-project', handler); return () => document.removeEventListener('open-create-project', handler); }, []); return (

Projects

Organize work into milestones and track progress.

{domains.length > 1 && ( )}
{loading ? (
Loading projects...
) : projects.length === 0 ? (
) : (
{projects.map((project) => (
{project.color && (
)} {project.name}
{project.description && (

{project.description}

)}
{project.status} {project.targetDate && ( Due {new Date(project.targetDate).toLocaleDateString()} )}
{project.completedCount}/{project.taskCount} tasks {project.progress}%
{project.tags.length > 0 && (
{project.tags.map((tag) => ( {tag.name} ))}
)}
))}
)}
); }