Add simple renewals timeline

This commit is contained in:
Daniel Roth 2026-02-02 16:17:00 +00:00
parent fb7adc3998
commit 244053bea8
3 changed files with 119 additions and 3 deletions

View file

@ -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 (
<div style={{ width: "100%", height: 400 }}>
<ResponsiveContainer>
<ScatterChart
margin={{ top: 20, right: 40, bottom: 20, left: 200 }}
>
<CartesianGrid />
<XAxis
type="number"
dataKey="x"
domain={["dataMin - 1", "dataMax + 1"]}
allowDecimals={false}
tickFormatter={tick => tick.toString()}
/>
<YAxis
type="number"
dataKey="y"
width={200}
ticks={elementTypes.map((_, i) => i)}
tickFormatter={index => elementTypes[index]}
interval={0} // force all element labels to render
/>
<Tooltip
cursor={{ strokeDasharray: "3 3" }}
content={({ active, payload }) => {
if (active && payload && payload.length) {
const data = payload[0].payload;
return (
<div style={{
background: "white",
padding: "8px",
border: "1px solid #ccc",
borderRadius: "4px",
fontSize: "0.875rem"
}}>
<div><strong>{data.elementType}</strong></div>
<div>Renewal year: {data.x}</div>
</div>
);
}
return null;
}}
/>
<ReferenceLine x={today} stroke="#000" strokeDasharray="3 3">
<Label value="Today" position="top" />
</ReferenceLine>
<Scatter
data={data}
shape={(props: any) => {
const { cx, cy, payload } = props;
return (
<circle
cx={cx}
cy={cy}
r={6}
fill={payload.color}
onClick={() => handleClick(payload)}
style={{ cursor: "pointer" }}
/>
);
}}
/>
</ScatterChart>
</ResponsiveContainer>
</div>
);
}

View file

@ -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) {
<ElementTable groupedElements={groupedElements} />
</TabsContent>
<TabsContent value="renewals">
<div>Coming soon...</div>
<RenewalsTimeline renewals={buildRenewals(survey.elements)} />
</TabsContent>
<TabsContent value="decent_homes">
<div>Coming soon...</div>

View file

@ -2,7 +2,6 @@
id: string; // unique for timeline
elementId: string;
aspectId: string;
label: string; // what user sees
renewalYear: number;
elementType: string;
aspectType: string;