From 3a37938088521817b7c12ea40e81aa6c2b054141 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 20 Feb 2026 11:37:53 +0000 Subject: [PATCH 1/6] finished frontend --- .devcontainer/devcontainer.json | 3 +- src/app/db/schema/crm/hubspot_deal_table.ts | 3 + .../your-projects/live/DealStageChart.tsx | 124 +++++++++++++----- .../(portfolio)/your-projects/live/Report.tsx | 2 + 4 files changed, 99 insertions(+), 33 deletions(-) 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"; From ecfaf8e237c47d974a3556ca1a12cabf7ef1480c Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 20 Feb 2026 11:47:10 +0000 Subject: [PATCH 2/6] made it visual more appealing --- .../your-projects/live/DealStageChart.tsx | 23 ++++------- .../(portfolio)/your-projects/live/Report.tsx | 38 ++++++++++++++----- .../live/SurveyedResultsPieChart.tsx | 5 +++ 3 files changed, 41 insertions(+), 25 deletions(-) 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 757fa79a..d34850c9 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx @@ -31,23 +31,16 @@ const getAfterAssessmentLabel = ( // 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") + coordStatusUpper.includes("V1 IOE/MTP COMPLETE") || + coordStatusUpper.includes("V2 IOE/MTP COMPLETE") || + coordStatusUpper.includes("V3 IOE/MTP COMPLETE") ) { - 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"; - } + // 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' 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 d7d9ed05..6da00291 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/Report.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/Report.tsx @@ -40,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) => + deal.outcome && surveyorOutcomes.includes(deal.outcome) + ); const totalProperties = deals.length; const majorConditionDeals = deals.filter( (d) => d.dealstage === MAJOR_CONDITION_STAGE_ID @@ -166,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 */} From 2d22478cd2e0f8c8e0eb8ea2b23e600d9f7cdf1b Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 20 Feb 2026 13:20:17 +0000 Subject: [PATCH 3/6] dark and light favicon --- .../your-projects/live/DealStageChart.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 d34850c9..f5879dbc 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx @@ -143,6 +143,14 @@ export function DealStageChart({ deals, onOpenTable }: DealStageChartProps) { 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; }); @@ -165,6 +173,14 @@ export function DealStageChart({ deals, onOpenTable }: DealStageChartProps) { 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) => ({ From f40974965835aaaf9046e94e6a9cefd200a3836f Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 20 Feb 2026 13:37:45 +0000 Subject: [PATCH 4/6] npm build fix --- .../portfolio/[slug]/(portfolio)/your-projects/live/Report.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6da00291..31a7a6bb 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/Report.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/Report.tsx @@ -53,7 +53,7 @@ export default function LiveTracker({ deals }: ReportsProps) { "Cancelled / No Show", "Rescheduled", ]; - const hasSurveyData = currentDeals.some((deal) => + const hasSurveyData = currentDeals.some((deal: any) => deal.outcome && surveyorOutcomes.includes(deal.outcome) ); const totalProperties = deals.length; From ad4b48269d5dd485b6f3c63cc27f88f0c11a9047 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 20 Feb 2026 13:43:14 +0000 Subject: [PATCH 5/6] redploy with git --- src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx | 1 + 1 file changed, 1 insertion(+) 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..c2d9e706 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,4 @@ export default async function LiveReportingPage(props: { ); } + From bb14900e1ad2828df8487c49b1b6abdab8be19a6 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 20 Feb 2026 13:51:49 +0000 Subject: [PATCH 6/6] redploy with git --- src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx | 1 + 1 file changed, 1 insertion(+) 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 c2d9e706..35542966 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx @@ -68,3 +68,4 @@ export default async function LiveReportingPage(props: { ); } +