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 082ff27..3851c52 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DealStageChart.tsx @@ -1,11 +1,15 @@ "use client"; -import { useMemo } from "react"; import { BarList, Card, Title } from "@tremor/react"; +import ExpandableCountBar from "./ExpandableCountBar"; + +/* ================================ + STAGE ORDER +================================ */ const STAGE_ORDER = [ "Initial planning", - "Booking Team to contact tenant", + "Booking team to contact tenant", "In Assessment", "In Coordination", "In Design", @@ -13,109 +17,62 @@ const STAGE_ORDER = [ "Queries", ]; -const stage = (label: string) => STAGE_ORDER.find((s) => s === label)!; +const stage = (label: string) => + STAGE_ORDER.find((s) => s === label)!; + +/* ================================ + AFTER ASSESSMENT LOGIC +================================ */ -// 🔧 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"; - } + 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' + if (designStatusUpper === "UPLOADED") return "Completed"; return "In Design"; } - // Default to 'In Coordination' return "In Coordination"; }; -// 🏷️ Deal stage → display stage mapping +/* ================================ + STAGE LABELS +================================ */ + const STAGE_LABELS: Record = { - "1617223910": stage("Initial planning"), // 0 - [Ops] Backlog - "3583836399": stage("Initial planning"), // 0 - [Ops] Route Planning + "1617223910": stage("Initial planning"), + "3583836399": stage("Initial planning"), + "3589581001": stage("Booking team to contact tenant"), + "1984401629": stage("In Assessment"), - "3589581001": stage("Booking team to contact tenant"), // 1 - [Bookings] Ready for Bookings Team - "3569878239": stage("Booking team to contact tenant"), // 1 - [Bookings] Send initial booking SMS - "1617223911": stage("Booking team to contact tenant"), // 1 - [Bookings] Send Email - "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("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 + "2628233422": "AFTER_ASSESSMENT", + "2702650617": "AFTER_ASSESSMENT", + "2473886962": "AFTER_ASSESSMENT", + "1668803774": "AFTER_ASSESSMENT", - "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("In Assessment"), // 5 - [Ops] Properties to Review Manually - - // 🔧 ===== 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 - - "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) + "1887735998": stage("Queries"), }; -// 🧩 Reasons for exception stages (HA support / Not viable) -const STAGE_REASONS: Record = { - // ---- Needs support from HA ---- - "2663668937": "Booking issues due to tenant difficulties.", - "3061261536": "Awaab's Law", - "1887735999": "", - "3016601828": "RA is currently EPR C. Convert to EPC?", - "2769407183": "Needs HA heating upgrade. Domna/HA discussion required.", +/* ================================ + TYPES +================================ */ - // ---- Not viable for funding ---- - "1617223917": "", - "1887735998": "", -}; - -// ✅ Define an explicit Deal type for clarity interface Deal { dealname: string; landlordPropertyId: string; dealstage: string; coordinationStatus?: string; designStatus?: string; - reason?: string; [key: string]: any; } @@ -123,124 +80,167 @@ interface DealStageChartProps { deals: Deal[]; onOpenTable?: ( stageName: string, - filteredDeals: Deal[], - columns?: string[], - columnLabels?: { [key: string]: string } + filteredDeals: Deal[] ) => void; } -export function DealStageChart({ deals, onOpenTable }: DealStageChartProps) { - const data = useMemo(() => { - const counts: Record = {}; +/* ================================ + STAGE RESOLUTION ENGINE +================================ */ - deals.forEach((d) => { - const stageId = d.dealstage || "unknown"; - let stageName = STAGE_LABELS[stageId] || "Unknown Stage"; +const resolveDisplayStage = (deal: Deal): string => { + let stageName = STAGE_LABELS[deal.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 - } + if (stageName === "AFTER_ASSESSMENT") { + stageName = + getAfterAssessmentLabel( + deal.coordinationStatus, + deal.designStatus + ) || "In Coordination"; + } - // 🔧 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"; - } - } + if (stageName === "Initial planning") { + const coordStatusUpper = + deal.coordinationStatus?.toUpperCase() ?? ""; + if (coordStatusUpper === "RA ISSUE") { + stageName = "Queries"; + } + } - counts[stageName] = (counts[stageName] || 0) + 1; - }); + return stageName; +}; - return STAGE_ORDER.map((name) => ({ - name, - value: counts[name] || 0, - })); - }, [deals]); +/* ================================ + GENERIC STAGE FILTER +================================ */ + +const getDealsByResolvedStage = ( + deals: Deal[], + stages: string[] +): Deal[] => { + return deals.filter((deal) => + stages.includes(resolveDisplayStage(deal)) + ); +}; + +/* ================================ + COMPONENT +================================ */ + +export function DealStageChart({ + deals, + onOpenTable, +}: DealStageChartProps) { + + /* ---------- Build Chart Data ---------- */ + + const counts: Record = {}; + + deals.forEach((deal) => { + const stage = resolveDisplayStage(deal); + counts[stage] = (counts[stage] || 0) + 1; + }); + + const data = STAGE_ORDER.map((name) => ({ + name, + value: counts[name] || 0, + })); + + /* ---------- Summary Buckets ---------- */ + + const coordinationCompletedDeals = getDealsByResolvedStage( + deals, + ["In Design", "Completed"] + ); + + const designCompletedDeals = getDealsByResolvedStage( + deals, + ["Completed"] + ); const total = deals.length; - const handleBarClick = (value: { name: string; value: number }) => { - const filteredDeals: Deal[] = deals - .filter((d) => { - let stageName = STAGE_LABELS[d.dealstage] || "Unknown Stage"; + /* ---------- Shared Summary Click Handler ---------- */ + const handleSummaryClick = ( + label: string, + stages: string[] +) => { + const filteredDeals = getDealsByResolvedStage( + deals, + stages + ); - // 🔧 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 - } + onOpenTable?.(label, filteredDeals); +}; - // 🔧 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) => ({ - ...d, - // ✅ Always provide a string to avoid undefined issues - reason: STAGE_REASONS[d.dealstage] ?? "", - })); + /* ---------- Stage Bar Click ---------- */ - const isException = - value.name === "Needs support from HA" || - value.name === "Not viable for funding"; + const handleBarClick = (value: { + name: string; + value: number; + }) => { + const filteredDeals = getDealsByResolvedStage( + deals, + [value.name] + ); - // Add "Reason" column if it's an exception stage - const columns = isException - ? ["dealname", "landlordPropertyId", "reason"] - : ["dealname", "landlordPropertyId"]; - - const columnLabels = isException - ? { - dealname: "Address Ref.", - landlordPropertyId: "Property Ref.", - reason: "Reason", - } - : { - dealname: "Address Ref.", - landlordPropertyId: "Property Ref.", - }; - - // ✅ Explicit cast ensures no type mismatch - onOpenTable?.(value.name, filteredDeals, columns, columnLabels as Record); + onOpenTable?.(value.name, filteredDeals); }; - // Split into normal + exception stages + /* ---------- Split Normal vs Exception ---------- */ + const normalStages = data.filter( - (d) => - !["Queries"].includes(d.name) && - d.name !== "" + (d) => d.name !== "Queries" ); - const exceptionStages = data.filter((d) => - ["Queries"].includes(d.name) + const exceptionStages = data.filter( + (d) => d.name === "Queries" ); -return ( -
- {/* ✅ Main Progress Chart */} - -
- - Project Progress by Stage - -

- Click a bar to view related properties -

-

- Total: {total.toLocaleString()} properties -

-
+ return ( +
+ + {/* ===== Summary Panels ===== */} + + + + handleSummaryClick( + "Coordination Completed", + ["In Design", "Completed"] + ) + } + /> + + + handleSummaryClick( + "Design Completed", + ["Completed"] + ) + } + /> + + {/* ===== Main Progress Chart ===== */} + + +
+ + Project Progress by Stage + +

+ Click a bar to view related properties +

+

+ Total: {total.toLocaleString()} properties +

+
-
-
-
+ - {/* 🚨 Exception Chart */} - -
- - Needs HA Support & Not Viable - -

- Click to explore exception properties (reasons appear in table) -

-
+ {/* ===== Queries / Exception Chart ===== */} -
- -
-
-
-); -} + +
+ + Needs HA Support & Not Viable + +

+ Properties requiring attention +

+
+ +
+ +
+
+ +
+ ); +} \ No newline at end of file