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 b5a0e012..98913b92 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,138 +1,16 @@ "use client"; -import { m } from "framer-motion"; -import React, { useState } from "react"; +import React, { useRef, useState } from "react"; +import { + Table, + TableHeader, + TableBody, + TableRow, + TableHead, + TableCell, +} from "@/app/shadcn_components/ui/table"; +import { ASPECT_TYPE_LABELS, ELEMENT_TYPE_LABELS } from "../constants"; -export const ELEMENT_TYPE_LABELS: Record = { - access_ramp: "Access Ramp", - accessible_housing_register: "Accessible Housing Register", - additional_wc_or_whb: "Additional WC or WHB", - asbestos: "Asbestos", - bathroom: "Bathroom", - bathroom_remaining_life_source: "Bathroom Remaining Life Source", - boiler_fuel: "Boiler Fuel", - central_heating: "Central Heating", - chimney: "Chimney", - community_heating: "Community Heating", - downpipes: "Downpipes", - drop_kerb: "Drop Kerb", - electrics: "Electrics", - external_brickwork_pointing: "External Brickwork Pointing", - external_decoration: "External Decoration", - external_door: "External Door", - external_noise_insulation: "External Noise Insulation", - external_structural_defects: "External Structural Defects", - external_wall: "External Wall", - external_windows: "External Windows", - fascia_soffit_bargeboards: "Fascia Soffit Bargeboards", - ffhh_damp: "FFHH Damp", - ffhh_drainage_lavatories: "FFHH Drainage Lavatories", - ffhh_food_prep_and_washup: "FFHH Food Prep and Washup", - ffhh_hold_and_cold_water: "FFHH Hold and Cold Water", - ffhh_natural_light: "FFHH Natural Light", - ffhh_neglected: "FFHH Neglected", - ffhh_unsafe_layout: "FFHH Unsafe Layout", - ffhh_unstable_building: "FFHH Unstable Building", - ffhh_ventilation: "FFHH Ventilation", - fire_risk_assessment: "Fire Risk Assessment", - floor_level: "Floor Level", - floor_level_front_door: "Floor Level Front Door", - garage_and_store_door: "Garage and Store Door", - garage_and_store_roof: "Garage and Store Roof", - garage_and_store_windows: "Garage and Store Windows", - garage_door: "Garage Door", - garage_roof: "Garage Roof", - garage_windows: "Garage Windows", - gas_available: "Gas Available", - gutters: "Gutters", - heat_recovery_units: "Heat Recovery Units", - heating_system: "Heating System", - hhsrs_amenities: "HHSRS Amenities", - hhsrs_asbestos_and_mmf: "HHSRS Asbestos and MMF", - hhsrs_biocides: "HHSRS Biocides", - hhsrs_carbon_monoxide: "HHSRS Carbon Monoxide", - hhsrs_collision_and_entrapment: "HHSRS Collision and Entrapment", - hhsrs_crowding_and_space: "HHSRS Crowding and Space", - hhsrs_damp_and_mould: "HHSRS Damp and Mould", - hhsrs_domestic_hygiene_pests_refuse: "HHSRS Domestic Hygiene, Pests & Refuse", - hhsrs_electrical_hazards: "HHSRS Electrical Hazards", - hhsrs_entry_by_intruders: "HHSRS Entry by Intruders", - hhsrs_excess_cold: "HHSRS Excess Cold", - hhsrs_excess_heat: "HHSRS Excess Heat", - hhsrs_explosions: "HHSRS Explosions", - hhsrs_falls_associated_with_baths: "HHSRS Falls Associated with Baths", - hhsrs_falls_between_levels: "HHSRS Falls Between Levels", - hhsrs_falls_on_level_surfaces: "HHSRS Falls on Level Surfaces", - hhsrs_falls_on_stairs: "HHSRS Falls on Stairs", - hhsrs_fire: "HHSRS Fire", - hhsrs_flames_hot_surfaces: "HHSRS Flames / Hot Surfaces", - hhsrs_food_safety: "HHSRS Food Safety", - hhsrs_lead: "HHSRS Lead", - hhsrs_lighting: "HHSRS Lighting", - hhsrs_noise: "HHSRS Noise", - hhsrs_personal_hygiene_sanitation: "HHSRS Personal Hygiene & Sanitation", - hhsrs_radiation: "HHSRS Radiation", - hhsrs_structural_collapse: "HHSRS Structural Collapse", - hhsrs_uncombusted_fuel_gas: "HHSRS Uncombusted Fuel Gas", - hhsrs_volatile_organic_compounds: "HHSRS Volatile Organic Compounds", - hhsrs_water_supply: "HHSRS Water Supply", - internal_downpipes_external_area: "Internal Downpipes External Area", - kitchen: "Kitchen", - kitchen_remaining_life_source: "Kitchen Remaining Life Source", - lintel: "Lintel", - loft_insulation: "Loft Insulation", - outbuilding_overhaul: "Outbuilding Overhaul", - parking_areas: "Parking Areas", - paths_and_hardstandings: "Paths and Hardstandings", - patio_french_door: "Patio French Door", - porch_canopy: "Porch Canopy", - private_balcony: "Private Balcony", - programmable_heating: "Programmable Heating", - property: "Property", - quality_standard: "Quality Standard", - roof: "Roof", - smoke_detection: "Smoke Detection", - steps_to_front_door: "Steps to Front Door", - store_door: "Store Door", - store_roof: "Store Roof", - store_windows: "Store Windows", - tenant_installed_kitchen: "Tenant Installed Kitchen", - water_heating: "Water Heating", -}; - -export const ASPECT_TYPE_LABELS: Record = { - material: "Material", - condition: "Condition", - type: "Type", - area: "Area", - configuration: "Configuration", - presence: "Presence", - risk: "Risk", - severity: "Severity", - location: "Location", - finish: "Finish", - insulation: "Insulation", - pointing: "Pointing", - spalling: "Spalling", - lintels: "Lintels", - cladding: "Cladding", - category: "Category", - quantity: "Quantity", - adequacy: "Adequacy", - rating: "Rating", - strategy: "Strategy", - extent: "Extent", - distribution: "Distribution", - structure: "Structure", - covering: "Covering", - fire_rating: "Fire Rating", - external_decoration: "External Decoration", - work_required: "Work Required", - age_band: "Age Band", - construction_type: "Construction Type", - classification: "Classification", - system: "System", -} export type AspectCondition = { id: string; @@ -179,10 +57,12 @@ 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 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); @@ -192,7 +72,6 @@ export default function ElementTable({ groupedElements }: Props) { }; const selectAll = () => setSelectedTypes(new Set(uniqueElementTypes)); - const deselectAll = () => setSelectedTypes(new Set()); const filteredGroups = @@ -211,149 +90,144 @@ export default function ElementTable({ groupedElements }: Props) { 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 -
- - {showDropdown && ( -
+
+ + + + + Element + {/* Filter button stays in header */} + - - + ▼ + + + No. Aspects + + - {uniqueElementTypes - .sort((a, b) => { - // Sort alphabetically by human-readable label - const labelA = ELEMENT_TYPE_LABELS[a] ?? a.replaceAll("_", " "); - const labelB = ELEMENT_TYPE_LABELS[b] ?? b.replaceAll("_", " "); - return labelA.localeCompare(labelB); - }) - .map(type => ( - - ))} + + {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); - - - )} + + {`${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 ?? "-"} + + )) + )} + +
+ + + )} + + ); + })} + +
+ - - 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 ( - - toggleParent(parentKey)} - style={{ - backgroundColor: "#f7f7f7", - cursor: "pointer", - fontWeight: "bold", - }} - > - {/* - {group.elementType.replaceAll("_", " ") + (group.elementInstance > 1 ? " (" + group.elementInstance + ")" : "")} - */} - - {`${ELEMENT_TYPE_LABELS[group.elementType] ?? group.elementType.replaceAll("_", " ")}${group.elementInstance > 1 ? ` (${group.elementInstance})` : ""}`} - - {totalAspects} - - - {isExpanded && ( - - - - - - - - - - - - - - - {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 TypeValueQuantityInstall DateRenewal YearComments
- {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 => ( + + ))} + +
+ )} + ); -} +} \ No newline at end of file diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/constants.ts b/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/constants.ts new file mode 100644 index 00000000..18f9468e --- /dev/null +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/condition/constants.ts @@ -0,0 +1,130 @@ +export const ELEMENT_TYPE_LABELS: Record = { + access_ramp: "Access Ramp", + accessible_housing_register: "Accessible Housing Register", + additional_wc_or_whb: "Additional WC or WHB", + asbestos: "Asbestos", + bathroom: "Bathroom", + bathroom_remaining_life_source: "Bathroom Remaining Life Source", + boiler_fuel: "Boiler Fuel", + central_heating: "Central Heating", + chimney: "Chimney", + community_heating: "Community Heating", + downpipes: "Downpipes", + drop_kerb: "Drop Kerb", + electrics: "Electrics", + external_brickwork_pointing: "External Brickwork Pointing", + external_decoration: "External Decoration", + external_door: "External Door", + external_noise_insulation: "External Noise Insulation", + external_structural_defects: "External Structural Defects", + external_wall: "External Wall", + external_windows: "External Windows", + fascia_soffit_bargeboards: "Fascia Soffit Bargeboards", + ffhh_damp: "FFHH Damp", + ffhh_drainage_lavatories: "FFHH Drainage Lavatories", + ffhh_food_prep_and_washup: "FFHH Food Prep and Washup", + ffhh_hold_and_cold_water: "FFHH Hold and Cold Water", + ffhh_natural_light: "FFHH Natural Light", + ffhh_neglected: "FFHH Neglected", + ffhh_unsafe_layout: "FFHH Unsafe Layout", + ffhh_unstable_building: "FFHH Unstable Building", + ffhh_ventilation: "FFHH Ventilation", + fire_risk_assessment: "Fire Risk Assessment", + floor_level: "Floor Level", + floor_level_front_door: "Floor Level Front Door", + garage_and_store_door: "Garage and Store Door", + garage_and_store_roof: "Garage and Store Roof", + garage_and_store_windows: "Garage and Store Windows", + garage_door: "Garage Door", + garage_roof: "Garage Roof", + garage_windows: "Garage Windows", + gas_available: "Gas Available", + gutters: "Gutters", + heat_recovery_units: "Heat Recovery Units", + heating_system: "Heating System", + hhsrs_amenities: "HHSRS Amenities", + hhsrs_asbestos_and_mmf: "HHSRS Asbestos and MMF", + hhsrs_biocides: "HHSRS Biocides", + hhsrs_carbon_monoxide: "HHSRS Carbon Monoxide", + hhsrs_collision_and_entrapment: "HHSRS Collision and Entrapment", + hhsrs_crowding_and_space: "HHSRS Crowding and Space", + hhsrs_damp_and_mould: "HHSRS Damp and Mould", + hhsrs_domestic_hygiene_pests_refuse: "HHSRS Domestic Hygiene, Pests & Refuse", + hhsrs_electrical_hazards: "HHSRS Electrical Hazards", + hhsrs_entry_by_intruders: "HHSRS Entry by Intruders", + hhsrs_excess_cold: "HHSRS Excess Cold", + hhsrs_excess_heat: "HHSRS Excess Heat", + hhsrs_explosions: "HHSRS Explosions", + hhsrs_falls_associated_with_baths: "HHSRS Falls Associated with Baths", + hhsrs_falls_between_levels: "HHSRS Falls Between Levels", + hhsrs_falls_on_level_surfaces: "HHSRS Falls on Level Surfaces", + hhsrs_falls_on_stairs: "HHSRS Falls on Stairs", + hhsrs_fire: "HHSRS Fire", + hhsrs_flames_hot_surfaces: "HHSRS Flames / Hot Surfaces", + hhsrs_food_safety: "HHSRS Food Safety", + hhsrs_lead: "HHSRS Lead", + hhsrs_lighting: "HHSRS Lighting", + hhsrs_noise: "HHSRS Noise", + hhsrs_personal_hygiene_sanitation: "HHSRS Personal Hygiene & Sanitation", + hhsrs_radiation: "HHSRS Radiation", + hhsrs_structural_collapse: "HHSRS Structural Collapse", + hhsrs_uncombusted_fuel_gas: "HHSRS Uncombusted Fuel Gas", + hhsrs_volatile_organic_compounds: "HHSRS Volatile Organic Compounds", + hhsrs_water_supply: "HHSRS Water Supply", + internal_downpipes_external_area: "Internal Downpipes External Area", + kitchen: "Kitchen", + kitchen_remaining_life_source: "Kitchen Remaining Life Source", + lintel: "Lintel", + loft_insulation: "Loft Insulation", + outbuilding_overhaul: "Outbuilding Overhaul", + parking_areas: "Parking Areas", + paths_and_hardstandings: "Paths and Hardstandings", + patio_french_door: "Patio French Door", + porch_canopy: "Porch Canopy", + private_balcony: "Private Balcony", + programmable_heating: "Programmable Heating", + property: "Property", + quality_standard: "Quality Standard", + roof: "Roof", + smoke_detection: "Smoke Detection", + steps_to_front_door: "Steps to Front Door", + store_door: "Store Door", + store_roof: "Store Roof", + store_windows: "Store Windows", + tenant_installed_kitchen: "Tenant Installed Kitchen", + water_heating: "Water Heating", +}; + +export const ASPECT_TYPE_LABELS: Record = { + material: "Material", + condition: "Condition", + type: "Type", + area: "Area", + configuration: "Configuration", + presence: "Presence", + risk: "Risk", + severity: "Severity", + location: "Location", + finish: "Finish", + insulation: "Insulation", + pointing: "Pointing", + spalling: "Spalling", + lintels: "Lintels", + cladding: "Cladding", + category: "Category", + quantity: "Quantity", + adequacy: "Adequacy", + rating: "Rating", + strategy: "Strategy", + extent: "Extent", + distribution: "Distribution", + structure: "Structure", + covering: "Covering", + fire_rating: "Fire Rating", + external_decoration: "External Decoration", + work_required: "Work Required", + age_band: "Age Band", + construction_type: "Construction Type", + classification: "Classification", + system: "System", +}