From 7b6763934c4e7367172ae0480013170e41e38772 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 8 May 2026 17:27:52 +0000 Subject: [PATCH] increased width of layout to 8xl --- .../(portfolio)/your-projects/live/page.tsx | 153 +++++++++++++----- 1 file changed, 111 insertions(+), 42 deletions(-) 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 48d8631..560aefe 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx @@ -11,12 +11,27 @@ import { hubspotUsers } from "@/app/db/schema/crm/hubspot_user_table"; import { uploadedFiles } from "@/app/db/schema/uploaded_files"; import { portfolioOrganisation } from "@/app/db/schema/portfolio_organisation"; import { organisation } from "@/app/db/schema/organisation"; -import { portfolioCapabilities, portfolioUsers } from "@/app/db/schema/portfolio"; +import { + portfolioCapabilities, + portfolioUsers, +} from "@/app/db/schema/portfolio"; import { dealMeasureApprovals } from "@/app/db/schema/approvals"; import { propertyRemovalRequests } from "@/app/db/schema/removal_requests"; import { user as userTable } from "@/app/db/schema/users"; -import type { HubspotDeal, DocStatusMap, DocStatus, MeasureDocProgress, PortfolioCapabilityType, ApprovalsByDeal, RemovalStatusByDeal, EffectiveRemovalState } from "./types"; -import { EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES, SURVEY_ALL_DOC_TYPES } from "./types"; +import type { + HubspotDeal, + DocStatusMap, + DocStatus, + MeasureDocProgress, + PortfolioCapabilityType, + ApprovalsByDeal, + RemovalStatusByDeal, + EffectiveRemovalState, +} from "./types"; +import { + EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES, + SURVEY_ALL_DOC_TYPES, +} from "./types"; import { getRequiredDocs } from "@/app/lib/measureDocumentRequirements"; import type { InferSelectModel } from "drizzle-orm"; import { Card, CardContent } from "@/app/shadcn_components/ui/card"; @@ -105,12 +120,17 @@ export default async function LiveReportingPage(props: { const link = await db .select({ hubspotCompanyId: organisation.hubspotCompanyId }) .from(portfolioOrganisation) - .innerJoin(organisation, eq(portfolioOrganisation.organisationId, organisation.id)) + .innerJoin( + organisation, + eq(portfolioOrganisation.organisationId, organisation.id), + ) .where(eq(portfolioOrganisation.portfolioId, BigInt(portfolioId))); const pageHeader = (
-
Live Projects
+
+ Live Projects +

{`Check in on your projects' progress with real-time data updates.`}

@@ -118,11 +138,13 @@ export default async function LiveReportingPage(props: {
); - const companyIds = link.map((l) => l.hubspotCompanyId).filter((id): id is string => !!id); + const companyIds = link + .map((l) => l.hubspotCompanyId) + .filter((id): id is string => !!id); if (companyIds.length === 0) { return ( -
+
{pageHeader} @@ -130,10 +152,13 @@ export default async function LiveReportingPage(props: {
-

No organisation linked

+

+ No organisation linked +

- A Domna administrator needs to connect this portfolio to an organisation in{" "} - Portfolio Settings before live tracking data can be displayed. + A Domna administrator needs to connect this portfolio to an + organisation in Portfolio Settings before live + tracking data can be displayed.

@@ -145,12 +170,22 @@ export default async function LiveReportingPage(props: { const rawDeals = await db .select({ deal: hubspotDealData, - coordinator: sql`CASE WHEN ${hubspotDealData.coordinator} IS NULL THEN NULL ELSE COALESCE(${coordinatorUser.firstName} || ' ' || ${coordinatorUser.lastName}, 'Domna Coordinator') END`, - designer: sql`CASE WHEN ${hubspotDealData.designer} IS NULL THEN NULL ELSE COALESCE(${designerUser.firstName} || ' ' || ${designerUser.lastName}, 'Domna Designer') END`, + coordinator: sql< + string | null + >`CASE WHEN ${hubspotDealData.coordinator} IS NULL THEN NULL ELSE COALESCE(${coordinatorUser.firstName} || ' ' || ${coordinatorUser.lastName}, 'Domna Coordinator') END`, + designer: sql< + string | null + >`CASE WHEN ${hubspotDealData.designer} IS NULL THEN NULL ELSE COALESCE(${designerUser.firstName} || ' ' || ${designerUser.lastName}, 'Domna Designer') END`, }) .from(hubspotDealData) - .leftJoin(coordinatorUser, eq(hubspotDealData.coordinator, coordinatorUser.hubspotOwnerId)) - .leftJoin(designerUser, eq(hubspotDealData.designer, designerUser.hubspotOwnerId)) + .leftJoin( + coordinatorUser, + eq(hubspotDealData.coordinator, coordinatorUser.hubspotOwnerId), + ) + .leftJoin( + designerUser, + eq(hubspotDealData.designer, designerUser.hubspotOwnerId), + ) .where(inArray(hubspotDealData.companyId, companyIds)); const deals = rawDeals.map(mapDbRowToHubspotDeal); @@ -178,7 +213,10 @@ export default async function LiveReportingPage(props: { ); userCapability = capRows .map((r) => r.capability) - .filter((c): c is "approver" | "contractor" => c === "approver" || c === "contractor"); + .filter( + (c): c is "approver" | "contractor" => + c === "approver" || c === "contractor", + ); } } @@ -247,7 +285,8 @@ export default async function LiveReportingPage(props: { seenDeals.add(row.hubspotDealId); let state: EffectiveRemovalState = "none"; if (row.status === "pending") { - state = row.type === "re_addition" ? "pending_re_addition" : "pending_removal"; + state = + row.type === "re_addition" ? "pending_re_addition" : "pending_removal"; } else if (row.type === "removal" && row.status === "approved") { state = "removed"; } else if (row.type === "re_addition" && row.status === "declined") { @@ -259,7 +298,10 @@ export default async function LiveReportingPage(props: { // Fetch document status for all deals — two-phase strategy: // Phase 1: query by dealId (reliable even when UPRN is missing from hubspot_deal_data) // Phase 2: UPRN fallback only for deals that returned no results in phase 1 - const docsByDealId = new Map>(); + const docsByDealId = new Map< + string, + Array<{ fileType: string; measureName: string | null }> + >(); if (dealIds.length > 0) { const phase1Rows = await db @@ -273,8 +315,11 @@ export default async function LiveReportingPage(props: { for (const row of phase1Rows) { if (!row.hubsotDealId || row.fileType === null) continue; - if (!docsByDealId.has(row.hubsotDealId)) docsByDealId.set(row.hubsotDealId, []); - docsByDealId.get(row.hubsotDealId)!.push({ fileType: row.fileType, measureName: row.measureName }); + if (!docsByDealId.has(row.hubsotDealId)) + docsByDealId.set(row.hubsotDealId, []); + docsByDealId + .get(row.hubsotDealId)! + .push({ fileType: row.fileType, measureName: row.measureName }); } } @@ -283,7 +328,13 @@ export default async function LiveReportingPage(props: { const fallbackUprns = dealsWithoutDocs .map((d) => d.uprn) .filter((u): u is string => !!u) - .map((u) => { try { return BigInt(u); } catch { return null; } }) + .map((u) => { + try { + return BigInt(u); + } catch { + return null; + } + }) .filter((u): u is bigint => u !== null); if (fallbackUprns.length > 0) { @@ -301,7 +352,11 @@ export default async function LiveReportingPage(props: { dealsWithoutDocs .filter((d) => d.uprn) .map((d) => { - try { return [String(BigInt(d.uprn!)), d.dealId] as [string, string]; } catch { return null; } + try { + return [String(BigInt(d.uprn!)), d.dealId] as [string, string]; + } catch { + return null; + } }) .filter((e): e is [string, string] => e !== null), ); @@ -311,7 +366,9 @@ export default async function LiveReportingPage(props: { const dealId = uprnToDealId.get(String(row.uprn)); if (!dealId) continue; if (!docsByDealId.has(dealId)) docsByDealId.set(dealId, []); - docsByDealId.get(dealId)!.push({ fileType: row.fileType, measureName: row.measureName }); + docsByDealId + .get(dealId)! + .push({ fileType: row.fileType, measureName: row.measureName }); } } @@ -319,9 +376,13 @@ export default async function LiveReportingPage(props: { const measuresByDealId = new Map(); for (const deal of deals) { const approved = approvalsByDeal[deal.dealId] ?? []; - const measures = approved.length > 0 - ? approved - : (deal.proposedMeasures ?? "").split(",").map((m: string) => m.trim()).filter(Boolean); + const measures = + approved.length > 0 + ? approved + : (deal.proposedMeasures ?? "") + .split(",") + .map((m: string) => m.trim()) + .filter(Boolean); measuresByDealId.set(deal.dealId, measures); } @@ -330,26 +391,32 @@ export default async function LiveReportingPage(props: { for (const [dealId, docs] of docsByDealId) { 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 installDocs = docs.filter( + (d) => !SURVEY_ALL_DOC_TYPES.has(d.fileType), + ); const surveyTypeSet = new Set(surveyDocs.map((d) => d.fileType)); const measures = measuresByDealId.get(dealId) ?? []; // Compute per-measure document progress against the requirements matrix - const measureProgress: MeasureDocProgress[] = measures.map((measureName) => { - const required = getRequiredDocs(measureName); - const docsForMeasure = installDocs.filter((d) => d.measureName === measureName); - const uploadedTypeSet = new Set(docsForMeasure.map((d) => d.fileType)); - const uploaded = required.filter((r) => uploadedTypeSet.has(r)); - return { - measureName, - required, - uploaded, - isComplete: uploaded.length === required.length, - uploadedCount: uploaded.length, - requiredCount: required.length, - }; - }); + const measureProgress: MeasureDocProgress[] = measures.map( + (measureName) => { + const required = getRequiredDocs(measureName); + const docsForMeasure = installDocs.filter( + (d) => d.measureName === measureName, + ); + const uploadedTypeSet = new Set(docsForMeasure.map((d) => d.fileType)); + const uploaded = required.filter((r) => uploadedTypeSet.has(r)); + return { + measureName, + required, + uploaded, + isComplete: uploaded.length === required.length, + uploadedCount: uploaded.length, + requiredCount: required.length, + }; + }, + ); let installStatus: DocStatus["installStatus"] = "none"; if (installDocs.length > 0) { @@ -367,7 +434,9 @@ export default async function LiveReportingPage(props: { docStatusMap[dealId] = { presentSurveyTypes: Array.from(surveyTypeSet), hasSurveyDocs: surveyDocs.length > 0, - isSurveyComplete: EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES.every((t) => surveyTypeSet.has(t)), + isSurveyComplete: EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES.every((t) => + surveyTypeSet.has(t), + ), hasInstallDocs: installDocs.length > 0, installStatus, measureProgress, @@ -375,7 +444,7 @@ export default async function LiveReportingPage(props: { } return ( -
+
{pageHeader}