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..f5879dbc 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,48 @@ 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 IOE/MTP COMPLETE") || + coordStatusUpper.includes("V2 IOE/MTP COMPLETE") || + coordStatusUpper.includes("V3 IOE/MTP COMPLETE") + ) { + // 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 +58,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 +113,8 @@ interface Deal { dealname: string; landlordPropertyId: string; dealstage: string; + coordinationStatus?: string; + designStatus?: string; reason?: string; [key: string]: any; } @@ -96,7 +135,22 @@ 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 + } + + // 🔧 For "Initial Planning" deals, check if coordination status is 'RA ISSUE' + if (stageName === "Initial planning") { + const coordStatusUpper = d.coordinationStatus?.toUpperCase() ?? ""; + if (coordStatusUpper === "RA ISSUE") { + stageName = "Queries"; + } + } + counts[stageName] = (counts[stageName] || 0) + 1; }); @@ -111,7 +165,22 @@ 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 + } + + // 🔧 For "Initial Planning" deals, check if coordination status is 'RA ISSUE' + if (stageName === "Initial planning") { + const coordStatusUpper = d.coordinationStatus?.toUpperCase() ?? ""; + if (coordStatusUpper === "RA ISSUE") { + stageName = "Queries"; + } + } + return stageName === value.name; }) .map((d) => ({ @@ -147,12 +216,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..31a7a6bb 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"; @@ -38,6 +40,22 @@ export default function LiveTracker({ deals }: ReportsProps) { const projectCodes = Object.keys(groupedDeals); const [currentProjectCode, setCurrentProjectCode] = useState(projectCodes[0]); const currentDeals = groupedDeals[currentProjectCode]; + + // Check if there's any survey data + const surveyorOutcomes = [ + "Surveyed", + "Surveyed - Pending Upload", + "Tenant Refusal", + "Other", + "Not Viable", + "Not Attempted", + "No Answer", + "Cancelled / No Show", + "Rescheduled", + ]; + const hasSurveyData = currentDeals.some((deal: any) => + deal.outcome && surveyorOutcomes.includes(deal.outcome) + ); const totalProperties = deals.length; const majorConditionDeals = deals.filter( (d) => d.dealstage === MAJOR_CONDITION_STAGE_ID @@ -164,7 +182,7 @@ export default function LiveTracker({ deals }: ReportsProps) { - + - - - + {hasSurveyData && ( + + + + )} diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/SurveyedResultsPieChart.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/SurveyedResultsPieChart.tsx index b126c43a..8162454d 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/SurveyedResultsPieChart.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/SurveyedResultsPieChart.tsx @@ -60,6 +60,11 @@ export default function SurveyedPieChart({ const [hovered, setHovered] = useState(null); + // Don't show the chart if there's no data + if (data.length === 0) { + return null; + } + return ( {/* Header */} 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 640b04c7..35542966 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx @@ -67,3 +67,5 @@ export default async function LiveReportingPage(props: { ); } + +