mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
Merge pull request #179 from Hestia-Homes/feature/guiness_live_tracking
Feature/guiness live tracking
This commit is contained in:
commit
140fd27263
6 changed files with 143 additions and 43 deletions
|
|
@ -19,7 +19,8 @@
|
|||
"editor.insertSpaces": true
|
||||
},
|
||||
"extensions": [
|
||||
"esbenp.prettier-vscode"
|
||||
"esbenp.prettier-vscode",
|
||||
"Anthropic.claude-code"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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<string, string> = {
|
||||
"1617223910": stage("Initial planning"), // 0 - [Ops] Backlog
|
||||
|
|
@ -27,34 +58,40 @@ const STAGE_LABELS: Record<string, string> = {
|
|||
"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 (
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ import { motion } from "framer-motion";
|
|||
interface ReportsProps {
|
||||
deals: Record<string, any>[];
|
||||
}
|
||||
|
||||
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) {
|
|||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<CardContent className={`grid gap-6 ${hasSurveyData ? "grid-cols-1 md:grid-cols-2" : "grid-cols-1 max-w-2xl mx-auto"}`}>
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.01 }}
|
||||
className="border rounded-xl p-5 bg-white shadow-sm hover:shadow-md transition"
|
||||
|
|
@ -175,15 +193,17 @@ export default function LiveTracker({ deals }: ReportsProps) {
|
|||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.01 }}
|
||||
className="border rounded-xl p-5 bg-white shadow-sm hover:shadow-md transition"
|
||||
>
|
||||
<SurveyedPieChart
|
||||
deals={currentDeals}
|
||||
onOpenTable={handleOpenTable}
|
||||
/>
|
||||
</motion.div>
|
||||
{hasSurveyData && (
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.01 }}
|
||||
className="border rounded-xl p-5 bg-white shadow-sm hover:shadow-md transition"
|
||||
>
|
||||
<SurveyedPieChart
|
||||
deals={currentDeals}
|
||||
onOpenTable={handleOpenTable}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,11 @@ export default function SurveyedPieChart({
|
|||
|
||||
const [hovered, setHovered] = useState<string | null>(null);
|
||||
|
||||
// Don't show the chart if there's no data
|
||||
if (data.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="flex flex-col items-center p-6 pt-10 pb-8 bg-white">
|
||||
{/* Header */}
|
||||
|
|
|
|||
|
|
@ -67,3 +67,5 @@ export default async function LiveReportingPage(props: {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue