From 62e9c548f15eb62eb7f38933a1115e1a8993bc41 Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Thu, 28 May 2026 14:58:23 +0000
Subject: [PATCH 1/2] Surface coordinator damp & mould commentary in the risk
drill-down
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The risk drill-down's coordinator-stage table showed "Yes" for every row,
which carried no useful signal. It also missed properties where the
coordinator wrote a comment without setting the growth flag.
Include rows where dampmould_growth is "yes" (case-insensitive) OR the
comment is populated, and render the comment in the cell — truncated
with a popover for the full text, or a "no note from coordinator"
placeholder when the row is here only because the flag was ticked.
Also drop the typo in the schema property name
(damnp -> damp); SQL column unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
package-lock.json | 56 +++++++++++++++++++
package.json | 1 +
src/app/db/schema/crm/hubspot_deal_table.ts | 2 +-
.../your-projects/live/DampMouldRiskPanel.tsx | 2 -
.../your-projects/live/DrillDownTable.tsx | 51 ++++++++++++++++-
.../your-projects/live/[dealId]/page.test.ts | 2 +-
.../your-projects/live/dealQuery.ts | 2 +-
.../your-projects/live/transforms.test.ts | 32 +++++++++++
.../your-projects/live/transforms.ts | 10 +++-
src/app/shadcn_components/ui/popover.tsx | 31 ++++++++++
10 files changed, 181 insertions(+), 8 deletions(-)
create mode 100644 src/app/shadcn_components/ui/popover.tsx
diff --git a/package-lock.json b/package-lock.json
index ef282c48..7bc83da5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,6 +25,7 @@
"@radix-ui/react-hover-card": "^1.0.6",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.1.3",
+ "@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.0.3",
@@ -3812,6 +3813,61 @@
}
}
},
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
+ "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-popper": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
diff --git a/package.json b/package.json
index 30b73740..eae3d631 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
"@radix-ui/react-hover-card": "^1.0.6",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.1.3",
+ "@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.0.3",
diff --git a/src/app/db/schema/crm/hubspot_deal_table.ts b/src/app/db/schema/crm/hubspot_deal_table.ts
index 1a651107..3d2e97c5 100644
--- a/src/app/db/schema/crm/hubspot_deal_table.ts
+++ b/src/app/db/schema/crm/hubspot_deal_table.ts
@@ -47,7 +47,7 @@ export const hubspotDealData = pgTable("hubspot_deal_data", {
expectedCommencementDate: timestamp("expected_commencement_date", { precision: 6, withTimezone: true }),
coordination_comments: text("coordination_comments"),
surveyor: text("surveyor"),
- damnpMouldAndRepairComments: text("damp_mould_and_repairs_comments"),
+ dampMouldAndRepairComments: text("damp_mould_and_repairs_comments"),
batch: text("batch"),
batchDescription: text("batch_description"),
blockReference: text("block_reference"),
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DampMouldRiskPanel.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DampMouldRiskPanel.tsx
index ffebcd4c..816de4b6 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DampMouldRiskPanel.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DampMouldRiskPanel.tsx
@@ -120,7 +120,6 @@ export default function DampMouldRiskPanel({
const coordColumns: (keyof ClassifiedDeal)[] = [
"dealname",
"landlordPropertyId",
- "dampMouldFlag",
"dampMouldAndRepairComments",
"coordinator",
];
@@ -128,7 +127,6 @@ export default function DampMouldRiskPanel({
const coordLabels: Partial> = {
dealname: "Address",
landlordPropertyId: "Property Ref",
- dampMouldFlag: "Coordinator Flag",
dampMouldAndRepairComments: "Comments",
coordinator: "Coordinator",
};
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DrillDownTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DrillDownTable.tsx
index 7adf28e6..25dc5f84 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DrillDownTable.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DrillDownTable.tsx
@@ -22,9 +22,55 @@ import {
TableRow,
} from "@/app/shadcn_components/ui/table";
import { Input } from "@/app/shadcn_components/ui/input";
-import { Search, Download, ChevronLeft, ChevronRight } from "lucide-react";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/app/shadcn_components/ui/popover";
+import { Search, Download, ChevronLeft, ChevronRight, ChevronDown } from "lucide-react";
import type { ClassifiedDeal, HubspotDeal } from "./types";
+const NO_COMMENT_PLACEHOLDER =
+ "Damp & mould discovered — no note from coordinator";
+const COMMENT_PREVIEW_LIMIT = 60;
+
+function DampMouldCommentCell({ value }: { value: unknown }) {
+ const comment = typeof value === "string" ? value.trim() : "";
+
+ if (!comment) {
+ return (
+
+ {NO_COMMENT_PLACEHOLDER}
+
+ );
+ }
+
+ const preview =
+ comment.length > COMMENT_PREVIEW_LIMIT
+ ? comment.slice(0, COMMENT_PREVIEW_LIMIT).trimEnd() + "…"
+ : comment;
+
+ return (
+
+
+
+
+
+ {comment}
+
+
+ );
+}
+
interface DrillDownTableProps {
data: ClassifiedDeal[];
columns?: (keyof HubspotDeal)[];
@@ -127,6 +173,9 @@ export default function DrillDownTable({
if (key === "majorConditionIssuePhotosS3") {
return ;
}
+ if (key === "dampMouldAndRepairComments") {
+ return ;
+ }
return (
{value != null ? String(value) : (
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/[dealId]/page.test.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/[dealId]/page.test.ts
index f4fa001c..0b2f2d78 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/[dealId]/page.test.ts
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/[dealId]/page.test.ts
@@ -165,7 +165,7 @@ const mockDealRow = {
pashubLink: null,
sharepointLink: null,
dampmouldGrowth: null,
- damnpMouldAndRepairComments: null,
+ dampMouldAndRepairComments: null,
preSap: null,
mtpCompletionDate: null,
mtpReModelCompletionDate: null,
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/dealQuery.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/dealQuery.ts
index bf34ff99..8f659192 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/dealQuery.ts
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/dealQuery.ts
@@ -34,7 +34,7 @@ export function mapDbRowToHubspotDeal(row: DealRow): HubspotDeal {
pashubLink: d.pashubLink,
sharepointLink: d.sharepointLink,
dampMouldFlag: d.dampmouldGrowth,
- dampMouldAndRepairComments: d.damnpMouldAndRepairComments,
+ dampMouldAndRepairComments: d.dampMouldAndRepairComments,
preSapScore: d.preSap,
coordinator: row.coordinator,
ioeV1Date: d.mtpCompletionDate,
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/transforms.test.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/transforms.test.ts
index f8df537b..9145af89 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/transforms.test.ts
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/transforms.test.ts
@@ -305,6 +305,38 @@ describe("computeDampMouldRisk", () => {
expect(result.coordinatorFlagCount).toBe(2);
});
+ it("ignores a growth flag of 'No' when there is no comment", () => {
+ const deals = [
+ makeClassified({ dampMouldFlag: "No", dampMouldAndRepairComments: null }),
+ makeClassified({ dampMouldFlag: "no", dampMouldAndRepairComments: " " }),
+ ];
+ const result = computeDampMouldRisk(deals);
+ expect(result.coordinatorFlagCount).toBe(0);
+ });
+
+ it("treats a 'yes' growth flag case-insensitively, ignoring whitespace", () => {
+ const deals = [
+ makeClassified({ dampMouldFlag: "yes" }),
+ makeClassified({ dampMouldFlag: " Yes " }),
+ makeClassified({ dampMouldFlag: "YES" }),
+ ];
+ const result = computeDampMouldRisk(deals);
+ expect(result.coordinatorFlagCount).toBe(3);
+ });
+
+ it("counts a deal with a comment but no growth flag as coordinator-flagged", () => {
+ const deals = [
+ makeClassified({
+ dampMouldFlag: null,
+ dampMouldAndRepairComments: "Mould in NE bedroom corner",
+ }),
+ makeClassified({ dampMouldFlag: null, dampMouldAndRepairComments: null }),
+ ];
+ const result = computeDampMouldRisk(deals);
+ expect(result.coordinatorFlagCount).toBe(1);
+ expect(result.coordinatorFlagDeals).toHaveLength(1);
+ });
+
it("counts deals flagged at both stages independently", () => {
const deals = [
makeClassified({ majorConditionIssuePhotosS3: "s3://x", dampMouldFlag: "Yes" }),
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/transforms.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/transforms.ts
index 0223e76a..5d788391 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/transforms.ts
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/transforms.ts
@@ -145,10 +145,16 @@ export function classifyDeals(deals: HubspotDeal[]): ClassifiedDeal[] {
// -----------------------------------------------------------------------
// Compute damp & mould risk — survey vs coordination stage comparison
// -----------------------------------------------------------------------
+function isCoordinatorFlagged(d: ClassifiedDeal): boolean {
+ const growthIsYes = d.dampMouldFlag?.trim().toLowerCase() === "yes";
+ const hasComment = !!d.dampMouldAndRepairComments?.trim();
+ return growthIsYes || hasComment;
+}
+
export function computeDampMouldRisk(deals: ClassifiedDeal[]): DampMouldRiskData {
const surveyFlagDeals = deals.filter((d) => !!d.majorConditionIssuePhotosS3);
- const coordinatorFlagDeals = deals.filter((d) => !!d.dampMouldFlag);
- const bothFlaggedCount = surveyFlagDeals.filter((d) => !!d.dampMouldFlag).length;
+ const coordinatorFlagDeals = deals.filter(isCoordinatorFlagged);
+ const bothFlaggedCount = surveyFlagDeals.filter(isCoordinatorFlagged).length;
return {
surveyFlagCount: surveyFlagDeals.length,
diff --git a/src/app/shadcn_components/ui/popover.tsx b/src/app/shadcn_components/ui/popover.tsx
new file mode 100644
index 00000000..3a991084
--- /dev/null
+++ b/src/app/shadcn_components/ui/popover.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+const Popover = PopoverPrimitive.Root
+
+const PopoverTrigger = PopoverPrimitive.Trigger
+
+const PopoverContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+PopoverContent.displayName = PopoverPrimitive.Content.displayName
+
+export { Popover, PopoverTrigger, PopoverContent }
From 9e3df59ea04eb01e23400b4d47cb9c413139cbaa Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Thu, 28 May 2026 15:33:08 +0000
Subject: [PATCH 2/2] Broaden the Awaab's Law panel to cover other condition
issues
Coordinators record non-damp/mould observations (e.g. wasp nests) in the
same comments field, but the section was framed entirely around damp and
mould. Reframe the panel copy and table titles around "condition issues",
keep "Damp, Mould" up front so the Awaab's Law urgency still leads, and
mark the damp/mould rows specifically with a red badge column so they
don't blend into the broader list.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
.../your-projects/live/DampMouldRiskPanel.tsx | 14 +++++++++-----
.../your-projects/live/DrillDownTable.tsx | 16 ++++++++++++++++
2 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DampMouldRiskPanel.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DampMouldRiskPanel.tsx
index 816de4b6..e3667c2d 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DampMouldRiskPanel.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DampMouldRiskPanel.tsx
@@ -106,6 +106,7 @@ export default function DampMouldRiskPanel({
const surveyColumns: (keyof ClassifiedDeal)[] = [
"dealname",
"landlordPropertyId",
+ "dampMouldFlag",
"majorConditionIssueDescription",
"majorConditionIssuePhotosS3",
];
@@ -113,6 +114,7 @@ export default function DampMouldRiskPanel({
const surveyLabels: Partial> = {
dealname: "Address",
landlordPropertyId: "Property Ref",
+ dampMouldFlag: "Damp & Mould",
majorConditionIssueDescription: "Surveyor Notes",
majorConditionIssuePhotosS3: "Photo Evidence",
};
@@ -120,6 +122,7 @@ export default function DampMouldRiskPanel({
const coordColumns: (keyof ClassifiedDeal)[] = [
"dealname",
"landlordPropertyId",
+ "dampMouldFlag",
"dampMouldAndRepairComments",
"coordinator",
];
@@ -127,6 +130,7 @@ export default function DampMouldRiskPanel({
const coordLabels: Partial> = {
dealname: "Address",
landlordPropertyId: "Property Ref",
+ dampMouldFlag: "Damp & Mould",
dampMouldAndRepairComments: "Comments",
coordinator: "Coordinator",
};
@@ -145,7 +149,7 @@ export default function DampMouldRiskPanel({
- Awaab's Law — Damp & Mould Risk
+ Awaab's Law — Damp, Mould & Other Condition Issues
Comparison of flags raised at survey vs coordination stage
@@ -159,7 +163,7 @@ export default function DampMouldRiskPanel({
- No damp or mould flags recorded for this project.
+ No condition issues recorded for this project.
) : (
@@ -174,7 +178,7 @@ export default function DampMouldRiskPanel({
color="red"
onClick={() =>
onOpenTable(
- "Damp & Mould — Survey Stage Flags",
+ "Condition Issues — Survey Stage",
risk.surveyFlagDeals,
surveyColumns,
surveyLabels
@@ -190,7 +194,7 @@ export default function DampMouldRiskPanel({
color="red"
onClick={() =>
onOpenTable(
- "Damp & Mould — Coordination Stage Flags",
+ "Condition Issues — Coordination Stage",
risk.coordinatorFlagDeals,
coordColumns,
coordLabels
@@ -208,7 +212,7 @@ export default function DampMouldRiskPanel({
{risk.coordinatorFlagCount - risk.surveyFlagCount} additional{" "}
{risk.coordinatorFlagCount - risk.surveyFlagCount === 1 ? "property was" : "properties were"}{" "}
- flagged for damp & mould at the coordination stage that{" "}
+ flagged with condition issues at the coordination stage that{" "}
{risk.coordinatorFlagCount - risk.surveyFlagCount === 1 ? "was" : "were"} not
identified during the initial survey.
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DrillDownTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DrillDownTable.tsx
index 25dc5f84..c5b314d0 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DrillDownTable.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DrillDownTable.tsx
@@ -34,6 +34,19 @@ const NO_COMMENT_PLACEHOLDER =
"Damp & mould discovered — no note from coordinator";
const COMMENT_PREVIEW_LIMIT = 60;
+function DampMouldBadgeCell({ value }: { value: unknown }) {
+ const isYes =
+ typeof value === "string" && value.trim().toLowerCase() === "yes";
+
+ if (!isYes) return null;
+
+ return (
+
+ Damp & Mould
+
+ );
+}
+
function DampMouldCommentCell({ value }: { value: unknown }) {
const comment = typeof value === "string" ? value.trim() : "";
@@ -176,6 +189,9 @@ export default function DrillDownTable({
if (key === "dampMouldAndRepairComments") {
return ;
}
+ if (key === "dampMouldFlag") {
+ return ;
+ }
return (
{value != null ? String(value) : (