diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e11255b7..d1416d85 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -19,7 +19,8 @@ "editor.insertSpaces": true }, "extensions": [ - "esbenp.prettier-vscode" + "esbenp.prettier-vscode", + "Anthropic.claude-code" ] } } diff --git a/src/app/db/schema/crm/hubspot_deal_table.ts b/src/app/db/schema/crm/hubspot_deal_table.ts index 4993cf67..0a06d014 100644 --- a/src/app/db/schema/crm/hubspot_deal_table.ts +++ b/src/app/db/schema/crm/hubspot_deal_table.ts @@ -19,6 +19,9 @@ export const hubspotDealData = pgTable("hubspot_deal_data", { majorConditionIssuePhotos: text("major_condition_issue_photos"), majorConditionIssuePhotosS3: text("major_condition_issue_evidence_s3_url"), + coordinationStatus: text("coordination_status"), + designStatus: text("design_status"), + createdAt: timestamp("created_at", { precision: 6, withTimezone: true }) .defaultNow() .notNull(), diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx index b9d7dbe5..757fa79a 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx @@ -5,17 +5,55 @@ import { BarList, Card, Title } from "@tremor/react"; const STAGE_ORDER = [ "Initial planning", - "Booking team to contact tenant", - "Survey in progress", - "Coordination + design", - "Ready for install", - "Installed - Ready for Post work EPC", - "Needs support from HA", - "Not viable for funding", + "Booking Team to contact tenant", + "In Assessment", + "In Coordination", + "In Design", + "Completed", + "Queries", ]; const stage = (label: string) => STAGE_ORDER.find((s) => s === label)!; +// 🔧 Helper function to determine stage label after assessment based on coordination and design status +const getAfterAssessmentLabel = ( + coordinationStatus?: string, + designStatus?: string +): string => { + // Normalize strings to uppercase for case-insensitive comparison + const coordStatusUpper = coordinationStatus?.toUpperCase() ?? ""; + const designStatusUpper = designStatus?.toUpperCase() ?? ""; + + // 1. If coordination status is 'ra issue', return to 'queries' + if (coordStatusUpper === "RA ISSUE") { + return "Queries"; + } + + // 2. If coordination status contains v1/v2/v3 ioe/mtp completed, show as 'In Design' + if ( + coordStatusUpper.includes("V1") || + coordStatusUpper.includes("V2") || + coordStatusUpper.includes("V3") + ) { + if ( + coordStatusUpper.includes("IOE") || + coordStatusUpper.includes("MTP") + ) { + if (coordStatusUpper.includes("COMPLETED")) { + // 3. If design status is 'Uploaded', show as 'Completed' + if (designStatusUpper === "UPLOADED") { + return "Completed"; + } + // Otherwise show as 'In Design' + return "In Design"; + } + } + } + + // Default to 'In Coordination' + return "In Coordination"; +}; + // 🏷️ Deal stage → display stage mapping const STAGE_LABELS: Record = { "1617223910": stage("Initial planning"), // 0 - [Ops] Backlog @@ -27,34 +65,40 @@ const STAGE_LABELS: Record = { "1984184569": stage("Booking team to contact tenant"), // 1 - [Bookings] Phone booking "3569572028": stage("Booking team to contact tenant"), // 1 - [Bookings] Preferences received from Tenant "3570936026": stage("Booking team to contact tenant"), // 1 - [Bookings] Send Confirmation Comms - "2663668937": stage("Needs support from HA"), // 4 - [Bookings/Sales] Booking issues - needs HA support (Check with Aidan) - "1984401629": stage("Survey in progress"), // 2 - [Bookings/Ops/Sales] No Contact Details - Ready for Route + "2663668937": stage("Queries"), // 4 - [Bookings/Sales] Booking issues - needs HA support (Check with Aidan) + "1984401629": stage("In Assessment"), // 2 - [Bookings/Ops/Sales] No Contact Details - Ready for Route "2558220518": stage("Booking team to contact tenant"), // 1 - [Ops] Not attempted - needs reallocation "3474594026": stage("Booking team to contact tenant"), // 1 - [Ops/Bookings] Rebooked - Needs updating - "1617223912": stage("Survey in progress"), // 2 - [Ops] Ready for Assignment to Route - "1617223913": stage("Survey in progress"), // 2 - [Ops] Survey in Progress - "3206388924": stage("Survey in progress"), // 2 - [Ops] Surveyed - Pending Upload from Surveyor - "1617223915": stage("Survey in progress"), // 2 - [Ops] No Access - Need Sign Off - "1617223917": stage("Not viable for funding"), // 3 - [Ops] No Access - No Revisit + "1617223912": stage("In Assessment"), // 2 - [Ops] Ready for Assignment to Route + "1617223913": stage("In Assessment"), // 2 - [Ops] Survey in Progress + "3206388924": stage("In Assessment"), // 2 - [Ops] Surveyed - Pending Upload from Surveyor + "1617223915": stage("In Assessment"), // 2 - [Ops] No Access - Need Sign Off + "1617223917": stage("Queries"), // 3 - [Ops] No Access - No Revisit "2571417798": stage("Booking team to contact tenant"), // 1 - [Ops] Surveyed under 2019 - Needs Re-survey - "1617223916": stage("Coordination + design"), // 5 - [Ops] Properties to Review Manually - "2628341989": stage("Coordination + design"), // 5 - [Ops] Assessment needs correction - "3441170637": stage("Coordination + design"), // 5 - [Ops] Awaiting PV Design + "1617223916": stage("In Assessment"), // 5 - [Ops] Properties to Review Manually - "1887735998": stage("Not viable for funding"), // 3 - [Ops] Not Viable - "3061261536": stage("Needs support from HA"), // 4 - [Sales/Tech] Major condition issue - "1887735999": stage("Needs support from HA"), // 4 - [Ops] Needs HA Works - "3016601828": stage("Needs support from HA"), // 4 - [Engagement Team] EPC C Before Works - "1617223914": stage("Coordination + design"), // 5 - [Ops] Surveyed in Pashub, Transit Job to Co-ordination - "2628233422": stage("Coordination + design"), // 5 - [Coordination] Ready for coordination - "2702650617": stage("Coordination + design"), // 5 - [Design] Ready for Design - "2473886962": stage("Coordination + design"), // 5 - [Design] Design in progress + // 🔧 ===== AFTER ASSESSMENT - Determine exact stage using coordination/design status logic ===== + // These are special internal stages that will be processed by getAfterAssessmentLabel + // and mapped to their final display stages ("In Coordination", "In Design", "Completed") + "2628341989": "AFTER_ASSESSMENT", // 5 - [Ops] Assessment needs correction + "3441170637": "AFTER_ASSESSMENT", // 5 - [Ops] Awaiting PV Design - "1668803774": stage("Ready for install"), // 6 - [Finance] Ready for Invoicing - "3440363736": stage("Ready for install"), // 6 - [Finance] Needs Invoicing - Files Sent - "2769407183": stage("Needs support from HA"), // 4 - [Ops] PV - Needs Heating Upgrade (Pre EPR D) + "1617223914": "AFTER_ASSESSMENT", // 5 - [Ops] Surveyed in Pashub, Transit Job to Co-ordination + "2628233422": "AFTER_ASSESSMENT", // 5 - [Coordination] Ready for coordination + "2702650617": "AFTER_ASSESSMENT", // 5 - [Design] Ready for Design + "2473886962": "AFTER_ASSESSMENT", // 5 - [Design] Design in progress + + "1668803774": "AFTER_ASSESSMENT", // 6 - [Finance] Ready for Invoicing + "3440363736": "AFTER_ASSESSMENT", // 6 - [Finance] Needs Invoicing - Files Sent + + // 🔧 Exception stages (handled separately) + "1887735998": stage("Queries"), // 3 - [Ops] Not Viable + "3061261536": stage("Queries"), // 4 - [Sales/Tech] Major condition issue + "1887735999": stage("Queries"), // 4 - [Ops] Needs HA Works + "3016601828": stage("Queries"), // 4 - [Engagement Team] EPC C Before Works + "2769407183": stage("Queries"), // 4 - [Ops] PV - Needs Heating Upgrade (Pre EPR D) }; // 🧩 Reasons for exception stages (HA support / Not viable) @@ -76,6 +120,8 @@ interface Deal { dealname: string; landlordPropertyId: string; dealstage: string; + coordinationStatus?: string; + designStatus?: string; reason?: string; [key: string]: any; } @@ -96,7 +142,14 @@ export function DealStageChart({ deals, onOpenTable }: DealStageChartProps) { deals.forEach((d) => { const stageId = d.dealstage || "unknown"; - const stageName = STAGE_LABELS[stageId] || "Unknown Stage"; + let stageName = STAGE_LABELS[stageId] || "Unknown Stage"; + + // 🔧 For deals marked as "AFTER_ASSESSMENT", determine exact stage using coordination/design status logic + if (stageName === "AFTER_ASSESSMENT") { + const label = getAfterAssessmentLabel(d.coordinationStatus, d.designStatus); + stageName = label || "In Coordination"; // Default to "In Coordination" if no label returned + } + counts[stageName] = (counts[stageName] || 0) + 1; }); @@ -111,7 +164,14 @@ export function DealStageChart({ deals, onOpenTable }: DealStageChartProps) { const handleBarClick = (value: { name: string; value: number }) => { const filteredDeals: Deal[] = deals .filter((d) => { - const stageName = STAGE_LABELS[d.dealstage] || "Unknown Stage"; + let stageName = STAGE_LABELS[d.dealstage] || "Unknown Stage"; + + // 🔧 For deals marked as "AFTER_ASSESSMENT", determine exact stage using coordination/design status logic + if (stageName === "AFTER_ASSESSMENT") { + const label = getAfterAssessmentLabel(d.coordinationStatus, d.designStatus); + stageName = label || "In Coordination"; // Default to "In Coordination" if no label returned + } + return stageName === value.name; }) .map((d) => ({ @@ -147,12 +207,12 @@ export function DealStageChart({ deals, onOpenTable }: DealStageChartProps) { // Split into normal + exception stages const normalStages = data.filter( (d) => - !["Needs support from HA", "Not viable for funding"].includes(d.name) && + !["Queries"].includes(d.name) && d.name !== "" ); const exceptionStages = data.filter((d) => - ["Needs support from HA", "Not viable for funding"].includes(d.name) + ["Queries"].includes(d.name) ); return ( diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/Report.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/Report.tsx index abbca3dd..d7d9ed05 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/Report.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/Report.tsx @@ -16,9 +16,11 @@ import { motion } from "framer-motion"; interface ReportsProps { deals: Record[]; } + const MAJOR_CONDITION_STAGE_ID = "3061261536"; export default function LiveTracker({ deals }: ReportsProps) { + const groupedDeals = deals.reduce( (acc, deal) => { const project = deal.projectCode || "Unknown Project";