"use client"; import { useState, useEffect, useCallback } from "react"; import type { ReactElement } from "react"; import { useParams, useRouter } from "next/navigation"; import { ArrowLeft } from "lucide-react"; import { MosaicSpinner } from "@/components/ui/MosaicSpinner"; import { fetchProject, type ProjectDetail } from "@/lib/api/projects"; import { useWorkspaceId } from "@/lib/hooks"; interface BadgeStyle { label: string; bg: string; color: string; } interface StatusBadgeProps { style: BadgeStyle; } interface MetaItemProps { label: string; value: string; } function getProjectStatusStyle(status: string): BadgeStyle { switch (status) { case "PLANNING": return { label: "Planning", bg: "rgba(47,128,255,0.15)", color: "var(--primary)" }; case "ACTIVE": return { label: "Active", bg: "rgba(20,184,166,0.15)", color: "var(--success)" }; case "PAUSED": return { label: "Paused", bg: "rgba(245,158,11,0.15)", color: "var(--warn)" }; case "COMPLETED": return { label: "Completed", bg: "rgba(139,92,246,0.15)", color: "var(--purple)" }; case "ARCHIVED": return { label: "Archived", bg: "rgba(143,157,183,0.15)", color: "var(--muted)" }; default: return { label: status, bg: "rgba(143,157,183,0.15)", color: "var(--muted)" }; } } function getPriorityStyle(priority: string | null | undefined): BadgeStyle { switch (priority) { case "HIGH": return { label: "High", bg: "rgba(229,72,77,0.15)", color: "var(--danger)" }; case "MEDIUM": return { label: "Medium", bg: "rgba(245,158,11,0.15)", color: "var(--warn)" }; case "LOW": return { label: "Low", bg: "rgba(143,157,183,0.15)", color: "var(--muted)" }; default: return { label: "Unspecified", bg: "rgba(143,157,183,0.15)", color: "var(--muted)" }; } } function getTaskStatusStyle(status: string): BadgeStyle { switch (status) { case "NOT_STARTED": return { label: "Not Started", bg: "rgba(47,128,255,0.15)", color: "var(--primary)" }; case "IN_PROGRESS": return { label: "In Progress", bg: "rgba(245,158,11,0.15)", color: "var(--warn)" }; case "PAUSED": return { label: "Paused", bg: "rgba(143,157,183,0.15)", color: "var(--muted)" }; case "COMPLETED": return { label: "Completed", bg: "rgba(20,184,166,0.15)", color: "var(--success)" }; case "ARCHIVED": return { label: "Archived", bg: "rgba(143,157,183,0.15)", color: "var(--muted)" }; default: return { label: status, bg: "rgba(143,157,183,0.15)", color: "var(--muted)" }; } } function formatDate(iso: string | null | undefined): string { if (!iso) return "Not set"; try { return new Date(iso).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", }); } catch { return iso; } } function formatDateTime(iso: string | null | undefined): string { if (!iso) return "Not set"; try { return new Date(iso).toLocaleString("en-US", { month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit", }); } catch { return iso; } } function toFriendlyErrorMessage(error: unknown): string { const fallback = "We had trouble loading this project. Please try again when you're ready."; if (!(error instanceof Error)) { return fallback; } const message = error.message.trim(); if (message.toLowerCase().includes("not found")) { return "Project not found. It may have been deleted or you may not have access to it."; } return message || fallback; } function StatusBadge({ style: statusStyle }: StatusBadgeProps): ReactElement { return ( {statusStyle.label} ); } function MetaItem({ label, value }: MetaItemProps): ReactElement { return (

{label}

{value}

); } export default function ProjectDetailPage(): ReactElement { const router = useRouter(); const params = useParams<{ id: string | string[] }>(); const workspaceId = useWorkspaceId(); const rawProjectId = params.id; const projectId = Array.isArray(rawProjectId) ? (rawProjectId[0] ?? null) : rawProjectId; const [project, setProject] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const loadProject = useCallback(async (id: string, wsId: string): Promise => { try { setIsLoading(true); setError(null); const data = await fetchProject(id, wsId); setProject(data); } catch (err: unknown) { console.error("[ProjectDetail] Failed to fetch project:", err); setProject(null); setError(toFriendlyErrorMessage(err)); } finally { setIsLoading(false); } }, []); useEffect(() => { if (!projectId) { setProject(null); setError("The project link is invalid. Please return to the projects page."); setIsLoading(false); return; } if (!workspaceId) { setProject(null); setError("Select a workspace to view this project."); setIsLoading(false); return; } const id = projectId; const wsId = workspaceId; let cancelled = false; async function load(): Promise { try { setIsLoading(true); setError(null); const data = await fetchProject(id, wsId); if (!cancelled) { setProject(data); } } catch (err: unknown) { console.error("[ProjectDetail] Failed to fetch project:", err); if (!cancelled) { setProject(null); setError(toFriendlyErrorMessage(err)); } } finally { if (!cancelled) { setIsLoading(false); } } } void load(); return (): void => { cancelled = true; }; }, [projectId, workspaceId]); function handleRetry(): void { if (!projectId || !workspaceId) return; void loadProject(projectId, workspaceId); } function handleBack(): void { router.push("/projects"); } const projectStatus = project ? getProjectStatusStyle(project.status) : null; const projectPriority = project ? getPriorityStyle(project.priority) : null; const dueDate = project?.dueDate ?? project?.endDate; const creator = project?.creator.name && project.creator.name.trim().length > 0 ? `${project.creator.name} (${project.creator.email})` : (project?.creator.email ?? "Unknown"); return (
{isLoading ? (
) : error !== null ? (

{error}

) : project === null ? (

Project details are not available.

) : (

{project.name}

{projectStatus && } {projectPriority && }
{project.description ? (

{project.description}

) : (

No description provided.

)}

Tasks ({String(project._count.tasks)})

{project.tasks.length === 0 ? (

No tasks yet for this project.

) : (
{project.tasks.map((task, index) => (

{task.title}

Due: {formatDate(task.dueDate)}

))}
)}

Events ({String(project._count.events)})

{project.events.length === 0 ? (

No events scheduled for this project.

) : (
{project.events.map((event, index) => (

{event.title}

{formatDateTime(event.startTime)} - {formatDateTime(event.endTime)}

))}
)}
)}
); }