From f04fb8f0265fd15dae9adcc330906a997d246cb1 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 17 Apr 2026 18:49:10 +0000 Subject: [PATCH] updated install documents ui --- .../live-tracking/property-documents/route.ts | 2 + .../your-projects/live/DocumentTable.tsx | 101 +++++++--- .../live/DocumentTableColumns.tsx | 77 ++++++-- .../your-projects/live/PropertyDrawer.tsx | 182 +++++++++++------- .../your-projects/live/PropertyTable.tsx | 6 +- .../live/PropertyTableColumns.tsx | 4 +- .../(portfolio)/your-projects/live/page.tsx | 53 ++++- .../(portfolio)/your-projects/live/types.ts | 27 ++- 8 files changed, 327 insertions(+), 125 deletions(-) diff --git a/src/app/api/live-tracking/property-documents/route.ts b/src/app/api/live-tracking/property-documents/route.ts index 0e73dc6..270d826 100644 --- a/src/app/api/live-tracking/property-documents/route.ts +++ b/src/app/api/live-tracking/property-documents/route.ts @@ -32,6 +32,7 @@ export async function GET(req: Request) { source: uploadedFiles.source, uprn: uploadedFiles.uprn, landlordPropertyId: uploadedFiles.landlordPropertyId, + measureName: uploadedFiles.measureName, }) .from(uploadedFiles) .where(condition); @@ -45,6 +46,7 @@ export async function GET(req: Request) { s3UploadTimestamp: row.s3UploadTimestamp.toISOString(), uprn: row.uprn !== null ? String(row.uprn) : null, landlordPropertyId: row.landlordPropertyId, + measureName: row.measureName ?? null, })); return NextResponse.json(documents); diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx index 195b534..01baed9 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx @@ -31,7 +31,8 @@ import { createDocumentTableColumns } from "./DocumentTableColumns"; import ContractorUploadModal from "./ContractorUploadModal"; import type { ClassifiedDeal, DocStatusMap, PortfolioCapabilityType } from "./types"; -type SurveyStatusFilter = "all" | "none" | "partial" | "complete"; +type RetroAssessmentFilter = "all" | "none" | "partial" | "complete"; +type InstallStatusFilter = "all" | "none" | "hasDocs" | "partial" | "complete"; interface DocumentTableProps { data: ClassifiedDeal[]; @@ -54,7 +55,8 @@ function escapeCell(value: unknown): string { export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfolioId, userCapability }: DocumentTableProps) { const [globalFilter, setGlobalFilter] = useState(""); - const [surveyStatusFilter, setSurveyStatusFilter] = useState("all"); + const [retroAssessmentFilter, setRetroAssessmentFilter] = useState("all"); + const [installStatusFilter, setInstallStatusFilter] = useState("all"); const [sorting, setSorting] = useState([]); const [pagination, setPagination] = useState({ pageIndex: 0, @@ -63,15 +65,26 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfo const [uploadDeal, setUploadDeal] = useState(null); const filteredData = useMemo(() => { - if (surveyStatusFilter === "all") return data; return data.filter((d) => { const status = d.uprn ? docStatusMap[d.uprn] : undefined; - if (surveyStatusFilter === "none") return !status || !status.hasDocs; - if (surveyStatusFilter === "partial") return !!status?.hasDocs && !status.isComplete; - if (surveyStatusFilter === "complete") return !!status?.isComplete; + + if (retroAssessmentFilter !== "all") { + if (retroAssessmentFilter === "none" && !(!status || !status.hasSurveyDocs)) return false; + if (retroAssessmentFilter === "partial" && !(status?.hasSurveyDocs && !status.isSurveyComplete)) return false; + if (retroAssessmentFilter === "complete" && !status?.isSurveyComplete) return false; + } + + if (installStatusFilter !== "all") { + const s = status?.installStatus ?? "none"; + if (installStatusFilter === "none" && s !== "none") return false; + if (installStatusFilter === "hasDocs" && s !== "hasDocs") return false; + if (installStatusFilter === "partial" && s !== "partial") return false; + if (installStatusFilter === "complete" && s !== "all") return false; + } + return true; }); - }, [data, surveyStatusFilter, docStatusMap]); + }, [data, retroAssessmentFilter, installStatusFilter, docStatusMap]); const columns = useMemo( () => createDocumentTableColumns( @@ -98,19 +111,27 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfo const downloadCsv = () => { const rows = table.getFilteredRowModel().rows; - const header = "Address,Landlord ID,Survey Status"; + const header = "Address,Landlord ID,Retrofit Assessment Status,Install Docs Status"; const body = rows .map((row) => { const status = row.original.uprn ? docStatusMap[row.original.uprn] : undefined; - const surveyStatus = status?.isComplete + const retroStatus = status?.isSurveyComplete ? "Complete" - : status?.hasDocs + : status?.hasSurveyDocs ? "Partial" : "No Docs"; + const installStatusMap: Record = { + all: "All Measures", + partial: "Some Measures", + hasDocs: "Has Docs", + none: "No Docs", + }; + const installStatus = installStatusMap[status?.installStatus ?? "none"]; return [ escapeCell(row.original.dealname), escapeCell(row.original.landlordPropertyId), - surveyStatus, + retroStatus, + installStatus, ].join(","); }) .join("\n"); @@ -127,11 +148,19 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfo const currentPage = table.getState().pagination.pageIndex + 1; const totalFiltered = table.getFilteredRowModel().rows.length; - const surveyStatusLabel: Record = { - all: "All statuses", - none: "No Survey Docs", - partial: "Partial Survey Docs", - complete: "Complete Survey Docs", + const retroAssessmentLabel: Record = { + all: "All retrofit statuses", + none: "No Retrofit Docs", + partial: "Partial Retrofit Docs", + complete: "Complete Retrofit Docs", + }; + + const installStatusLabel: Record = { + all: "All install statuses", + none: "No Install Docs", + hasDocs: "Has Install Docs", + partial: "Some Measures", + complete: "All Measures", }; return ( @@ -152,22 +181,42 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfo /> - {/* Survey status filter */} + {/* Retrofit assessment filter */} + + {/* Install docs filter */} + @@ -192,7 +241,7 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfo {" "} of{" "} {totalFiltered}{" "} - {surveyStatusFilter !== "all" ? `(${surveyStatusLabel[surveyStatusFilter].toLowerCase()}) ` : ""} + {(retroAssessmentFilter !== "all" || installStatusFilter !== "all") ? "(filtered) " : ""} propert{totalFiltered === 1 ? "y" : "ies"}

diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx index 1b762c9..d630130 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx @@ -1,7 +1,7 @@ "use client"; import { ColumnDef } from "@tanstack/react-table"; -import { ArrowUpDown, CheckCircle2, AlertCircle, FileX, Upload } from "lucide-react"; +import { ArrowUpDown, CheckCircle2, AlertCircle, FileX, Upload, Package } from "lucide-react"; import type { ClassifiedDeal, DocStatusMap, DocStatus } from "./types"; function SortableHeader({ @@ -22,8 +22,8 @@ function SortableHeader({ ); } -function SurveyStatusBadge({ status }: { status: DocStatus | undefined }) { - if (status?.isComplete) { +function RetroAssessmentBadge({ status }: { status: DocStatus | undefined }) { + if (status?.isSurveyComplete) { return ( @@ -31,7 +31,7 @@ function SurveyStatusBadge({ status }: { status: DocStatus | undefined }) { ); } - if (status?.hasDocs) { + if (status?.hasSurveyDocs) { return ( @@ -47,6 +47,40 @@ function SurveyStatusBadge({ status }: { status: DocStatus | undefined }) { ); } +function InstallDocsBadge({ status }: { status: DocStatus | undefined }) { + const installStatus = status?.installStatus ?? "none"; + if (installStatus === "all") { + return ( + + + All Measures + + ); + } + if (installStatus === "partial") { + return ( + + + Some Measures + + ); + } + if (installStatus === "hasDocs") { + return ( + + + Has Docs + + ); + } + return ( + + + No Docs + + ); +} + export function createDocumentTableColumns( onOpenDrawer: (uprn: string | null, landlordPropertyId: string | null, dealname: string | null) => void, docStatusMap: DocStatusMap = {}, @@ -81,19 +115,38 @@ export function createDocumentTableColumns( enableHiding: false, }, - // ── Survey Status ───────────────────────────────────────────────────── + // ── Retrofit Assessment Docs Status ─────────────────────────────────── { - id: "surveyStatus", + id: "retroAssessmentStatus", accessorFn: (row) => { const status = row.uprn ? docStatusMap[row.uprn] : undefined; - if (status?.isComplete) return 2; - if (status?.hasDocs) return 1; + if (status?.isSurveyComplete) return 2; + if (status?.hasSurveyDocs) return 1; return 0; }, - header: ({ column }) => , + header: ({ column }) => , cell: ({ row }) => { const status = row.original.uprn ? docStatusMap[row.original.uprn] : undefined; - return ; + return ; + }, + enableHiding: false, + }, + + // ── Install Docs Status ─────────────────────────────────────────────── + { + id: "installDocs", + accessorFn: (row) => { + const status = row.uprn ? docStatusMap[row.uprn] : undefined; + const s = status?.installStatus ?? "none"; + if (s === "all") return 3; + if (s === "partial") return 2; + if (s === "hasDocs") return 1; + return 0; + }, + header: ({ column }) => , + cell: ({ row }) => { + const status = row.original.uprn ? docStatusMap[row.original.uprn] : undefined; + return ; }, enableHiding: false, }, @@ -111,11 +164,11 @@ export function createDocumentTableColumns( let icon: React.ReactNode; let className: string; - if (status?.isComplete) { + if (status?.isSurveyComplete) { icon = ; className = "inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium border border-emerald-200 text-emerald-700 bg-emerald-50 hover:bg-emerald-100 hover:border-emerald-300 transition-all duration-150 whitespace-nowrap"; - } else if (status?.hasDocs) { + } else if (status?.hasSurveyDocs) { icon = ; className = "inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium border border-amber-200 text-amber-700 bg-amber-50 hover:bg-amber-100 hover:border-amber-300 transition-all duration-150 whitespace-nowrap"; diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDrawer.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDrawer.tsx index 6dc83ef..63ef131 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDrawer.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDrawer.tsx @@ -11,6 +11,7 @@ import { FolderOpen, X, ExternalLink, + HardHat, } from "lucide-react"; import { Drawer, @@ -21,10 +22,11 @@ import { DrawerDescription, } from "@/app/shadcn_components/ui/drawer"; import type { PropertyDocument } from "./types"; -import { EXPECTED_SURVEY_DOC_TYPES } from "./types"; +import { EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES, SURVEY_ALL_DOC_TYPES } from "./types"; -// Human-readable labels for the main DB fileType enum values +// Human-readable labels for all DB fileType enum values const DOC_TYPE_LABELS: Record = { + // Survey / retrofit assessment docs photo_pack: "Photo Pack", site_note: "Site Note", rd_sap_site_note: "RdSAP Site Note", @@ -34,13 +36,43 @@ const DOC_TYPE_LABELS: Record = { par_photo_pack: "PAR Photo Pack", pas_2023_property: "PAS 2023 Property Report", pas_2023_occupancy: "PAS 2023 Occupancy Report", + ecmk_site_note: "ECMK Site Note", + ecmk_rd_sap_site_note: "ECMK RdSAP Site Note", + ecmk_survey_xml: "ECMK Survey XML", + // Install docs — photos + pre_photo: "Pre-Install Photos", + mid_photo: "Mid-Install Photos", + post_photo: "Post-Install Photos", + loft_hatch_photo: "Loft Hatch & Draft Excluder Photos", + dmev_photos: "DMEV Photos (Wetrooms)", + door_undercut_photos: "Door Undercut Photos", + trickle_vent_photos: "Trickle Vent Photos", + // Install docs — pre-installation + pre_installation_building_inspection: "PIBI / Tech Survey", + point_of_work_risk_assessment: "Point of Work Risk Assessment", + // Install docs — compliance & lodgement + claim_of_compliance: "DOCC 2030 (Claim of Compliance)", + mcs_compliance_certificate: "MCS Compliance Certificate", + certificate_of_conformity: "Certificate of Conformity", + minor_works_electrical_certificate: "Minor Works Electrical Certificate", + trustmark_licence_numbers: "TrustMark Licence Numbers", + operative_competency: "Operative Competency", + // Install docs — ventilation + ventilation_assessment_checklist: "Ventilation Assessment Checklist", + anemometer_readings: "Anemometer Readings", + commissioning_records: "Commissioning Records", + part_f_ventilation_document: "Approved Document Part F", + // Install docs — handover & warranties + handover_pack: "Handover Pack", + insurance_guarantee: "Insurance Backed Guarantee (IBG)", + workmanship_warranty: "Workmanship Warranty", + g98_notification: "G98 / G99 Notification", + // Install docs — qualifications & other + installer_qualifications: "Installer Qualifications", + installer_feedback: "Installer Feedback", + contractor_other: "Other", }; -// All survey docs go under this group for now (extensible later) -function getDocCategory(_docType: string): string { - return "Survey Documents"; -} - function formatDate(iso: string): string { try { return new Date(iso).toLocaleDateString("en-GB", { @@ -56,7 +88,7 @@ function formatDate(iso: string): string { // ----------------------------------------------------------------------- // Individual document row // ----------------------------------------------------------------------- -function DocumentRow({ doc }: { doc: PropertyDocument }) { +function DocumentRow({ doc, showMeasure }: { doc: PropertyDocument; showMeasure?: boolean }) { const label = DOC_TYPE_LABELS[doc.docType] ?? doc.docType; const { mutate: download, isPending: signing } = useMutation({ @@ -90,7 +122,10 @@ function DocumentRow({ doc }: { doc: PropertyDocument }) {

{label}

- {formatDate(doc.s3UploadTimestamp)} + {showMeasure && doc.measureName + ? <>{doc.measureName} · {formatDate(doc.s3UploadTimestamp)} + : formatDate(doc.s3UploadTimestamp) + }

@@ -161,20 +196,16 @@ export default function PropertyDrawer({ } const documents = open ? (fetchedDocuments as PropertyDocument[]) : lastDocumentsRef.current; - // Group docs by category for display - const grouped = documents.reduce< - Record - >((acc, doc) => { - const category = getDocCategory(doc.docType); - (acc[category] ??= []).push(doc); - return acc; - }, {}); + // Split documents into the two sections + const retrofitDocs = documents.filter((d) => SURVEY_ALL_DOC_TYPES.has(d.docType)); + const installDocs = documents.filter((d) => !SURVEY_ALL_DOC_TYPES.has(d.docType)); const hasDocuments = documents.length > 0; - const presentTypes = new Set(documents.map((d) => d.docType)); - const missingTypes = EXPECTED_SURVEY_DOC_TYPES.filter( - (t) => !presentTypes.has(t), + // Missing mandatory retrofit assessment docs (ecmk types are optional — not shown as missing) + const presentRetrofitTypes = new Set(retrofitDocs.map((d) => d.docType)); + const missingRetrofitTypes = EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES.filter( + (t) => !presentRetrofitTypes.has(t), ); return ( @@ -220,7 +251,7 @@ export default function PropertyDrawer({ {/* Body */} -
+
{/* Loading state */} {isFetching && (
@@ -248,7 +279,7 @@ export default function PropertyDrawer({
)} - {/* Empty state — shows all missing doc types */} + {/* Empty state */} {!isFetching && !isError && !hasDocuments && (
@@ -259,15 +290,14 @@ export default function PropertyDrawer({ No documents available

- All {EXPECTED_SURVEY_DOC_TYPES.length} survey documents are - outstanding. + All {EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES.length} retrofit assessment documents are outstanding.

- Missing Documents ({missingTypes.length}) + Missing Documents ({missingRetrofitTypes.length})

- {missingTypes.map((t) => ( + {missingRetrofitTypes.map((t) => (
)} - {/* Document groups */} - {!isFetching && - !isError && - hasDocuments && - Object.entries(grouped).map(([category, docs]) => ( + {!isFetching && !isError && hasDocuments && ( + <> + {/* ── Retrofit Assessment Documents ── */}

- {category} + Retrofit Assessment Documents

-
- {docs.map((doc) => ( - - ))} -
-
- ))} -
- - {/* Missing documents section — shown when some but not all docs are present */} - {!isFetching && - !isError && - hasDocuments && - missingTypes.length > 0 && ( - -

- Missing Documents ({missingTypes.length}) -

-
- {missingTypes.map((t) => ( -
- - - {DOC_TYPE_LABELS[t] ?? t} - + {retrofitDocs.length > 0 ? ( +
+ {retrofitDocs.map((doc) => ( + + ))}
- ))} -
- + ) : ( +

None uploaded yet.

+ )} + + {/* Missing mandatory retrofit assessment docs */} + {missingRetrofitTypes.length > 0 && ( +
+

+ Missing ({missingRetrofitTypes.length}) +

+ {missingRetrofitTypes.map((t) => ( +
+ + + {DOC_TYPE_LABELS[t] ?? t} + +
+ ))} +
+ )} + + + {/* ── Install Documents ── */} + +

+ + Install Documents +

+ {installDocs.length > 0 ? ( +
+ {installDocs.map((doc) => ( + + ))} +
+ ) : ( +

No install documents uploaded yet.

+ )} +
+ )} +
{/* Footer */} diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTable.tsx index ac29be0..db9c867 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTable.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTable.tsx @@ -126,9 +126,9 @@ export default function PropertyTable({ data, onOpenDrawer, onOpenDetail, showDo if (docFilter !== "all") { result = result.filter((d) => { const status = d.uprn ? docStatusMap[d.uprn] : undefined; - if (docFilter === "none") return !status || !status.hasDocs; - if (docFilter === "has_docs") return !!status?.hasDocs; - if (docFilter === "incomplete") return !!status?.hasDocs && !status.isComplete; + if (docFilter === "none") return !status || !status.hasSurveyDocs; + if (docFilter === "has_docs") return !!status?.hasSurveyDocs; + if (docFilter === "incomplete") return !!status?.hasSurveyDocs && !status.isSurveyComplete; return true; }); } diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTableColumns.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTableColumns.tsx index 9562418..03804ee 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTableColumns.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTableColumns.tsx @@ -285,8 +285,8 @@ export function createPropertyTableColumns( cell: ({ row }) => { const uprn = row.original.uprn ?? ""; const status = uprn ? docStatusMap[uprn] : undefined; - const isComplete = status?.isComplete; - const hasDocs = status?.hasDocs; + const isComplete = status?.isSurveyComplete; + const hasDocs = status?.hasSurveyDocs; let icon: React.ReactNode; let className: string; diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx index 0cbc869..db5a3ae 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx @@ -13,7 +13,7 @@ import { portfolioCapabilities } from "@/app/db/schema/portfolio"; import { dealMeasureApprovals } from "@/app/db/schema/approvals"; import { user as userTable } from "@/app/db/schema/users"; import type { HubspotDeal, DocStatusMap, DocStatus, PortfolioCapabilityType, ApprovalsByDeal } from "./types"; -import { EXPECTED_SURVEY_DOC_TYPES } from "./types"; +import { EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES, SURVEY_ALL_DOC_TYPES } from "./types"; import type { InferSelectModel } from "drizzle-orm"; import { Card, CardContent } from "@/app/shadcn_components/ui/card"; import { Building2 } from "lucide-react"; @@ -184,23 +184,58 @@ export default async function LiveReportingPage(props: { if (uprnList.length > 0) { const docRows = await db - .select() + .select({ + uprn: uploadedFiles.uprn, + fileType: uploadedFiles.fileType, + measureName: uploadedFiles.measureName, + }) .from(uploadedFiles) .where(inArray(uploadedFiles.uprn, uprnList)); - const grouped: Record> = {}; + // Group docs by UPRN + const docsByUprn = new Map>(); for (const row of docRows) { if (row.uprn === null || row.fileType === null) continue; const key = String(row.uprn); - (grouped[key] ??= new Set()).add(row.fileType); + if (!docsByUprn.has(key)) docsByUprn.set(key, []); + docsByUprn.get(key)!.push({ fileType: row.fileType, measureName: row.measureName }); } - for (const [uprn, types] of Object.entries(grouped)) { - const presentTypes = Array.from(types); + // Build measures lookup from deals (uprn → proposed measure names) + const measuresByUprn = new Map(); + for (const deal of deals) { + if (deal.uprn) { + const key = String(deal.uprn); + const measures = (deal.proposedMeasures ?? "") + .split(",").map((m: string) => m.trim()).filter(Boolean); + measuresByUprn.set(key, measures); + } + } + + for (const [uprn, docs] of docsByUprn) { + const surveyDocs = docs.filter((d) => SURVEY_ALL_DOC_TYPES.has(d.fileType)); + const installDocs = docs.filter((d) => !SURVEY_ALL_DOC_TYPES.has(d.fileType)); + const surveyTypeSet = new Set(surveyDocs.map((d) => d.fileType)); + + const measures = measuresByUprn.get(uprn) ?? []; + let installStatus: DocStatus["installStatus"] = "none"; + if (installDocs.length > 0) { + if (measures.length === 0) { + installStatus = "hasDocs"; + } else { + const measuresWithDocs = new Set( + installDocs.map((d) => d.measureName).filter(Boolean), + ); + installStatus = measures.every((m) => measuresWithDocs.has(m)) ? "all" : "partial"; + } + } + const status: DocStatus = { - presentTypes, - hasDocs: presentTypes.length > 0, - isComplete: EXPECTED_SURVEY_DOC_TYPES.every((t) => types.has(t)), + presentSurveyTypes: Array.from(surveyTypeSet), + hasSurveyDocs: surveyDocs.length > 0, + isSurveyComplete: EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES.every((t) => surveyTypeSet.has(t)), + hasInstallDocs: installDocs.length > 0, + installStatus, }; docStatusMap[uprn] = status; } diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts index a244f68..749a97d 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts @@ -205,10 +205,11 @@ export type PropertyDocument = { s3UploadTimestamp: string; // ISO string uprn: string | null; landlordPropertyId: string | null; + measureName: string | null; // set for install docs }; -// All survey document types expected for a complete survey -export const EXPECTED_SURVEY_DOC_TYPES = [ +// Mandatory retrofit assessment doc types (used for completeness check — ecmk types are optional) +export const EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES = [ "photo_pack", "site_note", "rd_sap_site_note", @@ -220,10 +221,26 @@ export const EXPECTED_SURVEY_DOC_TYPES = [ "pas_2023_occupancy", ] as const; +// All survey-adjacent types (including optional ecmk docs) — used for display categorisation +export const SURVEY_ALL_DOC_TYPES = new Set([ + ...EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES, + "ecmk_site_note", + "ecmk_rd_sap_site_note", + "ecmk_survey_xml", +]); + export type DocStatus = { - presentTypes: string[]; - hasDocs: boolean; - isComplete: boolean; // all EXPECTED_SURVEY_DOC_TYPES present + // Retrofit assessment docs + presentSurveyTypes: string[]; + hasSurveyDocs: boolean; + isSurveyComplete: boolean; // all 9 EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES present (ecmk not counted) + // Install docs + hasInstallDocs: boolean; + installStatus: "none" | "partial" | "hasDocs" | "all"; + // "all" = install docs exist for every proposed measure + // "partial" = some (but not all) proposed measures have docs + // "hasDocs" = has install docs but no measures defined on the deal + // "none" = no install docs at all }; export type DocStatusMap = Record; // keyed by UPRN string