"use client" import { useEffect, useState } from "react" import { useParams } from "next/navigation" import { CheckCircle2, XCircle, Clock, ChevronDown, ChevronUp, FileText, ExternalLink } from "lucide-react" interface Milestone { id: string name: string description: string progress: number status: string due_date: string } interface Deliverable { id: string name: string description: string file_url: string file_type: string status: string review_notes: string milestone_id: string } const statusIcon: Record = { pending: { icon: Clock, color: "#6a6a75" }, in_progress: { icon: Clock, color: "#3b82f6" }, awaiting_approval: { icon: Clock, color: "#f59e0b" }, approved: { icon: CheckCircle2, color: "#22c55e" }, rejected: { icon: XCircle, color: "#ef4444" }, completed: { icon: CheckCircle2, color: "#22c55e" }, } export default function ProjectDetail() { const { id } = useParams<{ id: string }>() const [data, setData] = useState<{ project: any; milestones: Milestone[]; deliverables: Deliverable[] } | null>(null) const [expanded, setExpanded] = useState([]) const [loadingMilestone, setLoadingMilestone] = useState(null) useEffect(() => { fetch(`/client-portal/api/projects/${id}`, { credentials: "include" }) .then((r) => r.json()) .then((d) => { if (d.project) setData({ ...d, milestones: d.milestones || [], deliverables: d.deliverables || [] }) }) .catch(() => {}) }, [id]) const handleAction = async (milestoneId: string, action: "approve" | "reject") => { setLoadingMilestone(milestoneId) try { const res = await fetch(`/client-portal/api/milestones/${milestoneId}/approve`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action }), }) if (res.ok) { const updated = await res.json() setData((prev) => { if (!prev) return prev return { ...prev, milestones: (prev.milestones || []).map((m) => m.id === milestoneId ? { ...m, ...updated.milestone } : m, ), deliverables: (prev.deliverables || []).map((d) => d.milestone_id === milestoneId ? { ...d, status: action === "approve" ? "approved" : "rejected" } : d, ), } }) } } finally { setLoadingMilestone(null) } } const toggleExpanded = (id: string) => { setExpanded((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]) } if (!data) { return (
) } const { project, milestones, deliverables } = data const overallProgress = milestones.length ? Math.round(milestones.reduce((sum, m) => sum + m.progress, 0) / milestones.length) : 0 return (

{project.name}

{project.description && (

{project.description}

)} {/* Overall progress bar */}
Overall Progress {overallProgress}%
{/* Milestones */}

Milestones

{milestones.length === 0 ? (

No milestones yet

) : ( (milestones || []).map((m) => { const si = statusIcon[m.status] || { icon: Clock, color: "#6a6a75" } const Icon = si.icon const milestoneDeliverables = deliverables.filter((d) => d.milestone_id === m.id) return (
{expanded.includes(m.id) && (
{m.description && (

{m.description}

)} {/* Deliverables */} {milestoneDeliverables.length > 0 && (
Deliverables {(milestoneDeliverables || []).map((d) => (
{d.name} {d.status.replace("_", " ")}
{d.file_url && ( View )}
))}
)} {/* Approve/Reject buttons */} {m.status === "awaiting_approval" && (
)}
)}
) }) )}
) }