From 244053bea89019025e6128b852c1c240e8084fb6 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 2 Feb 2026 16:17:00 +0000 Subject: [PATCH] Add simple renewals timeline --- .../condition/components/RenewalsTimeline.tsx | 117 ++++++++++++++++++ .../[propertyId]/condition/page.tsx | 4 +- .../condition/viewModels/renewal.ts | 1 - 3 files changed, 119 insertions(+), 3 deletions(-) diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/components/RenewalsTimeline.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/components/RenewalsTimeline.tsx index e69de29b..9a9e1c28 100644 --- a/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/components/RenewalsTimeline.tsx +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/components/RenewalsTimeline.tsx @@ -0,0 +1,117 @@ +"use client"; + +import React from "react"; +import { + ScatterChart, + Scatter, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + ReferenceLine, + Label, +} from "recharts"; +import { Renewal } from "../viewModels/renewal"; + +type RenewalsTimelineProps = { + renewals: Renewal[]; +}; + +export default function RenewalsTimeline({ renewals }: RenewalsTimelineProps) { + const today = new Date().getFullYear(); + + const elementTypes = Array.from( + new Set(renewals.map(r => (r.elementType ?? "").trim())) + ); + const elementTypeToY = Object.fromEntries(elementTypes.map((type, i) => [type, i])); + + const data = renewals.map(r => { + let color = "#10b981"; + if (r.renewalYear < today) color = "#ef4444"; + else if (r.renewalYear - today <= 3) color = "#f59e0b"; + + return { + id: r.id, + elementType: r.elementType, + x: r.renewalYear, + y: elementTypeToY[r.elementType ?? ""], + comments: r.comments ?? "", + color, + }; + }); + + // 3️⃣ Click handler + const handleClick = (payload: any) => { + if (!payload) return; + alert(`Element Type: ${payload.elementType}\nYear: ${payload.x}\nComments: ${payload.comments}`); + }; + + return ( +
+ + + + tick.toString()} + /> + i)} + tickFormatter={index => elementTypes[index]} + interval={0} // force all element labels to render + /> + { + if (active && payload && payload.length) { + const data = payload[0].payload; + return ( +
+
{data.elementType}
+
Renewal year: {data.x}
+
+ ); + } + return null; + }} + /> + + + + { + const { cx, cy, payload } = props; + return ( + handleClick(payload)} + style={{ cursor: "pointer" }} + /> + ); + }} + /> +
+
+
+ ); +} diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/page.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/page.tsx index 87059775..ed66cb5e 100644 --- a/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/page.tsx +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/page.tsx @@ -5,6 +5,7 @@ import { Survey } from "./types/survey"; import { Renewal } from "./viewModels/renewal"; import ElementTable, { ElementGroup } from "./components/ElementTable"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/app/shadcn_components/ui/tabs"; +import RenewalsTimeline from "./components/RenewalsTimeline"; type ConditionPageProps = { params: { @@ -50,7 +51,6 @@ function buildRenewals(elements: Element[]): Renewal[] { renewalYear: aspect.renewalYear!, elementType: element.elementType, aspectType: aspect.aspectType, - label: `${element.elementType} ${element.elementInstance} – ${aspect.aspectType}`, comments: aspect.comments, })) ); @@ -101,7 +101,7 @@ export default async function Condition({ params }: ConditionPageProps) { -
Coming soon...
+
Coming soon...
diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/viewModels/renewal.ts b/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/viewModels/renewal.ts index 1ea24cfb..71d8371b 100644 --- a/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/viewModels/renewal.ts +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/viewModels/renewal.ts @@ -2,7 +2,6 @@ id: string; // unique for timeline elementId: string; aspectId: string; - label: string; // what user sees renewalYear: number; elementType: string; aspectType: string;