From 3e102ae6ea6d17c9f162852e1af34075e8c0234d Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 3 Feb 2026 09:59:22 +0000 Subject: [PATCH] tidy up renewals chart, allow filtering out of --- .../condition/components/ElementTable.tsx | 231 ------------------ .../condition/components/RenewalsTimeline.tsx | 112 +++++---- 2 files changed, 65 insertions(+), 278 deletions(-) diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/components/ElementTable.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/components/ElementTable.tsx index c87b96b9..9cc2a969 100644 --- a/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/components/ElementTable.tsx +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/components/ElementTable.tsx @@ -1,218 +1,3 @@ -// "use client"; - -// import React, { useRef, useState } from "react"; -// import { -// Table, -// TableHeader, -// TableBody, -// TableRow, -// TableHead, -// TableCell, -// } from "@/app/shadcn_components/ui/table"; -// import { Element } from "../types/element"; -// import { ASPECT_TYPE_LABELS, ELEMENT_TYPE_LABELS } from "../constants"; - - -// export type ElementGroup = { -// elementType: string; -// elementInstance: number; -// elements: Element[]; -// }; - -// const thStyle: React.CSSProperties = { -// borderBottom: "1px solid #ccc", -// textAlign: "left", -// padding: "4px", -// }; - -// const tdStyle: React.CSSProperties = { -// borderBottom: "1px solid #eee", -// padding: "4px", -// }; - -// type Props = { -// groupedElements: ElementGroup[]; -// }; - -// export default function ElementTable({ groupedElements }: Props) { -// const [selectedTypes, setSelectedTypes] = useState>(new Set()); -// const [expandedParents, setExpandedParents] = useState>(new Set()); -// const [showDropdown, setShowDropdown] = useState(false); -// const [dropdownPos, setDropdownPos] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); -// const buttonRef = useRef(null); - -// const uniqueElementTypes = Array.from(new Set(groupedElements.map(g => g.elementType))).sort((a, b) => -// a.localeCompare(b) -// ); - -// const toggleTypeFilter = (type: string) => { -// const newSet = new Set(selectedTypes); -// if (newSet.has(type)) newSet.delete(type); -// else newSet.add(type); -// setSelectedTypes(newSet); -// }; - -// const selectAll = () => setSelectedTypes(new Set(uniqueElementTypes)); -// const deselectAll = () => setSelectedTypes(new Set()); - -// const filteredGroups = -// selectedTypes.size === 0 -// ? groupedElements -// : groupedElements.filter(g => selectedTypes.has(g.elementType)); - -// const sortedGroups = [...filteredGroups].sort((a, b) => -// a.elementType.localeCompare(b.elementType) -// ); - -// const toggleParent = (key: string) => { -// const newSet = new Set(expandedParents); -// if (newSet.has(key)) newSet.delete(key); -// else newSet.add(key); -// setExpandedParents(newSet); -// }; - -// // Handle dropdown toggle and position -// const handleDropdownToggle = (e: React.MouseEvent) => { -// const rect = e.currentTarget.getBoundingClientRect(); -// const parentRect = (e.currentTarget.offsetParent as HTMLElement)?.getBoundingClientRect(); -// if (parentRect) { -// setDropdownPos({ -// top: rect.bottom - parentRect.top, -// left: rect.left - parentRect.left, -// }); -// } -// setShowDropdown(prev => !prev); -// }; - -// return ( -//
-//
-// -// -// -// -// Element -// {/* Filter button stays in header */} -// -// -// No. Aspects -// -// - -// -// {sortedGroups.map(group => { -// const parentKey = `${group.elementType}__${group.elementInstance}`; -// const isExpanded = expandedParents.has(parentKey); -// const totalAspects = group.elements.reduce((acc, el) => acc + el.aspects.length, 0); - -// return ( -// -// {/* Parent Row */} -// toggleParent(parentKey)} -// > -// -// {`${ELEMENT_TYPE_LABELS[group.elementType] ?? group.elementType.replaceAll("_", " ")}${group.elementInstance > 1 ? ` (${group.elementInstance})` : ""}`} -// -// {totalAspects} -// - -// {/* Child Table */} -// {isExpanded && ( -// -// -//
-// -// -// Aspect Type -// Value -// Quantity -// Install Date -// Renewal Year -// Comments -// -// -// -// {group.elements.flatMap(el => -// el.aspects -// .sort((a, b) => { -// const labelA = ASPECT_TYPE_LABELS[a.aspectType] ?? a.aspectType; -// const labelB = ASPECT_TYPE_LABELS[b.aspectType] ?? b.aspectType; -// return labelA.localeCompare(labelB); -// }) -// .map(aspect => ( -// -// {ASPECT_TYPE_LABELS[aspect.aspectType] ?? aspect.aspectType} -// {aspect.value ?? "-"} -// {aspect.quantity ?? "-"} -// {aspect.installDate ?? "-"} -// {aspect.renewalYear ?? "-"} -// {aspect.comments ?? "-"} -// -// )) -// )} -// -//
-// -// -// )} -// -// ); -// })} -// -// -//
- -// {/* Floating Dropdown */} -// {showDropdown && ( -//
-//
-// -// -//
-// {uniqueElementTypes -// .sort((a, b) => { -// const labelA = ELEMENT_TYPE_LABELS[a] ?? a.replaceAll("_", " "); -// const labelB = ELEMENT_TYPE_LABELS[b] ?? b.replaceAll("_", " "); -// return labelA.localeCompare(labelB); -// }) -// .map(type => ( -// -// ))} -// -//
-// )} -//
-// ); -// } - "use client"; import React, { useState, useEffect } from "react"; @@ -238,18 +23,12 @@ type Props = { }; export default function ElementTable({ groupedElements }: Props) { - // ----------------------- - // Hooks - // ----------------------- const [expandedRows, setExpandedRows] = useState>(new Set()); const [filterText, setFilterText] = useState(""); const [isMounted, setIsMounted] = useState(false); useEffect(() => setIsMounted(true), []); - // ----------------------- - // Columns - // ----------------------- const columns: ColumnDef[] = [ { accessorKey: "elementType", @@ -270,9 +49,6 @@ export default function ElementTable({ groupedElements }: Props) { }, ]; - // ----------------------- - // Table instance - // ----------------------- const table = useReactTable({ data: groupedElements, columns, @@ -298,9 +74,6 @@ export default function ElementTable({ groupedElements }: Props) { }); }; - // ----------------------- - // Prevent SSR hydration errors - // ----------------------- if (!isMounted) return null; return ( @@ -348,7 +121,6 @@ export default function ElementTable({ groupedElements }: Props) { ))} - {/* Child row / expanded table */} {isExpanded && ( @@ -365,9 +137,6 @@ export default function ElementTable({ groupedElements }: Props) { ); } -// ----------------------- -// Child table (ShadCN table) -// ----------------------- function ChildTable({ elements }: { elements: Element[] }) { return ( 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 a11ce20a..2384c42f 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 @@ -2,71 +2,89 @@ import React from "react"; import { ScatterChart } from "@tremor/react"; -// import { -// ScatterChart, -// Scatter, -// XAxis, -// YAxis, -// CartesianGrid, -// Tooltip, -// ResponsiveContainer, -// ReferenceLine, -// Label, -// } from "recharts"; import { Renewal } from "../viewModels/renewal"; -import { Tooltip } from "@/app/shadcn_components/ui/tooltip"; +import { ELEMENT_TYPE_LABELS } from "../constants"; type RenewalsTimelineProps = { renewals: Renewal[]; }; export default function RenewalsTimeline({ renewals }: RenewalsTimelineProps) { + const [showFuture, setShowFuture] = React.useState(false); + const today = new Date().getFullYear(); - - const renewalYears = renewals.map(r => r.renewalYear); - const elementIds = renewals.map(r => parseInt(r.elementId)); - - const minXValue = Math.min(...renewalYears); - const maxXValue = Math.max(...renewalYears); - const minYValue = Math.min(...elementIds); - const maxYValue = Math.max(...elementIds); - + const colouredData = renewals.map(r => ({ ...r, colourCategory: r.renewalYear < today - ? "past" - : r.renewalYear <= today + 3 - ? "soon" - : "future", + ? "past" + : r.renewalYear <= today + 3 + ? "soon" + : "future", + label: ELEMENT_TYPE_LABELS[r.elementType] ?? r.elementType.replaceAll("_", " "), })); - - const orderedData = colouredData.sort((a, b) => { + + const filteredData = showFuture + ? colouredData + : colouredData.filter(r => r.colourCategory !== "future"); + + const orderedData = filteredData.sort((a, b) => { const order: Record<'past' | 'soon' | 'future', number> = { past: 0, soon: 1, future: 2 }; return order[a.colourCategory as 'past' | 'soon' | 'future'] - order[b.colourCategory as 'past' | 'soon' | 'future']; }); + + const filteredRenewalYears = filteredData.map(r => r.renewalYear); + const filteredElementIds = filteredData.map(r => parseInt(r.elementId)); - const todayPosition = - ((today - (minXValue - 1)) / ((maxXValue + 1) - (minXValue - 1))) * 100; + const minXValue = Math.min(...filteredRenewalYears); + const maxXValue = Math.max(...filteredRenewalYears); + const minYValue = Math.min(...filteredElementIds); + const maxYValue = Math.max(...filteredElementIds); + + const customTooltip = ({ payload, active }: any) => { + if (!active || !payload?.length) return null; + + const data = payload[0].payload; + + return ( +
+
{data.renewalYear}
+
{data.label}
+
+ ); + }; return ( - +
+ + + +
); }; \ No newline at end of file