import { useEffect } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { projectsApi } from "../api/projects"; import { issuesApi } from "../api/issues"; import { assetsApi } from "../api/assets"; import { usePanel } from "../context/PanelContext"; import { useCompany } from "../context/CompanyContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { queryKeys } from "../lib/queryKeys"; import { ProjectProperties } from "../components/ProjectProperties"; import { InlineEditor } from "../components/InlineEditor"; import { StatusBadge } from "../components/StatusBadge"; import { EntityRow } from "../components/EntityRow"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import type { Issue } from "@paperclip/shared"; export function ProjectDetail() { const { projectId } = useParams<{ projectId: string }>(); const { selectedCompanyId } = useCompany(); const { openPanel, closePanel } = usePanel(); const { setBreadcrumbs } = useBreadcrumbs(); const queryClient = useQueryClient(); const navigate = useNavigate(); const { data: project, isLoading, error } = useQuery({ queryKey: queryKeys.projects.detail(projectId!), queryFn: () => projectsApi.get(projectId!), enabled: !!projectId, }); const { data: allIssues } = useQuery({ queryKey: queryKeys.issues.list(selectedCompanyId!), queryFn: () => issuesApi.list(selectedCompanyId!), enabled: !!selectedCompanyId, }); const projectIssues = (allIssues ?? []).filter((i) => i.projectId === projectId); const invalidateProject = () => { queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(projectId!) }); if (selectedCompanyId) { queryClient.invalidateQueries({ queryKey: queryKeys.projects.list(selectedCompanyId) }); } }; const updateProject = useMutation({ mutationFn: (data: Record) => projectsApi.update(projectId!, data), onSuccess: invalidateProject, }); const uploadImage = useMutation({ mutationFn: async (file: File) => { if (!selectedCompanyId) throw new Error("No company selected"); return assetsApi.uploadImage(selectedCompanyId, file, `projects/${projectId ?? "draft"}`); }, }); useEffect(() => { setBreadcrumbs([ { label: "Projects", href: "/projects" }, { label: project?.name ?? projectId ?? "Project" }, ]); }, [setBreadcrumbs, project, projectId]); useEffect(() => { if (project) { openPanel( updateProject.mutate(data)} />); } return () => closePanel(); }, [project]); // eslint-disable-line react-hooks/exhaustive-deps if (isLoading) return

Loading...

; if (error) return

{error.message}

; if (!project) return null; return (
updateProject.mutate({ name })} as="h2" className="text-xl font-bold" /> updateProject.mutate({ description })} as="p" className="text-sm text-muted-foreground" placeholder="Add a description..." multiline imageUploadHandler={async (file) => { const asset = await uploadImage.mutateAsync(file); return asset.contentPath; }} />
Overview Issues ({projectIssues.length})
Status
{project.targetDate && (
Target Date

{project.targetDate}

)}
{projectIssues.length === 0 ? (

No issues in this project.

) : (
{projectIssues.map((issue) => ( } onClick={() => navigate(`/issues/${issue.id}`)} /> ))}
)}
); }