mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
Add simple renewals timeline
This commit is contained in:
parent
fb7adc3998
commit
244053bea8
3 changed files with 119 additions and 3 deletions
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
id: string; // unique for timeline
|
||||
elementId: string;
|
||||
aspectId: string;
|
||||
label: string; // what user sees
|
||||
renewalYear: number;
|
||||
elementType: string;
|
||||
aspectType: string;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue