import { useEffect, useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { FileText, FolderOpen } from "lucide-react"; import { Link, Navigate, useCaseHref, useLocation, useParams } from "@/lib/router"; import { useBreadcrumbs } from "@/context/BreadcrumbContext"; import { casesApi, caseAttachmentUrl, isImageAttachment, type CaseSummary } from "@/api/cases"; import { ApiError } from "@/api/client"; import { agentsApi } from "@/api/agents"; import { projectsApi } from "@/api/projects"; import { CaseAttachmentsGallery } from "@/components/CaseAttachmentsGallery"; import { CaseFieldsTable } from "@/components/CaseFieldsPanel"; import { CaseIdentifierKey } from "@/components/CaseIdentifierKey"; import { EmptyState } from "@/components/EmptyState"; import { EntityRow } from "@/components/EntityRow"; import { IssueReferencePill } from "@/components/IssueReferencePill"; import { PageSkeleton } from "@/components/PageSkeleton"; import { CaseDocumentPreview } from "@/components/cases/CaseDocumentPreview"; import { CaseHandoffHistory } from "@/components/cases/CaseHandoffHistory"; import { CaseStatus } from "@/components/cases/CaseStatus"; import { CaseWorkSummary } from "@/components/cases/CaseWorkSummary"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { formatDateTime } from "@/lib/utils"; import { queryKeys } from "@/lib/queryKeys"; import { caseAuditHref } from "./audit/audit-navigation"; function CaseRelation({ item, search }: { item: Pick; search: string }) { const caseHref = useCaseHref(); return {item.identifier} · {item.caseType}} trailing={} className="px-0" />; } /** Cases are persistent records. This board page only reads them and their work. */ export function CaseDetail() { const { caseIdentifier } = useParams<{ caseIdentifier: string }>(); const { search } = useLocation(); const caseHref = useCaseHref(); const { setBreadcrumbs } = useBreadcrumbs(); const caseQuery = useQuery({ queryKey: queryKeys.cases.detail(caseIdentifier ?? ""), queryFn: () => casesApi.get(caseIdentifier!), enabled: !!caseIdentifier, }); const caseData = caseQuery.data; const listPath = `${caseHref()}${search}`; const childrenQuery = useQuery({ queryKey: queryKeys.cases.children(caseData?.id ?? ""), queryFn: () => casesApi.listChildren(caseData!.companyId, caseData!.id), enabled: !!caseData, }); const agentsQuery = useQuery({ queryKey: queryKeys.agents.list(caseData?.companyId ?? ""), queryFn: () => agentsApi.list(caseData!.companyId), enabled: !!caseData?.workIssueId, }); const projectQuery = useQuery({ queryKey: queryKeys.projects.detail(caseData?.projectId ?? ""), queryFn: () => projectsApi.get(caseData!.projectId!, caseData!.companyId), enabled: !!caseData?.projectId, }); const agents = useMemo(() => new Map((agentsQuery.data ?? []).map(agent => [agent.id, agent])), [agentsQuery.data]); useEffect(() => { setBreadcrumbs([{ label: "Cases", href: listPath }, { label: caseData?.identifier ?? caseIdentifier ?? "Case" }]); }, [setBreadcrumbs, listPath, caseData?.identifier, caseIdentifier]); if (!caseIdentifier) return ; if (caseQuery.isLoading) return
; if (caseQuery.isError || !caseData) { const unavailable = caseQuery.error instanceof ApiError && [403, 404].includes(caseQuery.error.status); return
{!unavailable && }
; } // A document is the authoritative version of a field with the same key. const documentKeys = new Set(caseData.documents.map(document => document.key.toLowerCase())); const taskDocuments = caseData.taskDocuments ?? []; const fields = Object.entries(caseData.fields).filter(([key, value]) => !documentKeys.has(key.toLowerCase()) && !(key.toLowerCase() === "title" && value === caseData.title) && !(key.toLowerCase() === "description" && value === caseData.summary)); const relatedTasks = caseData.issueLinks.filter(link => link.issueId !== caseData.workIssueId); const images = caseData.attachments.filter(isImageAttachment); const files = caseData.attachments.filter(attachment => !isImageAttachment(attachment)); const children = childrenQuery.data ?? []; return
{caseData.caseType}

{caseData.title}

{caseData.summary &&

{caseData.summary}

} {caseData.labels.length > 0 &&
{caseData.labels.map(label => {label.name})}
}
{fields.length > 0 &&

Details

} {(caseData.documents.length > 0 || taskDocuments.length > 0) &&

Documents

{taskDocuments.map(document => )} {caseData.documents.map(document => )}
{caseData.taskDocumentsOmitted &&

More documents are available on the linked tasks.

}
} {caseData.attachments.length > 0 &&

Attachments

{images.length > 0 && } {files.length > 0 && }
} {caseData.workIssueId && } {relatedTasks.length > 0 &&
Related tasks {relatedTasks.length}
    {relatedTasks.map(link =>
  • {link.issue.title}{link.role}
  • )}
} {(caseData.parent || children.length > 0 || childrenQuery.isError) &&
Related cases {caseData.parent &&

Parent

} {children.length > 0 &&

Children {children.length}

{children.map(child => )}
} {childrenQuery.isError &&

Child cases couldn’t be loaded.

}
} {!fields.length && !caseData.documents.length && !taskDocuments.length && !caseData.attachments.length &&

No findings recorded yet.

}
{projectQuery.data &&
Project
{projectQuery.data.name}
}
Created
Updated
View in Audit
; }