mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
save working version of summary information
This commit is contained in:
parent
302bdaf366
commit
9923e5ee13
1 changed files with 193 additions and 192 deletions
|
|
@ -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<string, string> = {
|
||||
"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<string, string> = {
|
||||
// ---- Needs support from HA ----
|
||||
"2663668937": "Booking issues due to tenant difficulties.",
|
||||
"3061261536": "Awaab's Law",
|
||||
"1887735999": "<Please contact the Tech Team for implementation>",
|
||||
"3016601828": "RA is currently EPR C. Convert to EPC?",
|
||||
"2769407183": "Needs HA heating upgrade. Domna/HA discussion required.",
|
||||
/* ================================
|
||||
TYPES
|
||||
================================ */
|
||||
|
||||
// ---- Not viable for funding ----
|
||||
"1617223917": "<Please contact the Tech Team for implementation>",
|
||||
"1887735998": "<Please contact the Tech Team for implementation>",
|
||||
};
|
||||
|
||||
// ✅ 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<string, number> = {};
|
||||
/* ================================
|
||||
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<string, number> = {};
|
||||
|
||||
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<string, string>);
|
||||
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 (
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* ✅ Main Progress Chart */}
|
||||
<Card className="bg-white rounded-xl shadow-sm hover:shadow-md transition-all duration-200 p-6 flex flex-col items-center justify-center">
|
||||
<div className="text-center mb-3">
|
||||
<Title className="text-gray-800 text-base font-semibold">
|
||||
Project Progress by Stage
|
||||
</Title>
|
||||
<p className="text-xs text-gray-500 mt-0.5">
|
||||
Click a bar to view related properties
|
||||
</p>
|
||||
<p className="text-xs text-gray-700 font-medium mt-1">
|
||||
Total: {total.toLocaleString()} properties
|
||||
</p>
|
||||
</div>
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
|
||||
{/* ===== Summary Panels ===== */}
|
||||
|
||||
|
||||
<ExpandableCountBar
|
||||
title="Coordination Completed"
|
||||
items={coordinationCompletedDeals}
|
||||
onClick={() =>
|
||||
handleSummaryClick(
|
||||
"Coordination Completed",
|
||||
["In Design", "Completed"]
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<ExpandableCountBar
|
||||
title="Design Completed"
|
||||
items={designCompletedDeals}
|
||||
onClick={() =>
|
||||
handleSummaryClick(
|
||||
"Design Completed",
|
||||
["Completed"]
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{/* ===== Main Progress Chart ===== */}
|
||||
|
||||
<Card className="bg-white rounded-xl shadow-sm p-6">
|
||||
<div className="text-center mb-3">
|
||||
<Title className="text-base font-semibold">
|
||||
Project Progress by Stage
|
||||
</Title>
|
||||
<p className="text-xs text-gray-500">
|
||||
Click a bar to view related properties
|
||||
</p>
|
||||
<p className="text-xs font-medium mt-1">
|
||||
Total: {total.toLocaleString()} properties
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-md">
|
||||
<BarList
|
||||
data={normalStages}
|
||||
color="blue"
|
||||
|
|
@ -248,30 +248,31 @@ return (
|
|||
className="cursor-pointer"
|
||||
onValueChange={handleBarClick}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
|
||||
{/* 🚨 Exception Chart */}
|
||||
<Card className="bg-white rounded-xl shadow-sm hover:shadow-md transition-all duration-200 p-6 flex flex-col items-center justify-center">
|
||||
<div className="text-center mb-3">
|
||||
<Title className="text-gray-800 text-base font-semibold">
|
||||
Needs HA Support & Not Viable
|
||||
</Title>
|
||||
<p className="text-xs text-gray-500 mt-0.5">
|
||||
Click to explore exception properties (reasons appear in table)
|
||||
</p>
|
||||
</div>
|
||||
{/* ===== Queries / Exception Chart ===== */}
|
||||
|
||||
<div className="w-full max-w-md">
|
||||
<BarList
|
||||
data={exceptionStages}
|
||||
color="red"
|
||||
sortOrder="none"
|
||||
className="cursor-pointer"
|
||||
onValueChange={handleBarClick}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<Card className="bg-white rounded-xl shadow-sm hover:shadow-md transition-all duration-200 p-6 flex flex-col items-center justify-center">
|
||||
<div className="text-center mb-3">
|
||||
<Title className="text-gray-800 text-base font-semibold">
|
||||
Needs HA Support & Not Viable
|
||||
</Title>
|
||||
<p className="text-xs text-gray-500 mt-0.5">
|
||||
Properties requiring attention
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-md">
|
||||
<BarList
|
||||
data={exceptionStages}
|
||||
color="red"
|
||||
sortOrder="none"
|
||||
className="cursor-pointer"
|
||||
onValueChange={handleBarClick}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue