Web Developer Work - I
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2571 lines
91 KiB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
  1. // import React, { useState, useEffect, useRef } from "react";
  2. // import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
  3. // import { type AllWidgetProps } from 'jimu-core';
  4. // import "./simple_widget.css";
  5. // import { JimuMapViewComponent, type JimuMapView } from 'jimu-arcgis';
  6. // import { mapViewManager } from '../../../utils/mapview';
  7. // import Graphic from "@arcgis/core/Graphic";
  8. // import SimpleFillSymbol from "@arcgis/core/symbols/SimpleFillSymbol";
  9. // import SimpleLineSymbol from "@arcgis/core/symbols/SimpleLineSymbol";
  10. // import zoomInIcon from './assets/zoom-in.svg';
  11. // import jsPDF from "jspdf";
  12. // import autoTable from "jspdf-autotable";
  13. // const RoadAssetFilter = (props: AllWidgetProps<any>) => {
  14. // // State variables
  15. // const [selectedZone, setSelectedZone] = useState<string>("");
  16. // const [selectedWard, setSelectedWard] = useState<string>("");
  17. // const [zoneOptions, setZoneOptions] = useState<string[]>([]);
  18. // const [wardOptions, setWardOptions] = useState<string[]>([]);
  19. // const [jimuMapView, setJimuMapView] = useState<JimuMapView>();
  20. // const [selectedLayer, setSelectedLayer] = useState<string>("");
  21. // const [selectedUtilityLayer, setSelectedUtilityLayer] = useState<string>("All");
  22. // const [utilityCount, setUtilityCount] = useState<number | null>(null);
  23. // const [tableData, setTableData] = useState<any[]>([]);
  24. // const [utilityLayerOptions, setUtilityLayerOptions] = useState<string[]>([]);
  25. // const [selectedRoadName, setSelectedRoadName] = useState<string>("");
  26. // const [roadNameOptions, setRoadNameOptions] = useState<string[]>([]);
  27. // const [isLoading, setIsLoading] = useState(false);
  28. // const [activeStep, setActiveStep] = useState<number>(0);
  29. // const addedUtilityLayerRef = useRef<any | null>(null);
  30. // // Refs for cleanup
  31. // const highlightRef = useRef<any>(null);
  32. // const graphicsLayerRef = useRef<any>(null);
  33. // // URLs for layers
  34. // const ZONE_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/GCC_Administration_Boundaries/FeatureServer/3";
  35. // const WARD_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/GCC_Administration_Boundaries/FeatureServer/4";
  36. // const UTILITY_GROUP_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Utility/FeatureServer";
  37. // const ROAD_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Transporations/FeatureServer/0";
  38. // // Utility layer configuration
  39. // const utilityLayers = [
  40. // { id: 1, title: "CCTV" },
  41. // { id: 2, title: "Gantry Sign Boards" },
  42. // { id: 3, title: "Garbage Bins" },
  43. // { id: 4, title: "Hand Pump" },
  44. // { id: 7, title: "Manhole" },
  45. // { id: 8, title: "Permanent Bunkshop" },
  46. // { id: 11, title: "Police Booth" },
  47. // { id: 12, title: "Public Taps" },
  48. // { id: 17, title: "Street Name Board" },
  49. // { id: 19, title: "Traffic Signals" },
  50. // { id: 21, title: "Transformer" },
  51. // { id: 23, title: "Water Tanks" },
  52. // { id: 30, title: "Bus Terminus" },
  53. // { id: 33, title: "Footpath" },
  54. // { id: 34, title: "Median" },
  55. // { id: 36, title: "Parking Stretch" },
  56. // { id: 40, title: "Traffic Island" }
  57. // ];
  58. // // Utility to escape single quotes in SQL
  59. // const safe = (val: string) => val.replace(/'/g, "''");
  60. // // Populate Zone dropdown
  61. // const populateZone = async () => {
  62. // try {
  63. // const layer = new FeatureLayer({ url: ZONE_LAYER_URL });
  64. // const query = layer.createQuery();
  65. // query.returnDistinctValues = true;
  66. // query.returnGeometry = false;
  67. // query.outFields = ["zone"];
  68. // query.where = "1=1";
  69. // const result = await layer.queryFeatures(query);
  70. // if (result) {
  71. // const zones = Array.from(new Set(result.features.map(f => f.attributes.zone).filter(Boolean)));
  72. // zones.sort((a, b) => Number(a) - Number(b));
  73. // setZoneOptions(['All', ...zones]);
  74. // }
  75. // } catch (error) {
  76. // console.error("Error fetching zones:", error);
  77. // }
  78. // };
  79. // useEffect(() => {
  80. // populateZone();
  81. // }, []);
  82. // // Populate Ward dropdown based on selected Zone
  83. // const populateWard = async () => {
  84. // try {
  85. // const layer = new FeatureLayer({ url: WARD_LAYER_URL });
  86. // const query = layer.createQuery();
  87. // query.returnDistinctValues = true;
  88. // query.returnGeometry = false;
  89. // query.outFields = ["ward_no"];
  90. // if (selectedZone && selectedZone !== 'All') {
  91. // query.where = `zone_no = '${safe(selectedZone)}'`;
  92. // } else {
  93. // query.where = "1=1";
  94. // }
  95. // const result = await layer.queryFeatures(query);
  96. // if (result) {
  97. // const wards = Array.from(new Set(result.features.map(f => f.attributes.ward_no).filter(Boolean)));
  98. // wards.sort((a, b) => Number(a) - Number(b));
  99. // setWardOptions(['All', ...wards]);
  100. // setSelectedWard(""); // Reset ward when zone changes
  101. // setActiveStep(1); // Move to next step
  102. // }
  103. // } catch (error) {
  104. // console.error("Error fetching wards:", error);
  105. // }
  106. // };
  107. // useEffect(() => {
  108. // if (selectedZone) {
  109. // populateWard();
  110. // }
  111. // }, [selectedZone]);
  112. // // Populate utility layer options
  113. // useEffect(() => {
  114. // if (selectedLayer === "Utility") {
  115. // const titles = utilityLayers.map(l => l.title);
  116. // setUtilityLayerOptions(["All", ...titles]);
  117. // setSelectedUtilityLayer("All");
  118. // setActiveStep(2); // Move to next step
  119. // }
  120. // }, [selectedLayer]);
  121. // // Populate road names for selected utility layer
  122. // const populateRoadNames = async (utilityLayer: string) => {
  123. // if (!selectedZone || !selectedWard || !utilityLayer || utilityLayer === "All") return;
  124. // const layerInfo = utilityLayers.find(l => l.title === utilityLayer);
  125. // if (!layerInfo) return;
  126. // const layerUrl = `${UTILITY_GROUP_URL}/${layerInfo.id}`;
  127. // const layer = new FeatureLayer({ url: layerUrl });
  128. // try {
  129. // const query = layer.createQuery();
  130. // query.returnDistinctValues = true;
  131. // query.outFields = ["road_name"];
  132. // query.returnGeometry = false;
  133. // let whereClause = "";
  134. // if (selectedZone && selectedZone !== 'All') {
  135. // whereClause += `zone_number = '${safe(selectedZone)}'`;
  136. // }
  137. // if (selectedWard && selectedWard !== 'All') {
  138. // whereClause += whereClause ? ` AND ward_number = '${safe(selectedWard)}'` : `ward_number = '${safe(selectedWard)}'`;
  139. // }
  140. // query.where = whereClause || "1=1";
  141. // const result = await layer.queryFeatures(query);
  142. // const names = Array.from(
  143. // new Set(result.features.map(f => f.attributes.road_name).filter(Boolean))
  144. // );
  145. // setRoadNameOptions(names.sort());
  146. // setActiveStep(3); // Move to next step
  147. // } catch (err) {
  148. // console.error("Failed to fetch road names:", err);
  149. // setRoadNameOptions([]);
  150. // }
  151. // };
  152. // // Build where clause for queries
  153. // const buildWhereClause = () => {
  154. // const clauses: string[] = [];
  155. // if (selectedZone && selectedZone !== 'All') {
  156. // clauses.push(`zone_number = '${safe(selectedZone)}'`);
  157. // }
  158. // if (selectedWard && selectedWard !== 'All') {
  159. // clauses.push(`ward_number = '${safe(selectedWard)}'`);
  160. // }
  161. // if (selectedRoadName) {
  162. // clauses.push(`road_name = '${safe(selectedRoadName)}'`);
  163. // }
  164. // return clauses.length > 0 ? clauses.join(" AND ") : "1=1";
  165. // };
  166. // // Zoom to zone geometry
  167. // const zoomToZoneGeometry = async (zoneValue: string) => {
  168. // if (!jimuMapView || !zoneValue || zoneValue === 'All') return;
  169. // const view = jimuMapView.view;
  170. // const zoneLayer = new FeatureLayer({ url: ZONE_LAYER_URL });
  171. // try {
  172. // await zoneLayer.load();
  173. // const query = zoneLayer.createQuery();
  174. // query.where = `zone = '${safe(zoneValue)}'`;
  175. // query.outFields = ["*"];
  176. // query.returnGeometry = true;
  177. // const result = await zoneLayer.queryFeatures(query);
  178. // if (result.features.length > 0) {
  179. // const geometry = result.features[0].geometry;
  180. // await view.goTo({
  181. // target: geometry,
  182. // padding: { top: 50, bottom: 50, left: 50, right: 50 },
  183. // duration: 1000
  184. // });
  185. // }
  186. // } catch (error) {
  187. // console.error("Error zooming to zone:", error);
  188. // }
  189. // };
  190. // // Zoom to ward geometry
  191. // const zoomToWardGeometry = async (wardValue: string) => {
  192. // if (!jimuMapView || !wardValue || wardValue === 'All') return;
  193. // const view = jimuMapView.view;
  194. // const wardLayer = new FeatureLayer({ url: WARD_LAYER_URL });
  195. // try {
  196. // await wardLayer.load();
  197. // const query = wardLayer.createQuery();
  198. // query.where = `ward_no = '${safe(wardValue)}'`;
  199. // query.outFields = ["*"];
  200. // query.returnGeometry = true;
  201. // const result = await wardLayer.queryFeatures(query);
  202. // if (result.features.length > 0) {
  203. // const geometry = result.features[0].geometry;
  204. // await view.goTo({
  205. // target: geometry,
  206. // padding: { top: 50, bottom: 50, left: 50, right: 50 },
  207. // duration: 1000
  208. // });
  209. // }
  210. // } catch (error) {
  211. // console.error("Error zooming to ward:", error);
  212. // }
  213. // };
  214. // // Get feature count for utility layer
  215. // const getFeatureCount = async (layerUrl: string): Promise<number> => {
  216. // const layer = new FeatureLayer({ url: layerUrl });
  217. // const query = layer.createQuery();
  218. // query.where = buildWhereClause();
  219. // query.returnGeometry = false;
  220. // try {
  221. // return await layer.queryFeatureCount(query);
  222. // } catch (error) {
  223. // console.error("Error counting features:", error);
  224. // return 0;
  225. // }
  226. // };
  227. // // Update utility count when filters change
  228. // useEffect(() => {
  229. // const fetchCount = async () => {
  230. // if (selectedUtilityLayer && selectedUtilityLayer !== "All") {
  231. // const layerInfo = utilityLayers.find(l => l.title === selectedUtilityLayer);
  232. // if (layerInfo) {
  233. // const count = await getFeatureCount(`${UTILITY_GROUP_URL}/${layerInfo.id}`);
  234. // setUtilityCount(count);
  235. // }
  236. // } else {
  237. // setUtilityCount(null);
  238. // }
  239. // };
  240. // if (selectedLayer === "Utility") {
  241. // fetchCount();
  242. // }
  243. // }, [selectedZone, selectedWard, selectedUtilityLayer, selectedRoadName]);
  244. // // Function to update ward boundary visibility
  245. // const updateWardBoundaryVisibility = async (wardValue: string) => {
  246. // if (!jimuMapView?.view) return;
  247. // const map = jimuMapView.view.map;
  248. // try {
  249. // // Find the ward boundary layer in the map
  250. // const wardLayer = map.layers.find(layer =>
  251. // layer.url === WARD_LAYER_URL
  252. // ) as FeatureLayer;
  253. // if (wardLayer) {
  254. // if (wardValue && wardValue !== "All") {
  255. // // Show only the selected ward
  256. // wardLayer.definitionExpression = `ward_no = '${safe(wardValue)}'`;
  257. // wardLayer.visible = true;
  258. // } else {
  259. // // Show all wards when "All" is selected
  260. // wardLayer.definitionExpression = "1=1";
  261. // wardLayer.visible = true;
  262. // }
  263. // }
  264. // } catch (error) {
  265. // console.error("Error updating ward boundary visibility:", error);
  266. // }
  267. // };
  268. // //Apply filters to utility layers
  269. // const applyFilters = async () => {
  270. // if (!jimuMapView?.view) return;
  271. // const view = jimuMapView.view;
  272. // const map = view.map;
  273. // const defExp = buildWhereClause() || "1=1";
  274. // const selected = (selectedUtilityLayer ?? "").toString().trim();
  275. // // ✅ Instead of hiding Asset & Road groups, keep them visible and just apply filters
  276. // const roadLayerTitles = ["Road", "CarraigeWay"];
  277. // const assetLayerTitles = ["Asset Point", "Asset Building"];
  278. // // --- Road group ---
  279. // const roadGroup = map.layers.find(l => l.title === "Transporations") as __esri.GroupLayer;
  280. // if (roadGroup && "layers" in roadGroup) {
  281. // roadGroup.visible = true;
  282. // roadGroup.layers.forEach((sub: any) => {
  283. // if (roadLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
  284. // // Road schema field is `ward`
  285. // sub.definitionExpression = defExp.replace(/ward_number/gi, "ward");
  286. // }
  287. // sub.visible = true;
  288. // });
  289. // }
  290. // // --- Asset group ---
  291. // const assetGroup = map.layers.find(l => l.title === "GCC Asset Layers") as __esri.GroupLayer;
  292. // if (assetGroup && "layers" in assetGroup) {
  293. // assetGroup.visible = true;
  294. // assetGroup.layers.forEach((sub: any) => {
  295. // if (assetLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
  296. // // Asset schema field is `ward_no`
  297. // sub.definitionExpression = defExp.replace(/ward_number/gi, "ward_no");
  298. // }
  299. // sub.visible = true;
  300. // });
  301. // }
  302. // // helper
  303. // const safeTitle = (t?: string | null) => (t ?? "").toString().trim();
  304. // const lower = (s?: string) => safeTitle(s).toLowerCase();
  305. // try {
  306. // // 1) Find Utility group and hide all its children (so we start clean)
  307. // // const utilityGroup = map.layers.find(
  308. // // (l: any) => safeTitle(l.title).toLowerCase().includes("utility")
  309. // // );
  310. // // if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
  311. // // const children = utilityGroup.layers.toArray();
  312. // // for (const child of children) {
  313. // // try {
  314. // // child.visible = false;
  315. // // if ("definitionExpression" in child) {
  316. // // try { child.definitionExpression = "1=0"; } catch (e) { /* some sublayers may not accept */ }
  317. // // }
  318. // // } catch (e) {
  319. // // console.warn("Error hiding utility child", child, e);
  320. // // }
  321. // // }
  322. // // // hide group itself to avoid stray visuals until we show the selected layer
  323. // // utilityGroup.visible = false;
  324. // // }
  325. // // 1) Find Utility group and hide all its children (start clean)
  326. // const utilityGroup = map.layers.find(
  327. // (l: any) => safeTitle(l.title).toLowerCase().includes("utility")
  328. // );
  329. // if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
  330. // const children = utilityGroup.layers.toArray();
  331. // for (const child of children) {
  332. // try {
  333. // child.visible = false;
  334. // if ("definitionExpression" in child) {
  335. // child.definitionExpression = "1=0";
  336. // }
  337. // } catch (e) {
  338. // console.warn("Error hiding utility child", child, e);
  339. // }
  340. // }
  341. // // ❌ Don’t hide the group itself
  342. // utilityGroup.visible = true; // keep group visible so Transportation & Assets stay untouched
  343. // }
  344. // // 2) Remove any previously added temporary FeatureLayer (we'll add a new one if needed)
  345. // if (addedUtilityLayerRef.current) {
  346. // try {
  347. // // remove from map
  348. // map.layers.remove(addedUtilityLayerRef.current);
  349. // } catch (e) { /* ignore */ }
  350. // addedUtilityLayerRef.current = null;
  351. // }
  352. // // If "All" -> show all relevant children (try to show children and apply defExp where possible)
  353. // if (selected === "" || lower(selected) === "all") {
  354. // if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
  355. // const children = utilityGroup.layers.toArray();
  356. // for (const child of children) {
  357. // try {
  358. // child.visible = true;
  359. // if ("definitionExpression" in child) {
  360. // try { child.definitionExpression = defExp; } catch (e) { /* ignore */ }
  361. // }
  362. // } catch (e) { console.warn("Error enabling child for All", child, e); }
  363. // }
  364. // utilityGroup.visible = true;
  365. // }
  366. // return;
  367. // }
  368. // // 3) Find the utilityLayers entry and service URL for the selected utility
  369. // const layerInfo = utilityLayers.find(u => lower(u.title) === lower(selected));
  370. // if (!layerInfo) {
  371. // console.warn("Selected utility not found in utilityLayers config:", selected);
  372. // return;
  373. // }
  374. // const serviceUrl = `${UTILITY_GROUP_URL}/${layerInfo.id}`;
  375. // // 4) Try to find an existing layer in the map whose .url matches the service url
  376. // // or whose sublayer has that url (some map configs attach FeatureLayer as child).
  377. // let foundLayer: any = null;
  378. // const walkAndFindByUrl = (top: any) => {
  379. // // top-level url
  380. // if (top?.url && top.url === serviceUrl) return top;
  381. // // child layers (.layers)
  382. // if ("layers" in top && typeof top.layers?.toArray === "function") {
  383. // for (const c of top.layers.toArray()) {
  384. // if (c?.url && c.url === serviceUrl) return c;
  385. // }
  386. // }
  387. // // sublayers (.sublayers)
  388. // if ("sublayers" in top && typeof top.sublayers?.toArray === "function") {
  389. // for (const s of top.sublayers.toArray()) {
  390. // if (s?.url && s.url === serviceUrl) return s;
  391. // }
  392. // }
  393. // return null;
  394. // };
  395. // for (const topLayer of map.layers.toArray()) {
  396. // try {
  397. // const match = walkAndFindByUrl(topLayer);
  398. // if (match) { foundLayer = match; break; }
  399. // } catch (e) { /* ignore per-layer errors */ }
  400. // }
  401. // // 5) If found in map, show and apply definitionExpression (best-case)
  402. // if (foundLayer) {
  403. // try {
  404. // foundLayer.visible = true;
  405. // if ("definitionExpression" in foundLayer) {
  406. // try { foundLayer.definitionExpression = defExp; } catch (e) { /* ignore */ }
  407. // }
  408. // // If it has a parent group, make parent visible too:
  409. // // (some APIs provide foundLayer.parent or you can set utilityGroup.visible)
  410. // if (utilityGroup) utilityGroup.visible = true;
  411. // return;
  412. // } catch (err) {
  413. // console.warn("Error enabling existing layer:", err);
  414. // }
  415. // }
  416. // // 6) If no matching layer found in map, add a new client FeatureLayer that points to the service endpoint.
  417. // // Client FeatureLayer is guaranteed to allow definitionExpression (unless service denies).
  418. // try {
  419. // const fl = new FeatureLayer({
  420. // url: serviceUrl,
  421. // title: `${selected} (temp)`,
  422. // visible: true,
  423. // definitionExpression: defExp
  424. // });
  425. // await fl.load();
  426. // map.layers.add(fl); // add top-level
  427. // addedUtilityLayerRef.current = fl;
  428. // // ensure Utility group is visible so user sees it in TOC if needed
  429. // if (utilityGroup) utilityGroup.visible = true;
  430. // return;
  431. // } catch (err) {
  432. // console.error("Failed to add FeatureLayer for selected utility:", err);
  433. // }
  434. // } catch (err) {
  435. // console.error("applyFilters error:", err);
  436. // }
  437. // };
  438. // const applyWardFilterToLayers = async (wardValue: string, viewMap: __esri.Map) => {
  439. // if (!wardValue || wardValue === "All") {
  440. // // Reset → show all features in all layers
  441. // viewMap.layers.forEach(layer => {
  442. // if ("layers" in layer) {
  443. // layer.layers.forEach((sub: any) => {
  444. // if ("definitionExpression" in sub) {
  445. // sub.definitionExpression = "1=1"; // show all features
  446. // }
  447. // sub.visible = true; // keep all visible
  448. // });
  449. // }
  450. // });
  451. // return;
  452. // }
  453. // const roadLayerTitles = ["Road", "CarraigeWay"];
  454. // const assetLayerTitles = ["Asset Point", "Asset Building"];
  455. // // 🔹 Transportation group (filter by `ward`)
  456. // const roadGroup = viewMap.layers.find(l => l.title === "Transporations") as __esri.GroupLayer;
  457. // if (roadGroup) {
  458. // roadGroup.visible = true;
  459. // roadGroup.layers.forEach((sub: any) => {
  460. // if (roadLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
  461. // sub.definitionExpression = `ward = '${wardValue}'`; // ✅ correct field
  462. // }
  463. // sub.visible = true;
  464. // });
  465. // }
  466. // // 🔹 Asset group (filter by `ward_no`)
  467. // const assetGroup = viewMap.layers.find(l => l.title === "GCC Asset Layers") as __esri.GroupLayer;
  468. // if (assetGroup) {
  469. // assetGroup.visible = true;
  470. // assetGroup.layers.forEach((sub: any) => {
  471. // if (assetLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
  472. // sub.definitionExpression = `ward_no = '${wardValue}'`; // ✅ correct field
  473. // }
  474. // sub.visible = true;
  475. // });
  476. // }
  477. // };
  478. // useEffect(() => {
  479. // if (jimuMapView?.view && selectedWard) {
  480. // applyWardFilterToLayers(selectedWard, jimuMapView.view.map);
  481. // updateWardBoundaryVisibility(selectedWard); // Add this line to update ward boundary visibility
  482. // }
  483. // }, [selectedWard, jimuMapView]);
  484. // // Apply filters when any filter changes
  485. // useEffect(() => {
  486. // if (jimuMapView && selectedLayer === "Utility") {
  487. // applyFilters();
  488. // }
  489. // }, [selectedZone, selectedWard, selectedUtilityLayer, selectedRoadName, jimuMapView]);
  490. // const handleSubmit = async (e: React.MouseEvent) => {
  491. // e.preventDefault();
  492. // setIsLoading(true);
  493. // if (!selectedZone) {
  494. // alert("Please select a Zone");
  495. // setIsLoading(false);
  496. // return;
  497. // }
  498. // if (!jimuMapView?.view) {
  499. // setIsLoading(false);
  500. // return;
  501. // }
  502. // try {
  503. // // 1. Apply filters first
  504. // await applyFilters();
  505. // let zoomed = false;
  506. // // 2. Query features only when a single utility is selected
  507. // if (selectedLayer === "Utility" && selectedUtilityLayer && selectedUtilityLayer !== "All") {
  508. // const layerInfo = utilityLayers.find(l => l.title === selectedUtilityLayer);
  509. // if (layerInfo) {
  510. // const layer = new FeatureLayer({ url: `${UTILITY_GROUP_URL}/${layerInfo.id}` });
  511. // const query = layer.createQuery();
  512. // query.where = buildWhereClause();
  513. // query.outFields = ["*"];
  514. // query.returnGeometry = true;
  515. // const result = await layer.queryFeatures(query);
  516. // setTableData(result.features);
  517. // // Zoom to filtered features if any
  518. // if (result.features.length > 0) {
  519. // const extent = result.features.reduce(
  520. // (acc, f) => acc ? acc.union(f.geometry.extent) : f.geometry.extent,
  521. // null as __esri.Extent | null
  522. // );
  523. // if (extent) {
  524. // await jimuMapView.view.goTo({ target: extent, padding: 100, duration: 1000 });
  525. // zoomed = true;
  526. // }
  527. // }
  528. // }
  529. // } else if (selectedLayer === "Utility" && selectedUtilityLayer === "All") {
  530. // // Clear table when all utilities are shown
  531. // setTableData([]);
  532. // }
  533. // // 3. If nothing was zoomed yet, zoom to ward/zone
  534. // if (!zoomed) {
  535. // if (selectedWard && selectedWard !== "All") {
  536. // await zoomToWardGeometry(selectedWard);
  537. // } else if (selectedZone && selectedZone !== "All") {
  538. // await zoomToZoneGeometry(selectedZone);
  539. // }
  540. // }
  541. // } catch (error) {
  542. // console.error("Error in handleSubmit:", error);
  543. // } finally {
  544. // setIsLoading(false);
  545. // }
  546. // };
  547. // // Zoom to individual feature with highlight
  548. // const zoomToFeature = async (feature: any) => {
  549. // if (!jimuMapView?.view || !feature?.geometry) return;
  550. // const view = jimuMapView.view;
  551. // try {
  552. // // Clear previous highlight
  553. // if (highlightRef.current) {
  554. // highlightRef.current.remove();
  555. // highlightRef.current = null;
  556. // }
  557. // // Clear any existing graphics
  558. // if (graphicsLayerRef.current) {
  559. // view.graphics.remove(graphicsLayerRef.current);
  560. // }
  561. // // Create highlight symbol based on feature type
  562. // let highlightSymbol;
  563. // if (feature.geometry.type === "point") {
  564. // highlightSymbol = {
  565. // type: "simple-marker",
  566. // color: [255, 0, 0, 0.8],
  567. // size: 12,
  568. // outline: {
  569. // color: [255, 255, 255],
  570. // width: 2
  571. // }
  572. // };
  573. // } else if (feature.geometry.type === "polyline") {
  574. // highlightSymbol = new SimpleLineSymbol({
  575. // color: [255, 0, 0, 0.8],
  576. // width: 4
  577. // });
  578. // } else {
  579. // highlightSymbol = new SimpleFillSymbol({
  580. // color: [255, 0, 0, 0.3],
  581. // outline: {
  582. // color: [255, 0, 0],
  583. // width: 2
  584. // }
  585. // });
  586. // }
  587. // // Create highlight graphic
  588. // const highlightGraphic = new Graphic({
  589. // geometry: feature.geometry,
  590. // symbol: highlightSymbol
  591. // });
  592. // // Add to map and store reference
  593. // view.graphics.add(highlightGraphic);
  594. // graphicsLayerRef.current = highlightGraphic;
  595. // // Zoom to feature
  596. // await view.goTo({
  597. // target: feature.geometry,
  598. // zoom: feature.geometry.type === "point" ? 18 : 14,
  599. // duration: 1000
  600. // });
  601. // } catch (error) {
  602. // console.error("Error zooming to feature:", error);
  603. // }
  604. // };
  605. // // Reset button handler
  606. // const handleReset = () => {
  607. // setSelectedZone("");
  608. // setSelectedWard("");
  609. // setSelectedLayer("");
  610. // setSelectedUtilityLayer("All");
  611. // setSelectedRoadName("");
  612. // setUtilityCount(null);
  613. // setTableData([]);
  614. // setRoadNameOptions([]);
  615. // setActiveStep(0);
  616. // // Clear graphics & highlights
  617. // jimuMapView?.view?.graphics.removeAll();
  618. // highlightRef.current?.remove();
  619. // highlightRef.current = null;
  620. // if (jimuMapView?.view) {
  621. // const map = jimuMapView.view.map;
  622. // // Reset all utility layers
  623. // map.layers.forEach(layer => {
  624. // if (layer.type === "feature") {
  625. // (layer as __esri.FeatureLayer).definitionExpression = "1=1";
  626. // layer.visible = true;
  627. // } else if ("layers" in layer) {
  628. // layer.layers.forEach((sub: any) => {
  629. // sub.visible = true;
  630. // if (sub.type === "feature") sub.definitionExpression = "1=1";
  631. // });
  632. // }
  633. // });
  634. // // Remove dynamically added utility layer
  635. // if (addedUtilityLayerRef.current) {
  636. // map.layers.remove(addedUtilityLayerRef.current);
  637. // addedUtilityLayerRef.current = null;
  638. // }
  639. // // Restore GCC Asset Layers (and its subs)
  640. // const assetGroup = map.layers.find(l => l.title === "GCC Asset Layers");
  641. // if (assetGroup && "layers" in assetGroup) {
  642. // assetGroup.visible = true;
  643. // assetGroup.layers.forEach((sub: any) => sub.visible = true);
  644. // }
  645. // // Reset ward boundary layer to show all wards
  646. // const wardLayer = map.layers.find(layer =>
  647. // layer.url === WARD_LAYER_URL
  648. // ) as FeatureLayer;
  649. // if (wardLayer) {
  650. // wardLayer.definitionExpression = "1=1";
  651. // wardLayer.visible = true;
  652. // }
  653. // }
  654. // };
  655. // // Active view change handler
  656. // const activeViewChangeHandler = (jmv: JimuMapView) => {
  657. // if (jmv) setJimuMapView(jmv);
  658. // };
  659. // // Initialize map view
  660. // useEffect(() => {
  661. // const interval = setInterval(() => {
  662. // const view = mapViewManager.getMapView();
  663. // if (view) {
  664. // setJimuMapView(view);
  665. // clearInterval(interval);
  666. // }
  667. // }, 500);
  668. // return () => clearInterval(interval);
  669. // }, []);
  670. // return (
  671. // <div className="property-widget-parent">
  672. // <div className="property-widget">
  673. // <JimuMapViewComponent
  674. // useMapWidgetId={props.useMapWidgetIds?.[0]}
  675. // onActiveViewChange={activeViewChangeHandler}
  676. // />
  677. // <h3 className="widget-title">Utility Asset Filter</h3>
  678. // {/* Zone selection */}
  679. // <div className="property_form">
  680. // <label>Zone:<span style={{ color: 'red' }}>*</span></label>
  681. // <select
  682. // value={selectedZone}
  683. // onChange={e => {
  684. // const value = e.target.value;
  685. // setSelectedZone(value);
  686. // setSelectedWard("");
  687. // setSelectedRoadName("");
  688. // setRoadNameOptions([]);
  689. // if (value && value !== "All") {
  690. // zoomToZoneGeometry(value);
  691. // }
  692. // }}
  693. // required
  694. // >
  695. // <option value="">--Select Zone--</option>
  696. // {zoneOptions.map(zone => (
  697. // <option key={zone} value={zone}>{zone}</option>
  698. // ))}
  699. // </select>
  700. // </div>
  701. // {/* Ward selection */}
  702. // <div className="property_form">
  703. // <label>Ward:</label>
  704. // <select
  705. // value={selectedWard}
  706. // onChange={e => {
  707. // const value = e.target.value;
  708. // setSelectedWard(value);
  709. // setSelectedRoadName("");
  710. // setRoadNameOptions([]);
  711. // if (value && value !== "All") {
  712. // zoomToWardGeometry(value);
  713. // if (jimuMapView?.view) {
  714. // applyWardFilterToLayers(value, jimuMapView.view.map);
  715. // updateWardBoundaryVisibility(value); // ✅ finish call here
  716. // }
  717. // } else {
  718. // // Reset when "All" is selected
  719. // if (jimuMapView?.view) {
  720. // applyWardFilterToLayers("All", jimuMapView.view.map);
  721. // updateWardBoundaryVisibility("All"); // ✅ reset visibility
  722. // }
  723. // }
  724. // }}
  725. // >
  726. // <option value="">--Select Ward--</option>
  727. // {wardOptions.map(ward => (
  728. // <option key={ward} value={ward}>{ward}</option>
  729. // ))}
  730. // </select>
  731. // </div>
  732. // {/* Layer selection */}
  733. // <div className="property_form">
  734. // <label>Select Layer:</label>
  735. // <select
  736. // value={selectedLayer}
  737. // onChange={e => {
  738. // const layer = e.target.value;
  739. // setSelectedLayer(layer);
  740. // setSelectedUtilityLayer("All");
  741. // setSelectedRoadName("");
  742. // setRoadNameOptions([]);
  743. // setUtilityCount(null);
  744. // }}
  745. // >
  746. // <option value="">--Select Layer--</option>
  747. // <option value="Utility">Utility</option>
  748. // </select>
  749. // </div>
  750. // {/* Show filtering options when Utility is selected */}
  751. // {selectedLayer === "Utility" && (
  752. // <>
  753. // {/* Utility Layer selection */}
  754. // <div className="property_form">
  755. // <label>Select Utility Type:</label>
  756. // <select
  757. // value={selectedUtilityLayer}
  758. // onChange={e => {
  759. // const value = e.target.value;
  760. // setSelectedUtilityLayer(value);
  761. // setSelectedRoadName("");
  762. // if (value !== "All") {
  763. // populateRoadNames(value);
  764. // } else {
  765. // setRoadNameOptions([]);
  766. // }
  767. // }}
  768. // disabled={!selectedZone || selectedZone === "All"}
  769. // >
  770. // <option value="All">All Utilities</option>
  771. // {utilityLayerOptions.map(name => (
  772. // <option key={name} value={name}>
  773. // {name}{selectedUtilityLayer === name && utilityCount !== null ? ` (${utilityCount})` : ""}
  774. // </option>
  775. // ))}
  776. // </select>
  777. // </div>
  778. // {/* Road Name selection */}
  779. // {selectedUtilityLayer !== "All" && roadNameOptions.length > 0 && (
  780. // <div className="property_form">
  781. // <label>Select Road Name:</label>
  782. // <select
  783. // value={selectedRoadName}
  784. // onChange={e => setSelectedRoadName(e.target.value)}
  785. // >
  786. // <option value="">--All Roads--</option>
  787. // {roadNameOptions.map(name => (
  788. // <option key={name} value={name}>{name}</option>
  789. // ))}
  790. // </select>
  791. // </div>
  792. // )}
  793. // </>
  794. // )}
  795. // {/* Action buttons */}
  796. // <div className="button-group">
  797. // <button
  798. // type="button"
  799. // className={`submit-button ${isLoading ? 'loading' : ''}`}
  800. // onClick={handleSubmit}
  801. // disabled={isLoading || !selectedZone || !selectedLayer}
  802. // >
  803. // {isLoading ? <div className="spinner"></div> : "Apply Filters"}
  804. // </button>
  805. // <button
  806. // type="button"
  807. // className="reset-button"
  808. // onClick={handleReset}
  809. // >
  810. // Reset All
  811. // </button>
  812. // </div>
  813. // {/* Results table */}
  814. // {tableData.length > 0 && (
  815. // <div className="attribute-table">
  816. // <h4>Asset Details ({tableData.length} features)</h4>
  817. // <div className="table-container">
  818. // <table>
  819. // <thead>
  820. // <tr>
  821. // <th>ID</th>
  822. // <th>Zone</th>
  823. // <th>Ward</th>
  824. // <th>Road Name</th>
  825. // <th>Action</th>
  826. // </tr>
  827. // </thead>
  828. // <tbody>
  829. // {tableData.map((feature, index) => {
  830. // const attr = feature.attributes;
  831. // return (
  832. // <tr key={feature.attributes.OBJECTID || index}>
  833. // <td>{attr.OBJECTID || index + 1}</td>
  834. // <td>{attr.zone_number || 'N/A'}</td>
  835. // <td>{attr.ward_number || 'N/A'}</td>
  836. // <td>{attr.road_name || 'N/A'}</td>
  837. // <td>
  838. // <button
  839. // onClick={() => zoomToFeature(feature)}
  840. // className="zoom-button"
  841. // title="Zoom to feature"
  842. // >
  843. // <img src={zoomInIcon} alt="Zoom" width={16} height={16} />
  844. // </button>
  845. // </td>
  846. // </tr>
  847. // );
  848. // })}
  849. // </tbody>
  850. // </table>
  851. // </div>
  852. // </div>
  853. // )}
  854. // {tableData.length === 0 && selectedLayer && (
  855. // <div className="no-results">
  856. // <p>No assets found with the current filters.</p>
  857. // </div>
  858. // )}
  859. // </div>
  860. // </div>
  861. // );
  862. // };
  863. // export default RoadAssetFilter;
  864. import React, { useState, useEffect, useRef } from "react";
  865. import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
  866. import { type AllWidgetProps } from 'jimu-core';
  867. import "./simple_widget.css";
  868. import { JimuMapViewComponent, type JimuMapView } from 'jimu-arcgis';
  869. import { mapViewManager } from '../../../utils/mapview';
  870. import Graphic from "@arcgis/core/Graphic";
  871. import SimpleFillSymbol from "@arcgis/core/symbols/SimpleFillSymbol";
  872. import SimpleLineSymbol from "@arcgis/core/symbols/SimpleLineSymbol";
  873. import zoomInIcon from './assets/zoom-in.svg';
  874. import jsPDF from "jspdf";
  875. import autoTable from "jspdf-autotable";
  876. const RoadAssetFilter = (props: AllWidgetProps<any>) => {
  877. // State variables
  878. const [selectedZone, setSelectedZone] = useState<string>("");
  879. const [selectedWard, setSelectedWard] = useState<string>("");
  880. const [zoneOptions, setZoneOptions] = useState<string[]>([]);
  881. const [wardOptions, setWardOptions] = useState<string[]>([]);
  882. const [jimuMapView, setJimuMapView] = useState<JimuMapView>();
  883. const [selectedLayer, setSelectedLayer] = useState<string>("");
  884. const [selectedUtilityLayer, setSelectedUtilityLayer] = useState<string>("All");
  885. const [utilityCount, setUtilityCount] = useState<number | null>(null);
  886. const [tableData, setTableData] = useState<any[]>([]);
  887. const [utilityLayerOptions, setUtilityLayerOptions] = useState<string[]>([]);
  888. const [selectedRoadName, setSelectedRoadName] = useState<string>("");
  889. const [roadNameOptions, setRoadNameOptions] = useState<string[]>([]);
  890. const [isLoading, setIsLoading] = useState(false);
  891. const [activeStep, setActiveStep] = useState<number>(0);
  892. const addedUtilityLayerRef = useRef<any | null>(null);
  893. // Refs for cleanup
  894. const highlightRef = useRef<any>(null);
  895. const graphicsLayerRef = useRef<any>(null);
  896. // URLs for layers
  897. const ZONE_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/GCC_Administration_Boundaries/FeatureServer/3";
  898. const WARD_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/GCC_Administration_Boundaries/FeatureServer/4";
  899. const UTILITY_GROUP_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Utility/FeatureServer";
  900. const ROAD_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Transporations/FeatureServer/0";
  901. // Utility layer configuration
  902. const utilityLayers = [
  903. { id: 1, title: "CCTV" },
  904. { id: 2, title: "Gantry Sign Boards" },
  905. { id: 3, title: "Garbage Bins" },
  906. { id: 4, title: "Hand Pump" },
  907. { id: 7, title: "Manhole" },
  908. { id: 8, title: "Permanent Bunkshop" },
  909. { id: 11, title: "Police Booth" },
  910. { id: 12, title: "Public Taps" },
  911. { id: 17, title: "Street Name Board" },
  912. { id: 19, title: "Traffic Signals" },
  913. { id: 21, title: "Transformer" },
  914. { id: 23, title: "Water Tanks" },
  915. { id: 30, title: "Bus Terminus" },
  916. { id: 33, title: "Footpath" },
  917. { id: 34, title: "Median" },
  918. { id: 36, title: "Parking Stretch" },
  919. { id: 40, title: "Traffic Island" }
  920. ];
  921. // Utility to escape single quotes in SQL
  922. const safe = (val: string) => val.replace(/'/g, "''");
  923. // Apply selected utility layer visibility + filter
  924. const applyUtilityLayerSelection = async (
  925. map: __esri.Map,
  926. selectedUtilityLayer: string,
  927. defExp: string,
  928. utilityLayers: { id: number; title: string }[],
  929. UTILITY_GROUP_URL: string,
  930. addedUtilityLayerRef: React.MutableRefObject<any | null>
  931. ) => {
  932. const safeTitle = (t?: string | null) => (t ?? "").toString().trim();
  933. const lower = (s?: string) => safeTitle(s).toLowerCase();
  934. // 1) Find Utility group and hide all its children first
  935. const utilityGroup = map.layers.find(
  936. (l: any) => lower(l.title).includes("utility")
  937. );
  938. if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
  939. const children = utilityGroup.layers.toArray();
  940. for (const child of children) {
  941. child.visible = false;
  942. if ("definitionExpression" in child) {
  943. child.definitionExpression = "1=0"; // hide
  944. }
  945. }
  946. utilityGroup.visible = true;
  947. }
  948. // 2) Remove any previously added temporary FeatureLayer
  949. if (addedUtilityLayerRef.current) {
  950. try { map.layers.remove(addedUtilityLayerRef.current); } catch {}
  951. addedUtilityLayerRef.current = null;
  952. }
  953. // 3) If "All" → show all children
  954. if (!selectedUtilityLayer || lower(selectedUtilityLayer) === "all") {
  955. if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
  956. const children = utilityGroup.layers.toArray();
  957. for (const child of children) {
  958. child.visible = true;
  959. if ("definitionExpression" in child) {
  960. child.definitionExpression = defExp;
  961. }
  962. }
  963. utilityGroup.visible = true;
  964. }
  965. return;
  966. }
  967. // 4) Find service url for selected utility
  968. const layerInfo = utilityLayers.find(u => lower(u.title) === lower(selectedUtilityLayer));
  969. if (!layerInfo) return;
  970. const serviceUrl = `${UTILITY_GROUP_URL}/${layerInfo.id}`;
  971. // 5) Try to find in map by URL
  972. let foundLayer: any = null;
  973. const walkAndFindByUrl = (top: any): any => {
  974. if (top?.url && top.url === serviceUrl) return top;
  975. if ("layers" in top && typeof top.layers?.toArray === "function") {
  976. for (const c of top.layers.toArray()) {
  977. const match = walkAndFindByUrl(c);
  978. if (match) return match;
  979. }
  980. }
  981. if ("sublayers" in top && typeof top.sublayers?.toArray === "function") {
  982. for (const s of top.sublayers.toArray()) {
  983. if (s?.url && s.url === serviceUrl) return s;
  984. }
  985. }
  986. return null;
  987. };
  988. for (const topLayer of map.layers.toArray()) {
  989. const match = walkAndFindByUrl(topLayer);
  990. if (match) { foundLayer = match; break; }
  991. }
  992. if (foundLayer) {
  993. foundLayer.visible = true;
  994. if ("definitionExpression" in foundLayer) {
  995. foundLayer.definitionExpression = defExp;
  996. }
  997. if (utilityGroup) utilityGroup.visible = true;
  998. return;
  999. }
  1000. // 6) If not found → add as new FeatureLayer
  1001. const fl = new FeatureLayer({
  1002. url: serviceUrl,
  1003. title: `${selectedUtilityLayer} (temp)`,
  1004. visible: true,
  1005. definitionExpression: defExp
  1006. });
  1007. await fl.load();
  1008. map.layers.add(fl);
  1009. addedUtilityLayerRef.current = fl;
  1010. if (utilityGroup) utilityGroup.visible = true;
  1011. };
  1012. // Populate Zone dropdown
  1013. const populateZone = async () => {
  1014. try {
  1015. const layer = new FeatureLayer({ url: ZONE_LAYER_URL });
  1016. const query = layer.createQuery();
  1017. query.returnDistinctValues = true;
  1018. query.returnGeometry = false;
  1019. query.outFields = ["zone"];
  1020. query.where = "1=1";
  1021. const result = await layer.queryFeatures(query);
  1022. if (result) {
  1023. const zones = Array.from(new Set(result.features.map(f => f.attributes.zone).filter(Boolean)));
  1024. zones.sort((a, b) => Number(a) - Number(b));
  1025. setZoneOptions(['All', ...zones]);
  1026. }
  1027. } catch (error) {
  1028. console.error("Error fetching zones:", error);
  1029. }
  1030. };
  1031. useEffect(() => {
  1032. populateZone();
  1033. }, []);
  1034. // Populate Ward dropdown based on selected Zone
  1035. const populateWard = async () => {
  1036. try {
  1037. const layer = new FeatureLayer({ url: WARD_LAYER_URL });
  1038. const query = layer.createQuery();
  1039. query.returnDistinctValues = true;
  1040. query.returnGeometry = false;
  1041. query.outFields = ["ward_no"];
  1042. if (selectedZone && selectedZone !== 'All') {
  1043. query.where = `zone_no = '${safe(selectedZone)}'`;
  1044. } else {
  1045. query.where = "1=1";
  1046. }
  1047. const result = await layer.queryFeatures(query);
  1048. if (result) {
  1049. const wards = Array.from(new Set(result.features.map(f => f.attributes.ward_no).filter(Boolean)));
  1050. wards.sort((a, b) => Number(a) - Number(b));
  1051. setWardOptions(['All', ...wards]);
  1052. setSelectedWard(""); // Reset ward when zone changes
  1053. setActiveStep(1); // Move to next step
  1054. }
  1055. } catch (error) {
  1056. console.error("Error fetching wards:", error);
  1057. }
  1058. };
  1059. useEffect(() => {
  1060. if (selectedZone) {
  1061. populateWard();
  1062. }
  1063. }, [selectedZone]);
  1064. // Populate utility layer options
  1065. useEffect(() => {
  1066. if (selectedLayer === "Utility") {
  1067. const titles = utilityLayers.map(l => l.title);
  1068. setUtilityLayerOptions(["All", ...titles]);
  1069. setSelectedUtilityLayer("All");
  1070. setActiveStep(2); // Move to next step
  1071. }
  1072. }, [selectedLayer]);
  1073. // Populate road names for selected utility layer
  1074. const populateRoadNames = async (utilityLayer: string) => {
  1075. if (!selectedZone || !selectedWard || !utilityLayer || utilityLayer === "All") return;
  1076. const layerInfo = utilityLayers.find(l => l.title === utilityLayer);
  1077. if (!layerInfo) return;
  1078. const layerUrl = `${UTILITY_GROUP_URL}/${layerInfo.id}`;
  1079. const layer = new FeatureLayer({ url: layerUrl });
  1080. try {
  1081. const query = layer.createQuery();
  1082. query.returnDistinctValues = true;
  1083. query.outFields = ["road_name"];
  1084. query.returnGeometry = false;
  1085. let whereClause = "";
  1086. if (selectedZone && selectedZone !== 'All') {
  1087. whereClause += `zone_number = '${safe(selectedZone)}'`;
  1088. }
  1089. if (selectedWard && selectedWard !== 'All') {
  1090. whereClause += whereClause ? ` AND ward_number = '${safe(selectedWard)}'` : `ward_number = '${safe(selectedWard)}'`;
  1091. }
  1092. query.where = whereClause || "1=1";
  1093. const result = await layer.queryFeatures(query);
  1094. const names = Array.from(
  1095. new Set(result.features.map(f => f.attributes.road_name).filter(Boolean))
  1096. );
  1097. setRoadNameOptions(names.sort());
  1098. setActiveStep(3); // Move to next step
  1099. } catch (err) {
  1100. console.error("Failed to fetch road names:", err);
  1101. setRoadNameOptions([]);
  1102. }
  1103. };
  1104. // Build where clause for queries
  1105. const buildWhereClause = () => {
  1106. const clauses: string[] = [];
  1107. if (selectedZone && selectedZone !== 'All') {
  1108. clauses.push(`zone_number = '${safe(selectedZone)}'`);
  1109. }
  1110. if (selectedWard && selectedWard !== 'All') {
  1111. clauses.push(`ward_number = '${safe(selectedWard)}'`);
  1112. }
  1113. if (selectedRoadName) {
  1114. clauses.push(`road_name = '${safe(selectedRoadName)}'`);
  1115. }
  1116. return clauses.length > 0 ? clauses.join(" AND ") : "1=1";
  1117. };
  1118. // Zoom to zone geometry
  1119. const zoomToZoneGeometry = async (zoneValue: string) => {
  1120. if (!jimuMapView || !zoneValue || zoneValue === 'All') return;
  1121. const view = jimuMapView.view;
  1122. const zoneLayer = new FeatureLayer({ url: ZONE_LAYER_URL });
  1123. try {
  1124. await zoneLayer.load();
  1125. const query = zoneLayer.createQuery();
  1126. query.where = `zone = '${safe(zoneValue)}'`;
  1127. query.outFields = ["*"];
  1128. query.returnGeometry = true;
  1129. const result = await zoneLayer.queryFeatures(query);
  1130. if (result.features.length > 0) {
  1131. const geometry = result.features[0].geometry;
  1132. await view.goTo({
  1133. target: geometry,
  1134. padding: { top: 50, bottom: 50, left: 50, right: 50 },
  1135. duration: 1000
  1136. });
  1137. }
  1138. } catch (error) {
  1139. console.error("Error zooming to zone:", error);
  1140. }
  1141. };
  1142. // Zoom to ward geometry
  1143. const zoomToWardGeometry = async (wardValue: string) => {
  1144. if (!jimuMapView || !wardValue || wardValue === 'All') return;
  1145. const view = jimuMapView.view;
  1146. const wardLayer = new FeatureLayer({ url: WARD_LAYER_URL });
  1147. try {
  1148. await wardLayer.load();
  1149. const query = wardLayer.createQuery();
  1150. query.where = `ward_no = '${safe(wardValue)}'`;
  1151. query.outFields = ["*"];
  1152. query.returnGeometry = true;
  1153. const result = await wardLayer.queryFeatures(query);
  1154. if (result.features.length > 0) {
  1155. const geometry = result.features[0].geometry;
  1156. await view.goTo({
  1157. target: geometry,
  1158. padding: { top: 50, bottom: 50, left: 50, right: 50 },
  1159. duration: 1000
  1160. });
  1161. }
  1162. } catch (error) {
  1163. console.error("Error zooming to ward:", error);
  1164. }
  1165. };
  1166. // Get feature count for utility layer
  1167. const getFeatureCount = async (layerUrl: string): Promise<number> => {
  1168. const layer = new FeatureLayer({ url: layerUrl });
  1169. const query = layer.createQuery();
  1170. query.where = buildWhereClause();
  1171. query.returnGeometry = false;
  1172. try {
  1173. return await layer.queryFeatureCount(query);
  1174. } catch (error) {
  1175. console.error("Error counting features:", error);
  1176. return 0;
  1177. }
  1178. };
  1179. // Update utility count when filters change
  1180. useEffect(() => {
  1181. const fetchCount = async () => {
  1182. if (selectedUtilityLayer && selectedUtilityLayer !== "All") {
  1183. const layerInfo = utilityLayers.find(l => l.title === selectedUtilityLayer);
  1184. if (layerInfo) {
  1185. const count = await getFeatureCount(`${UTILITY_GROUP_URL}/${layerInfo.id}`);
  1186. setUtilityCount(count);
  1187. }
  1188. } else {
  1189. setUtilityCount(null);
  1190. }
  1191. };
  1192. if (selectedLayer === "Utility") {
  1193. fetchCount();
  1194. }
  1195. }, [selectedZone, selectedWard, selectedUtilityLayer, selectedRoadName]);
  1196. // Function to update ward boundary visibility
  1197. const updateWardBoundaryVisibility = async (wardValue: string) => {
  1198. if (!jimuMapView?.view) return;
  1199. const map = jimuMapView.view.map;
  1200. try {
  1201. // Find the ward boundary layer in the map
  1202. const wardLayer = map.layers.find(layer =>
  1203. layer.url === WARD_LAYER_URL
  1204. ) as FeatureLayer;
  1205. if (wardLayer) {
  1206. if (wardValue && wardValue !== "All") {
  1207. // Show only the selected ward
  1208. wardLayer.definitionExpression = `ward_no = '${safe(wardValue)}'`;
  1209. wardLayer.visible = true;
  1210. } else {
  1211. // Show all wards when "All" is selected
  1212. wardLayer.definitionExpression = "1=1";
  1213. wardLayer.visible = true;
  1214. }
  1215. }
  1216. } catch (error) {
  1217. console.error("Error updating ward boundary visibility:", error);
  1218. }
  1219. };
  1220. const applyFilters = async () => {
  1221. if (!jimuMapView?.view) return;
  1222. const view = jimuMapView.view;
  1223. const map = view.map;
  1224. const defExp = buildWhereClause() || "1=1";
  1225. const selected = (selectedUtilityLayer ?? "").toString().trim().toLowerCase();
  1226. // --- Keep Road group visible & filtered ---
  1227. const roadGroup = map.layers.find(l => l.title === "Transporations") as __esri.GroupLayer;
  1228. if (roadGroup && "layers" in roadGroup) {
  1229. roadGroup.visible = true;
  1230. roadGroup.layers.forEach((sub: any) => {
  1231. if (["Road", "CarraigeWay"].includes(sub.title) && "definitionExpression" in sub) {
  1232. sub.definitionExpression = defExp.replace(/ward_number/gi, "ward");
  1233. }
  1234. sub.visible = true;
  1235. });
  1236. }
  1237. // --- Keep Asset group visible & filtered ---
  1238. const assetGroup = map.layers.find(l => l.title === "GCC Asset Layers") as __esri.GroupLayer;
  1239. if (assetGroup && "layers" in assetGroup) {
  1240. assetGroup.visible = true;
  1241. assetGroup.layers.forEach((sub: any) => {
  1242. if (["Asset Point", "Asset Building"].includes(sub.title) && "definitionExpression" in sub) {
  1243. sub.definitionExpression = defExp.replace(/ward_number/gi, "ward_no");
  1244. }
  1245. sub.visible = true;
  1246. });
  1247. }
  1248. // --- Handle Utility layers ---
  1249. const utilityGroup = map.layers.find(
  1250. (l: any) => (l.title ?? "").toString().toLowerCase().includes("utility")
  1251. ) as __esri.GroupLayer;
  1252. if (!utilityGroup || !("layers" in utilityGroup)) return;
  1253. utilityGroup.visible = true;
  1254. const children = utilityGroup.layers.toArray();
  1255. for (const child of children) {
  1256. const layerTitle = (child.title ?? "").toLowerCase();
  1257. const layerInfo = utilityLayers.find(u => u.title.toLowerCase() === layerTitle);
  1258. if (!layerInfo) continue;
  1259. if (selected === "all" || layerTitle === selected) {
  1260. child.visible = true;
  1261. if (typeof child.definitionExpression !== "undefined") {
  1262. child.definitionExpression = defExp; // apply filters
  1263. }
  1264. } else {
  1265. child.visible = false; // hide all other utility layers
  1266. }
  1267. }
  1268. };
  1269. // --- Toggle Utility layer visibility (no filter change) ---
  1270. // const toggleUtilityLayerVisibility = (
  1271. // map: __esri.Map,
  1272. // selectedUtilityLayer: string
  1273. // ) => {
  1274. // const utilityGroup = map.layers.find(
  1275. // (l: any) => (l.title ?? "").toString().toLowerCase().includes("utility")
  1276. // ) as __esri.GroupLayer;
  1277. // if (!utilityGroup || !("layers" in utilityGroup)) return;
  1278. // utilityGroup.visible = true;
  1279. // const children = utilityGroup.layers.toArray();
  1280. // for (const child of children) {
  1281. // const layerTitle = (child.title ?? "").toLowerCase();
  1282. // // Case 1: "all" → show all utility layers
  1283. // if (selectedUtilityLayer === "all") {
  1284. // child.visible = true;
  1285. // }
  1286. // // Case 2: specific layer selected → only show that one
  1287. // else if (layerTitle === selectedUtilityLayer) {
  1288. // child.visible = true;
  1289. // }
  1290. // // Hide everything else
  1291. // else {
  1292. // child.visible = false;
  1293. // }
  1294. // }
  1295. // };
  1296. // const toggleUtilityLayerVisibility = (
  1297. // map: __esri.Map,
  1298. // selectedUtilityLayer: string
  1299. // ) => {
  1300. // // find the right group layer (adjust title match if needed)
  1301. // const utilityGroup = map.layers.find(
  1302. // (l: any) =>
  1303. // (l.title ?? "").toString().trim().toLowerCase() === "utility" ||
  1304. // (l.title ?? "").toString().toLowerCase().includes("utility")
  1305. // ) as __esri.GroupLayer;
  1306. // if (!utilityGroup || !("layers" in utilityGroup)) {
  1307. // console.warn("Utility group not found");
  1308. // return;
  1309. // }
  1310. // utilityGroup.visible = true;
  1311. // const children = utilityGroup.layers.toArray();
  1312. // children.forEach(child => {
  1313. // const layerTitle = (child.title ?? "").toLowerCase();
  1314. // if (selectedUtilityLayer === "all") {
  1315. // child.visible = true;
  1316. // } else if (layerTitle === selectedUtilityLayer.toLowerCase()) {
  1317. // child.visible = true;
  1318. // } else {
  1319. // child.visible = false;
  1320. // }
  1321. // });
  1322. // };
  1323. // const toggleUtilityLayerVisibility = (
  1324. // map: __esri.Map,
  1325. // selectedUtilityLayer: string
  1326. // ) => {
  1327. // const utilityGroup = map.layers.find(
  1328. // (l: any) =>
  1329. // (l.title ?? "").toString().trim().toLowerCase() === "utility" ||
  1330. // (l.title ?? "").toString().toLowerCase().includes("utility")
  1331. // ) as __esri.GroupLayer;
  1332. // if (!utilityGroup || !("layers" in utilityGroup)) {
  1333. // console.warn("Utility group not found");
  1334. // return;
  1335. // }
  1336. // utilityGroup.visible = true;
  1337. // const children = utilityGroup.layers.toArray();
  1338. // children.forEach(child => {
  1339. // const layerTitle = (child.title ?? "").toLowerCase();
  1340. // console.log("Comparing:", layerTitle, "with", selectedUtilityLayer.toLowerCase());
  1341. // if (selectedUtilityLayer === "all") {
  1342. // child.visible = true;
  1343. // } else if (layerTitle === selectedUtilityLayer.toLowerCase()) {
  1344. // child.visible = true;
  1345. // } else {
  1346. // // don't touch visibility if not matching
  1347. // console.log("Skipping:", layerTitle);
  1348. // }
  1349. // });
  1350. // };
  1351. // const toggleUtilityLayerVisibility = (
  1352. // map: __esri.Map,
  1353. // selectedUtilityLayer: string
  1354. // ) => {
  1355. // const utilityGroup = map.layers.find(
  1356. // (l: any) =>
  1357. // (l.title ?? "").toString().trim().toLowerCase() === "utility" ||
  1358. // (l.title ?? "").toLowerCase().includes("utility")
  1359. // ) as __esri.GroupLayer;
  1360. // if (!utilityGroup || !("layers" in utilityGroup)) {
  1361. // console.warn("Utility group not found");
  1362. // return;
  1363. // }
  1364. // utilityGroup.visible = true;
  1365. // // Recursive function to check children (FeatureLayers or GroupLayers)
  1366. // const toggleChildren = (layers: __esri.Collection<__esri.Layer>) => {
  1367. // layers.forEach((child: any) => {
  1368. // const childTitle = (child.title ?? "").toLowerCase();
  1369. // if ("layers" in child) {
  1370. // // child is a subgroup → recurse
  1371. // toggleChildren(child.layers);
  1372. // } else {
  1373. // if (selectedUtilityLayer === "all") {
  1374. // child.visible = true;
  1375. // console.log("Showing:", child.title);
  1376. // } else if (childTitle === selectedUtilityLayer.toLowerCase()) {
  1377. // child.visible = true;
  1378. // console.log("Matched:", child.title);
  1379. // } else {
  1380. // child.visible = false;
  1381. // console.log("Hiding:", child.title);
  1382. // }
  1383. // }
  1384. // });
  1385. // };
  1386. // toggleChildren(utilityGroup.layers);
  1387. // };
  1388. // const toggleUtilityLayerVisibility = (
  1389. // map: __esri.Map,
  1390. // selectedUtilityLayer: string
  1391. // ) => {
  1392. // const utilityGroup = map.layers.find(
  1393. // (l: any) =>
  1394. // (l.title ?? "").toString().trim().toLowerCase() === "utility" ||
  1395. // (l.title ?? "").toLowerCase().includes("utility")
  1396. // ) as __esri.GroupLayer;
  1397. // if (!utilityGroup || !("layers" in utilityGroup)) {
  1398. // console.warn("Utility group not found");
  1399. // return;
  1400. // }
  1401. // // keep the utility group itself visible
  1402. // utilityGroup.visible = true;
  1403. // // 🚨 Do NOT touch other top-level layers like Road, Asset, etc.
  1404. // // Only change children inside Utility group
  1405. // const toggleChildren = (layers: __esri.Collection<__esri.Layer>) => {
  1406. // layers.forEach((child: any) => {
  1407. // const childTitle = (child.title ?? "").trim().toLowerCase();
  1408. // if ("layers" in child) {
  1409. // // recurse if subgroup
  1410. // toggleChildren(child.layers);
  1411. // } else {
  1412. // if (selectedUtilityLayer === "all") {
  1413. // child.visible = true; // show all utility sublayers
  1414. // } else if (childTitle === selectedUtilityLayer.toLowerCase()) {
  1415. // child.visible = true; // show only selected
  1416. // } else {
  1417. // child.visible = false; // hide other utility sublayers
  1418. // }
  1419. // }
  1420. // });
  1421. // };
  1422. // toggleChildren(utilityGroup.layers);
  1423. // };
  1424. const toggleUtilityLayerVisibility = (
  1425. map: __esri.Map,
  1426. selectedUtilityLayer: string
  1427. ) => {
  1428. const utilityGroup = map.layers.find(
  1429. (l: any) =>
  1430. (l.title ?? "").toString().trim().toLowerCase() === "utility" ||
  1431. (l.title ?? "").toLowerCase().includes("utility")
  1432. ) as __esri.GroupLayer;
  1433. if (!utilityGroup || !("layers" in utilityGroup)) {
  1434. console.warn("Utility group not found");
  1435. return;
  1436. }
  1437. utilityGroup.visible = true;
  1438. // names of layers we DON'T want to affect
  1439. const exclude = ["road", "asset"];
  1440. const toggleChildren = (layers: __esri.Collection<__esri.Layer>) => {
  1441. layers.forEach((child: any) => {
  1442. const childTitle = (child.title ?? "").trim().toLowerCase();
  1443. if ("layers" in child) {
  1444. toggleChildren(child.layers);
  1445. } else {
  1446. if (exclude.includes(childTitle)) {
  1447. // 🚫 skip road/asset etc.
  1448. child.visible = true;
  1449. return;
  1450. }
  1451. if (selectedUtilityLayer === "all") {
  1452. child.visible = true;
  1453. } else if (childTitle === selectedUtilityLayer.toLowerCase()) {
  1454. child.visible = true;
  1455. } else {
  1456. child.visible = false;
  1457. }
  1458. }
  1459. });
  1460. };
  1461. toggleChildren(utilityGroup.layers);
  1462. };
  1463. //Apply filters to utility layers
  1464. // const applyFilters = async () => {
  1465. // if (!jimuMapView?.view) return;
  1466. // const view = jimuMapView.view;
  1467. // const map = view.map;
  1468. // const defExp = buildWhereClause() || "1=1";
  1469. // const selected = (selectedUtilityLayer ?? "").toString().trim();
  1470. // // Keep Road & Asset groups visible & filtered
  1471. // const roadLayerTitles = ["Road", "CarraigeWay"];
  1472. // const assetLayerTitles = ["Asset Point", "Asset Building"];
  1473. // // --- Road group ---
  1474. // const roadGroup = map.layers.find(l => l.title === "Transporations") as __esri.GroupLayer;
  1475. // if (roadGroup && "layers" in roadGroup) {
  1476. // roadGroup.visible = true;
  1477. // roadGroup.layers.forEach((sub: any) => {
  1478. // if (roadLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
  1479. // // Road schema field is `ward`
  1480. // sub.definitionExpression = defExp.replace(/ward_number/gi, "ward");
  1481. // }
  1482. // sub.visible = true;
  1483. // });
  1484. // }
  1485. // // --- Asset group ---
  1486. // const assetGroup = map.layers.find(l => l.title === "GCC Asset Layers") as __esri.GroupLayer;
  1487. // if (assetGroup && "layers" in assetGroup) {
  1488. // assetGroup.visible = true;
  1489. // assetGroup.layers.forEach((sub: any) => {
  1490. // if (assetLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
  1491. // // Asset schema field is `ward_no`
  1492. // sub.definitionExpression = defExp.replace(/ward_number/gi, "ward_no");
  1493. // }
  1494. // sub.visible = true;
  1495. // });
  1496. // }
  1497. // const safeTitle = (t?: string | null) => (t ?? "").toString().trim();
  1498. // const lower = (s?: string) => safeTitle(s).toLowerCase();
  1499. // try {
  1500. // // Find Utility group (do NOT hide it by default) and ensure it's visible
  1501. // const utilityGroup = map.layers.find(
  1502. // (l: any) => safeTitle(l.title).toLowerCase().includes("utility")
  1503. // );
  1504. // if (utilityGroup) utilityGroup.visible = true;
  1505. // // Remove any previously added temporary FeatureLayer (we'll add a new one if needed)
  1506. // if (addedUtilityLayerRef.current) {
  1507. // try {
  1508. // map.layers.remove(addedUtilityLayerRef.current);
  1509. // } catch (e) { /* ignore */ }
  1510. // addedUtilityLayerRef.current = null;
  1511. // }
  1512. // // If "All" -> show all relevant children and apply defExp where possible
  1513. // if (selected === "" || lower(selected) === "all") {
  1514. // if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
  1515. // const children = utilityGroup.layers.toArray();
  1516. // for (const child of children) {
  1517. // try {
  1518. // if ("definitionExpression" in child) {
  1519. // try { child.definitionExpression = defExp; } catch (e) { /* ignore */ }
  1520. // }
  1521. // child.visible = true;
  1522. // } catch (e) { console.warn("Error enabling child for All", child, e); }
  1523. // }
  1524. // utilityGroup.visible = true;
  1525. // }
  1526. // return;
  1527. // }
  1528. // // 3) Find the utilityLayers entry and service URL for the selected utility
  1529. // const layerInfo = utilityLayers.find(u => lower(u.title) === lower(selected));
  1530. // if (!layerInfo) {
  1531. // console.warn("Selected utility not found in utilityLayers config:", selected);
  1532. // return;
  1533. // }
  1534. // const serviceUrl = `${UTILITY_GROUP_URL}/${layerInfo.id}`;
  1535. // // 4) Try to find an existing layer in the map whose .url matches the service url
  1536. // let foundLayer: any = null;
  1537. // const walkAndFindByUrl = (top: any) => {
  1538. // if (top?.url && top.url === serviceUrl) return top;
  1539. // if ("layers" in top && typeof top.layers?.toArray === "function") {
  1540. // for (const c of top.layers.toArray()) {
  1541. // if (c?.url && c.url === serviceUrl) return c;
  1542. // }
  1543. // }
  1544. // if ("sublayers" in top && typeof top.sublayers?.toArray === "function") {
  1545. // for (const s of top.sublayers.toArray()) {
  1546. // if (s?.url && s.url === serviceUrl) return s;
  1547. // }
  1548. // }
  1549. // return null;
  1550. // };
  1551. // for (const topLayer of map.layers.toArray()) {
  1552. // try {
  1553. // const match = walkAndFindByUrl(topLayer);
  1554. // if (match) { foundLayer = match; break; }
  1555. // } catch (e) { /* ignore per-layer errors */ }
  1556. // }
  1557. // // 5) If found in map, show and apply definitionExpression (best-case)
  1558. // // if (foundLayer) {
  1559. // // try {
  1560. // // // Make sure road & asset groups remain visible (don't hide them)
  1561. // // foundLayer.visible = true;
  1562. // // if ("definitionExpression" in foundLayer) {
  1563. // // try { foundLayer.definitionExpression = defExp; } catch (e) { /* ignore */ }
  1564. // // }
  1565. // // if (utilityGroup) utilityGroup.visible = true;
  1566. // // // Hide other utility children (optional) but keep ROAD/ASSET visible. We'll hide other utilities so user sees selected one clearly.
  1567. // // if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
  1568. // // const children = utilityGroup.layers.toArray();
  1569. // // for (const child of children) {
  1570. // // try {
  1571. // // if (child?.url && child.url === serviceUrl) {
  1572. // // child.visible = true;
  1573. // // if ("definitionExpression" in child) child.definitionExpression = defExp;
  1574. // // } else {
  1575. // // // hide other utility types (still keep groups for Road/Asset untouched)
  1576. // // try { child.visible = false; } catch (e) { /* ignore */ }
  1577. // // }
  1578. // // } catch (e) { /* ignore */ }
  1579. // // }
  1580. // // }
  1581. // // return;
  1582. // // } catch (err) {
  1583. // // console.warn("Error enabling existing layer:", err);
  1584. // // }
  1585. // // }
  1586. // if (foundLayer) {
  1587. // try {
  1588. // foundLayer.visible = true;
  1589. // const where = buildWhereClause(); // uses ward_number by design
  1590. // // apply to the found layer
  1591. // try { if (typeof foundLayer.definitionExpression !== 'undefined') foundLayer.definitionExpression = where; } catch(e){}
  1592. // // if it has sublayers (MapImageLayer), apply to sublayers too
  1593. // if (foundLayer.sublayers && typeof foundLayer.sublayers.toArray === 'function') {
  1594. // foundLayer.sublayers.toArray().forEach((sl: any) => {
  1595. // try { if (typeof sl.definitionExpression !== 'undefined') sl.definitionExpression = where; } catch(e){}
  1596. // });
  1597. // }
  1598. // if (utilityGroup) utilityGroup.visible = true;
  1599. // // Optionally hide other utility children so the selected one stands out (comment/uncomment)
  1600. // if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
  1601. // const children = utilityGroup.layers.toArray();
  1602. // for (const child of children) {
  1603. // try {
  1604. // if (child?.url && child.url === serviceUrl) {
  1605. // child.visible = true;
  1606. // if (typeof child.definitionExpression !== 'undefined') child.definitionExpression = where;
  1607. // } else {
  1608. // // hide other utility children (if you want)
  1609. // try { child.visible = false; } catch(e) {}
  1610. // }
  1611. // } catch (e) { /* ignore */ }
  1612. // }
  1613. // }
  1614. // return;
  1615. // } catch (err) {
  1616. // console.warn("Error enabling existing layer:", err);
  1617. // }
  1618. // }
  1619. // // 6) If no matching layer found in map, add a new client FeatureLayer that points to the service endpoint.
  1620. // try {
  1621. // // const fl = new FeatureLayer({
  1622. // // url: serviceUrl,
  1623. // // title: `${selected} (temp)`,
  1624. // // visible: true,
  1625. // // definitionExpression: defExp
  1626. // // });
  1627. // // await fl.load();
  1628. // // map.layers.add(fl); // add top-level
  1629. // // addedUtilityLayerRef.current = fl;
  1630. // const fl = new FeatureLayer({
  1631. // url: serviceUrl,
  1632. // title: `${selected} (temp)`,
  1633. // visible: true
  1634. // });
  1635. // await fl.load();
  1636. // // apply the same ward-level where clause (buildWhereClause uses ward_number)
  1637. // try { fl.definitionExpression = buildWhereClause(); } catch(e) {}
  1638. // map.layers.add(fl);
  1639. // addedUtilityLayerRef.current = fl;
  1640. // if (utilityGroup) utilityGroup.visible = true;
  1641. // return;
  1642. // } catch (err) {
  1643. // console.error("Failed to add FeatureLayer for selected utility:", err);
  1644. // }
  1645. // } catch (err) {
  1646. // console.error("applyFilters error:", err);
  1647. // }
  1648. // };
  1649. // Replace your existing applyWardFilterToLayers with this implementation
  1650. // const applyWardFilterToLayers = async (wardValue: string, viewMap: __esri.Map) => {
  1651. // if (!viewMap) return;
  1652. // // Reset: show everything and clear any dynamically added utility layer
  1653. // if (!wardValue || wardValue === "All") {
  1654. // const topLayers: any[] = (viewMap.layers && typeof (viewMap.layers as any).toArray === "function")
  1655. // ? (viewMap.layers as any).toArray()
  1656. // : Array.from((viewMap.layers as any) || []);
  1657. // topLayers.forEach((layer: any) => {
  1658. // try {
  1659. // // Group layers with children
  1660. // if (layer.layers && typeof layer.layers.toArray === 'function') {
  1661. // const children = layer.layers.toArray();
  1662. // children.forEach((child: any) => {
  1663. // try {
  1664. // if (typeof child.definitionExpression !== 'undefined') {
  1665. // child.definitionExpression = "1=1";
  1666. // } else if (child.sublayers && typeof child.sublayers.toArray === 'function') {
  1667. // child.sublayers.toArray().forEach((sl: any) => {
  1668. // try { if (typeof sl.definitionExpression !== 'undefined') sl.definitionExpression = "1=1"; } catch(e) {}
  1669. // });
  1670. // }
  1671. // try { child.visible = true; } catch(e) {}
  1672. // } catch(e) {}
  1673. // });
  1674. // return;
  1675. // }
  1676. // // Top-level feature layers
  1677. // try { if (typeof (layer as any).definitionExpression !== 'undefined') (layer as any).definitionExpression = "1=1"; } catch(e) {}
  1678. // try { layer.visible = true; } catch(e) {}
  1679. // } catch(e) {}
  1680. // });
  1681. // // Remove any temporary utility layer we added before
  1682. // if (addedUtilityLayerRef.current) {
  1683. // try { viewMap.layers.remove(addedUtilityLayerRef.current); } catch(e) {}
  1684. // addedUtilityLayerRef.current = null;
  1685. // }
  1686. // return;
  1687. // }
  1688. // // Specific ward -> apply ward filter to each group/sub-layer.
  1689. // const wardFilter = `ward_number = '${safe(wardValue)}'`;
  1690. // const topLayers: any[] = (viewMap.layers && typeof (viewMap.layers as any).toArray === "function")
  1691. // ? (viewMap.layers as any).toArray()
  1692. // : Array.from((viewMap.layers as any) || []);
  1693. // for (const layer of topLayers) {
  1694. // try {
  1695. // // Transportation group: filter by ward (field name is ward)
  1696. // if ((layer.title || '').toString().toLowerCase().includes('transpor') && layer.layers && typeof layer.layers.toArray === 'function') {
  1697. // layer.visible = true;
  1698. // layer.layers.toArray().forEach((sub: any) => {
  1699. // try {
  1700. // if (typeof sub.definitionExpression !== 'undefined') {
  1701. // // Road layer uses 'ward' field in your schema
  1702. // sub.definitionExpression = wardFilter.replace(/ward_number/gi, 'ward');
  1703. // }
  1704. // sub.visible = true;
  1705. // } catch (e) {}
  1706. // });
  1707. // continue;
  1708. // }
  1709. // // Asset group: filter by ward_no (if required)
  1710. // if ((layer.title || '').toString().toLowerCase().includes('asset') && layer.layers && typeof layer.layers.toArray === 'function') {
  1711. // layer.visible = true;
  1712. // layer.layers.toArray().forEach((sub: any) => {
  1713. // try {
  1714. // if (typeof sub.definitionExpression !== 'undefined') {
  1715. // sub.definitionExpression = wardFilter.replace(/ward_number/gi, 'ward_no');
  1716. // }
  1717. // sub.visible = true;
  1718. // } catch (e) {}
  1719. // });
  1720. // continue;
  1721. // }
  1722. // // Utility group: apply ward_number filter to every child (they all use ward_number)
  1723. // if ((layer.title || '').toString().toLowerCase().includes('utility')) {
  1724. // layer.visible = true;
  1725. // // If the utility layer is a GroupLayer with .layers
  1726. // if (layer.layers && typeof layer.layers.toArray === 'function') {
  1727. // const children = layer.layers.toArray();
  1728. // for (const child of children) {
  1729. // try {
  1730. // if (typeof child.definitionExpression !== 'undefined') {
  1731. // // APPLY ward_number directly (you said all utility layers have ward_number)
  1732. // child.definitionExpression = wardFilter;
  1733. // } else if (child.sublayers && typeof child.sublayers.toArray === 'function') {
  1734. // // MapImageLayer sublayers
  1735. // child.sublayers.toArray().forEach((sl: any) => {
  1736. // try { if (typeof sl.definitionExpression !== 'undefined') sl.definitionExpression = wardFilter; } catch(e) {}
  1737. // });
  1738. // } else {
  1739. // // fallback: just make sure child is visible
  1740. // try { child.visible = true; } catch(e) {}
  1741. // }
  1742. // } catch (e) {
  1743. // console.warn('applyWardFilterToLayers: error applying to utility child', e);
  1744. // }
  1745. // }
  1746. // } else {
  1747. // // If the utility layer itself is a FeatureLayer
  1748. // try { if (typeof (layer as any).definitionExpression !== 'undefined') (layer as any).definitionExpression = wardFilter; } catch(e) {}
  1749. // try { layer.visible = true; } catch(e) {}
  1750. // }
  1751. // }
  1752. // } catch (err) {
  1753. // console.warn('applyWardFilterToLayers: loop error', err);
  1754. // }
  1755. // } // end for
  1756. // };
  1757. const applyWardFilterToLayers = async (wardValue: string, viewMap: __esri.Map) => {
  1758. if (!viewMap) return;
  1759. // Helper to safely apply ward filter recursively
  1760. const applyWardToLayer = (layer: any, filter: string) => {
  1761. try {
  1762. // Skip ZoneBoundary and WardBoundary layers
  1763. const title = (layer.title || '').toLowerCase();
  1764. if (title.includes('zoneboundary') || title.includes('wardboundary')) {
  1765. layer.visible = true;
  1766. return;
  1767. }
  1768. // FeatureLayer
  1769. if (layer.type === 'feature' && typeof layer.definitionExpression !== 'undefined') {
  1770. layer.definitionExpression = filter;
  1771. layer.visible = true;
  1772. }
  1773. // MapImageLayer or GroupLayer with sublayers
  1774. else if (layer.sublayers && typeof layer.sublayers.toArray === 'function') {
  1775. layer.sublayers.toArray().forEach((sl: any) => applyWardToLayer(sl, filter));
  1776. }
  1777. else if (layer.layers && typeof layer.layers.toArray === 'function') {
  1778. layer.layers.toArray().forEach((child: any) => applyWardToLayer(child, filter));
  1779. }
  1780. else {
  1781. layer.visible = true; // fallback
  1782. }
  1783. } catch (e) {
  1784. console.warn('applyWardToLayer error', e);
  1785. }
  1786. };
  1787. // Reset all layers if "All" is selected
  1788. if (!wardValue || wardValue === "All") {
  1789. const topLayers: any[] = (viewMap.layers && typeof (viewMap.layers as any).toArray === "function")
  1790. ? (viewMap.layers as any).toArray()
  1791. : Array.from((viewMap.layers as any) || []);
  1792. topLayers.forEach((layer: any) => applyWardToLayer(layer, "1=1"));
  1793. if (addedUtilityLayerRef.current) {
  1794. try { viewMap.layers.remove(addedUtilityLayerRef.current); } catch(e) {}
  1795. addedUtilityLayerRef.current = null;
  1796. }
  1797. return;
  1798. }
  1799. // Specific ward filter
  1800. const wardFilter = `ward_number = '${safe(wardValue)}'`;
  1801. const topLayers: any[] = (viewMap.layers && typeof (viewMap.layers as any).toArray === "function")
  1802. ? (viewMap.layers as any).toArray()
  1803. : Array.from((viewMap.layers as any) || []);
  1804. for (const layer of topLayers) {
  1805. try {
  1806. const title = (layer.title || '').toLowerCase();
  1807. // Skip zone/ward boundary layers
  1808. if (title.includes('zoneboundary') || title.includes('wardboundary')) continue;
  1809. // Transportation group: field is 'ward'
  1810. if (title.includes('transpor')) {
  1811. layer.visible = true;
  1812. if (layer.layers && typeof layer.layers.toArray === 'function') {
  1813. layer.layers.toArray().forEach((sub: any) => {
  1814. applyWardToLayer(sub, wardFilter.replace(/ward_number/gi, 'ward'));
  1815. });
  1816. }
  1817. continue;
  1818. }
  1819. // Asset group: field is 'ward_no'
  1820. if (title.includes('asset')) {
  1821. layer.visible = true;
  1822. if (layer.layers && typeof layer.layers.toArray === 'function') {
  1823. layer.layers.toArray().forEach((sub: any) => {
  1824. applyWardToLayer(sub, wardFilter.replace(/ward_number/gi, 'ward_no'));
  1825. });
  1826. }
  1827. continue;
  1828. }
  1829. // Utility group: all use 'ward_number'
  1830. if (title.includes('utility')) {
  1831. layer.visible = true;
  1832. applyWardToLayer(layer, wardFilter);
  1833. continue;
  1834. }
  1835. // Other top-level layers: fallback
  1836. applyWardToLayer(layer, wardFilter);
  1837. } catch (err) {
  1838. console.warn('applyWardFilterToLayers loop error', err);
  1839. }
  1840. }
  1841. };
  1842. // const applyWardFilterToLayers = async (wardValue: string, viewMap: __esri.Map) => {
  1843. // if (!wardValue || wardValue === "All") {
  1844. // // Reset → show all features in all layers
  1845. // viewMap.layers.forEach(layer => {
  1846. // if ("layers" in layer) {
  1847. // layer.layers.forEach((sub: any) => {
  1848. // if ("definitionExpression" in sub) {
  1849. // sub.definitionExpression = "1=1"; // show all features
  1850. // }
  1851. // sub.visible = true; // keep all visible
  1852. // });
  1853. // } else if ((layer as any).type === 'feature') {
  1854. // try { (layer as any).definitionExpression = "1=1"; } catch(e){}
  1855. // layer.visible = true;
  1856. // }
  1857. // });
  1858. // // Also reset any dynamically added utility layer
  1859. // if (addedUtilityLayerRef.current) {
  1860. // try {
  1861. // viewMap.layers.remove(addedUtilityLayerRef.current);
  1862. // } catch (e) { /* ignore */ }
  1863. // addedUtilityLayerRef.current = null;
  1864. // }
  1865. // return;
  1866. // }
  1867. // const roadLayerTitles = ["Road", "CarraigeWay"];
  1868. // const assetLayerTitles = ["Asset Point", "Asset Building"];
  1869. // // 🔹 Transportation group (filter by `ward`)
  1870. // const roadGroup = viewMap.layers.find(l => l.title === "Transporations") as __esri.GroupLayer;
  1871. // if (roadGroup) {
  1872. // roadGroup.visible = true;
  1873. // roadGroup.layers.forEach((sub: any) => {
  1874. // if (roadLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
  1875. // sub.definitionExpression = `ward = '${wardValue}'`; // ✅ correct field
  1876. // }
  1877. // sub.visible = true;
  1878. // });
  1879. // }
  1880. // // 🔹 Asset group (filter by `ward_no`)
  1881. // const assetGroup = viewMap.layers.find(l => l.title === "GCC Asset Layers") as __esri.GroupLayer;
  1882. // if (assetGroup) {
  1883. // assetGroup.visible = true;
  1884. // assetGroup.layers.forEach((sub: any) => {
  1885. // if (assetLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
  1886. // sub.definitionExpression = `ward_no = '${wardValue}'`; // ✅ correct field
  1887. // }
  1888. // sub.visible = true;
  1889. // });
  1890. // }
  1891. // // 🔹 Utility group (filter utilities by ward_number) — ensure we DON'T hide the group
  1892. // const utilityGroup = viewMap.layers.find((l: any) => (l.title ?? '').toString().toLowerCase().includes('utility')) as __esri.GroupLayer;
  1893. // if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === 'function') {
  1894. // utilityGroup.visible = true;
  1895. // const children = utilityGroup.layers.toArray();
  1896. // for (const child of children) {
  1897. // try {
  1898. // if ("definitionExpression" in child) {
  1899. // // apply ward filter on utility children using ward_number
  1900. // child.definitionExpression = `ward_number = '${safe(wardValue)}'`;
  1901. // }
  1902. // // Keep utilities visible so user can see them filtered by ward
  1903. // child.visible = true;
  1904. // } catch (e) {
  1905. // console.warn('Error applying ward filter to utility child', child, e);
  1906. // }
  1907. // }
  1908. // }
  1909. // };
  1910. useEffect(() => {
  1911. if (jimuMapView?.view && selectedWard) {
  1912. applyWardFilterToLayers(selectedWard, jimuMapView.view.map);
  1913. updateWardBoundaryVisibility(selectedWard); // Add this line to update ward boundary visibility
  1914. }
  1915. }, [selectedWard, jimuMapView]);
  1916. // Apply filters when utility-related filters change
  1917. useEffect(() => {
  1918. if (jimuMapView && selectedLayer === "Utility") {
  1919. applyFilters();
  1920. }
  1921. }, [selectedZone, selectedWard, selectedUtilityLayer, selectedRoadName, jimuMapView]);
  1922. const handleSubmit = async (e: React.MouseEvent) => {
  1923. e.preventDefault();
  1924. setIsLoading(true);
  1925. if (!selectedZone) {
  1926. alert("Please select a Zone");
  1927. setIsLoading(false);
  1928. return;
  1929. }
  1930. if (!jimuMapView?.view) {
  1931. setIsLoading(false);
  1932. return;
  1933. }
  1934. try {
  1935. // 1. Apply filters first
  1936. await applyFilters();
  1937. let zoomed = false;
  1938. // 2. Query features only when a single utility is selected
  1939. if (selectedLayer === "Utility" && selectedUtilityLayer && selectedUtilityLayer !== "All") {
  1940. const layerInfo = utilityLayers.find(l => l.title === selectedUtilityLayer);
  1941. if (layerInfo) {
  1942. const layer = new FeatureLayer({ url: `${UTILITY_GROUP_URL}/${layerInfo.id}` });
  1943. const query = layer.createQuery();
  1944. query.where = buildWhereClause();
  1945. query.outFields = ["*"];
  1946. query.returnGeometry = true;
  1947. const result = await layer.queryFeatures(query);
  1948. setTableData(result.features);
  1949. // Zoom to filtered features if any
  1950. if (result.features.length > 0) {
  1951. const extent = result.features.reduce(
  1952. (acc, f) => acc ? acc.union(f.geometry.extent) : f.geometry.extent,
  1953. null as __esri.Extent | null
  1954. );
  1955. if (extent) {
  1956. await jimuMapView.view.goTo({ target: extent, padding: 100, duration: 1000 });
  1957. zoomed = true;
  1958. }
  1959. }
  1960. }
  1961. } else if (selectedLayer === "Utility" && selectedUtilityLayer === "All") {
  1962. // Clear table when all utilities are shown
  1963. setTableData([]);
  1964. }
  1965. // 3. If nothing was zoomed yet, zoom to ward/zone
  1966. if (!zoomed) {
  1967. if (selectedWard && selectedWard !== "All") {
  1968. await zoomToWardGeometry(selectedWard);
  1969. } else if (selectedZone && selectedZone !== "All") {
  1970. await zoomToZoneGeometry(selectedZone);
  1971. }
  1972. }
  1973. } catch (error) {
  1974. console.error("Error in handleSubmit:", error);
  1975. } finally {
  1976. setIsLoading(false);
  1977. }
  1978. };
  1979. // Zoom to individual feature with highlight
  1980. const zoomToFeature = async (feature: any) => {
  1981. if (!jimuMapView?.view || !feature?.geometry) return;
  1982. const view = jimuMapView.view;
  1983. try {
  1984. // Clear previous highlight
  1985. if (highlightRef.current) {
  1986. highlightRef.current.remove();
  1987. highlightRef.current = null;
  1988. }
  1989. // Clear any existing graphics
  1990. if (graphicsLayerRef.current) {
  1991. view.graphics.remove(graphicsLayerRef.current);
  1992. }
  1993. // Create highlight symbol based on feature type
  1994. let highlightSymbol;
  1995. if (feature.geometry.type === "point") {
  1996. highlightSymbol = {
  1997. type: "simple-marker",
  1998. color: [255, 0, 0, 0.8],
  1999. size: 12,
  2000. outline: {
  2001. color: [255, 255, 255],
  2002. width: 2
  2003. }
  2004. };
  2005. } else if (feature.geometry.type === "polyline") {
  2006. highlightSymbol = new SimpleLineSymbol({
  2007. color: [255, 0, 0, 0.8],
  2008. width: 4
  2009. });
  2010. } else {
  2011. highlightSymbol = new SimpleFillSymbol({
  2012. color: [255, 0, 0, 0.3],
  2013. outline: {
  2014. color: [255, 0, 0],
  2015. width: 2
  2016. }
  2017. });
  2018. }
  2019. // Create highlight graphic
  2020. const highlightGraphic = new Graphic({
  2021. geometry: feature.geometry,
  2022. symbol: highlightSymbol
  2023. });
  2024. // Add to map and store reference
  2025. view.graphics.add(highlightGraphic);
  2026. graphicsLayerRef.current = highlightGraphic;
  2027. // Zoom to feature
  2028. await view.goTo({
  2029. target: feature.geometry,
  2030. zoom: feature.geometry.type === "point" ? 18 : 14,
  2031. duration: 1000
  2032. });
  2033. } catch (error) {
  2034. console.error("Error zooming to feature:", error);
  2035. }
  2036. };
  2037. // Reset button handler
  2038. const handleReset = () => {
  2039. setSelectedZone("");
  2040. setSelectedWard("");
  2041. setSelectedLayer("");
  2042. setSelectedUtilityLayer("All");
  2043. setSelectedRoadName("");
  2044. setUtilityCount(null);
  2045. setTableData([]);
  2046. setRoadNameOptions([]);
  2047. setActiveStep(0);
  2048. // Clear graphics & highlights
  2049. jimuMapView?.view?.graphics.removeAll();
  2050. highlightRef.current?.remove();
  2051. highlightRef.current = null;
  2052. if (jimuMapView?.view) {
  2053. const map = jimuMapView.view.map;
  2054. // Reset all utility layers and other groups
  2055. map.layers.forEach(layer => {
  2056. if (layer.type === "feature") {
  2057. try { (layer as __esri.FeatureLayer).definitionExpression = "1=1"; } catch(e){}
  2058. layer.visible = true;
  2059. } else if ("layers" in layer) {
  2060. layer.layers.forEach((sub: any) => {
  2061. sub.visible = true;
  2062. if (sub.type === "feature") try { sub.definitionExpression = "1=1"; } catch(e){}
  2063. });
  2064. }
  2065. });
  2066. // Remove dynamically added utility layer
  2067. if (addedUtilityLayerRef.current) {
  2068. try { map.layers.remove(addedUtilityLayerRef.current); } catch(e){}
  2069. addedUtilityLayerRef.current = null;
  2070. }
  2071. // Restore GCC Asset Layers (and its subs)
  2072. const assetGroup = map.layers.find(l => l.title === "GCC Asset Layers");
  2073. if (assetGroup && "layers" in assetGroup) {
  2074. assetGroup.visible = true;
  2075. assetGroup.layers.forEach((sub: any) => sub.visible = true);
  2076. }
  2077. // Reset ward boundary layer to show all wards
  2078. const wardLayer = map.layers.find(layer =>
  2079. layer.url === WARD_LAYER_URL
  2080. ) as FeatureLayer;
  2081. if (wardLayer) {
  2082. wardLayer.definitionExpression = "1=1";
  2083. wardLayer.visible = true;
  2084. }
  2085. }
  2086. };
  2087. // Active view change handler
  2088. const activeViewChangeHandler = (jmv: JimuMapView) => {
  2089. if (jmv) setJimuMapView(jmv);
  2090. };
  2091. // Initialize map view
  2092. useEffect(() => {
  2093. const interval = setInterval(() => {
  2094. const view = mapViewManager.getMapView();
  2095. if (view) {
  2096. setJimuMapView(view);
  2097. clearInterval(interval);
  2098. }
  2099. }, 500);
  2100. return () => clearInterval(interval);
  2101. }, []);
  2102. return (
  2103. <div className="property-widget-parent">
  2104. <div className="property-widget">
  2105. <JimuMapViewComponent
  2106. useMapWidgetId={props.useMapWidgetIds?.[0]}
  2107. onActiveViewChange={activeViewChangeHandler}
  2108. />
  2109. <h3 className="widget-title">Utility Asset Filter</h3>
  2110. {/* Zone selection */}
  2111. <div className="property_form">
  2112. <label>Zone:<span style={{ color: 'red' }}>*</span></label>
  2113. <select
  2114. value={selectedZone}
  2115. onChange={e => {
  2116. const value = e.target.value;
  2117. setSelectedZone(value);
  2118. setSelectedWard("");
  2119. setSelectedRoadName("");
  2120. setRoadNameOptions([]);
  2121. if (value && value !== "All") {
  2122. zoomToZoneGeometry(value);
  2123. }
  2124. }}
  2125. required
  2126. >
  2127. <option value="">--Select Zone--</option>
  2128. {zoneOptions.map(zone => (
  2129. <option key={zone} value={zone}>{zone}</option>
  2130. ))}
  2131. </select>
  2132. </div>
  2133. {/* Ward selection */}
  2134. <div className="property_form">
  2135. <label>Ward:</label>
  2136. <select
  2137. value={selectedWard}
  2138. onChange={e => {
  2139. const value = e.target.value;
  2140. setSelectedWard(value);
  2141. setSelectedRoadName("");
  2142. setRoadNameOptions([]);
  2143. if (value && value !== "All") {
  2144. zoomToWardGeometry(value);
  2145. if (jimuMapView?.view) {
  2146. applyWardFilterToLayers(value, jimuMapView.view.map);
  2147. updateWardBoundaryVisibility(value); // ✅ finish call here
  2148. }
  2149. } else {
  2150. // Reset when "All" is selected
  2151. if (jimuMapView?.view) {
  2152. applyWardFilterToLayers("All", jimuMapView.view.map);
  2153. updateWardBoundaryVisibility("All"); // ✅ reset visibility
  2154. }
  2155. }
  2156. }}
  2157. >
  2158. <option value="">--Select Ward--</option>
  2159. {wardOptions.map(ward => (
  2160. <option key={ward} value={ward}>{ward}</option>
  2161. ))}
  2162. </select>
  2163. </div>
  2164. {/* Layer selection */}
  2165. <div className="property_form">
  2166. <label>Select Layer:</label>
  2167. <select
  2168. value={selectedLayer}
  2169. onChange={e => {
  2170. const layer = e.target.value;
  2171. setSelectedLayer(layer);
  2172. setSelectedUtilityLayer("All");
  2173. setSelectedRoadName("");
  2174. setRoadNameOptions([]);
  2175. setUtilityCount(null);
  2176. }}
  2177. >
  2178. <option value="">--Select Layer--</option>
  2179. <option value="Utility">Utility</option>
  2180. </select>
  2181. </div>
  2182. {/* Show filtering options when Utility is selected */}
  2183. {selectedLayer === "Utility" && (
  2184. <>
  2185. {/* Utility Layer selection */}
  2186. <div className="property_form">
  2187. <label>Select Utility Type:</label>
  2188. <select
  2189. value={selectedUtilityLayer}
  2190. onChange={e => {
  2191. const value = e.target.value;
  2192. setSelectedUtilityLayer(value);
  2193. setSelectedRoadName("");
  2194. if (value !== "All") {
  2195. populateRoadNames(value);
  2196. } else {
  2197. setRoadNameOptions([]);
  2198. }
  2199. if (jimuMapView?.view) {
  2200. const map = jimuMapView.view.map;
  2201. toggleUtilityLayerVisibility(map, value.toLowerCase()); // function from earlier
  2202. }
  2203. }}
  2204. disabled={!selectedZone || selectedZone === "All"}
  2205. >
  2206. <option value="All">All Utilities</option>
  2207. {utilityLayerOptions.map(name => (
  2208. <option key={name} value={name}>
  2209. {name}{selectedUtilityLayer === name && utilityCount !== null ? ` (${utilityCount})` : ""}
  2210. </option>
  2211. ))}
  2212. </select>
  2213. </div>
  2214. {/* Road Name selection */}
  2215. {selectedUtilityLayer !== "All" && roadNameOptions.length > 0 && (
  2216. <div className="property_form">
  2217. <label>Select Road Name:</label>
  2218. <select
  2219. value={selectedRoadName}
  2220. onChange={e => setSelectedRoadName(e.target.value)}
  2221. >
  2222. <option value="">--All Roads--</option>
  2223. {roadNameOptions.map(name => (
  2224. <option key={name} value={name}>{name}</option>
  2225. ))}
  2226. </select>
  2227. </div>
  2228. )}
  2229. </>
  2230. )}
  2231. {/* Action buttons */}
  2232. <div className="button-group">
  2233. <button
  2234. type="button"
  2235. className={`submit-button ${isLoading ? 'loading' : ''}`}
  2236. onClick={handleSubmit}
  2237. disabled={isLoading || !selectedZone || !selectedLayer}
  2238. >
  2239. {isLoading ? <div className="spinner"></div> : "Apply Filters"}
  2240. </button>
  2241. <button
  2242. type="button"
  2243. className="reset-button"
  2244. onClick={handleReset}
  2245. >
  2246. Reset All
  2247. </button>
  2248. </div>
  2249. {/* Results table */}
  2250. {tableData.length > 0 && (
  2251. <div className="attribute-table">
  2252. <h4>Asset Details ({tableData.length} features)</h4>
  2253. <div className="table-container">
  2254. <table>
  2255. <thead>
  2256. <tr>
  2257. <th>ID</th>
  2258. <th>Zone</th>
  2259. <th>Ward</th>
  2260. <th>Road Name</th>
  2261. <th>Action</th>
  2262. </tr>
  2263. </thead>
  2264. <tbody>
  2265. {tableData.map((feature, index) => {
  2266. const attr = feature.attributes;
  2267. return (
  2268. <tr key={feature.attributes.OBJECTID || index}>
  2269. <td>{attr.OBJECTID || index + 1}</td>
  2270. <td>{attr.zone_number || 'N/A'}</td>
  2271. <td>{attr.ward_number || 'N/A'}</td>
  2272. <td>{attr.road_name || 'N/A'}</td>
  2273. <td>
  2274. <button
  2275. onClick={() => zoomToFeature(feature)}
  2276. className="zoom-button"
  2277. title="Zoom to feature"
  2278. >
  2279. <img src={zoomInIcon} alt="Zoom" width={16} height={16} />
  2280. </button>
  2281. </td>
  2282. </tr>
  2283. );
  2284. })}
  2285. </tbody>
  2286. </table>
  2287. </div>
  2288. </div>
  2289. )}
  2290. {tableData.length === 0 && selectedLayer && (
  2291. <div className="no-results">
  2292. <p>No assets found with the current filters.</p>
  2293. </div>
  2294. )}
  2295. </div>
  2296. </div>
  2297. );
  2298. };
  2299. export default RoadAssetFilter;