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
2571 lines
91 KiB
// import React, { useState, useEffect, useRef } from "react";
|
|
// import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
|
|
// import { type AllWidgetProps } from 'jimu-core';
|
|
// import "./simple_widget.css";
|
|
// import { JimuMapViewComponent, type JimuMapView } from 'jimu-arcgis';
|
|
// import { mapViewManager } from '../../../utils/mapview';
|
|
// import Graphic from "@arcgis/core/Graphic";
|
|
// import SimpleFillSymbol from "@arcgis/core/symbols/SimpleFillSymbol";
|
|
// import SimpleLineSymbol from "@arcgis/core/symbols/SimpleLineSymbol";
|
|
// import zoomInIcon from './assets/zoom-in.svg';
|
|
// import jsPDF from "jspdf";
|
|
// import autoTable from "jspdf-autotable";
|
|
|
|
// const RoadAssetFilter = (props: AllWidgetProps<any>) => {
|
|
// // State variables
|
|
// const [selectedZone, setSelectedZone] = useState<string>("");
|
|
// const [selectedWard, setSelectedWard] = useState<string>("");
|
|
// const [zoneOptions, setZoneOptions] = useState<string[]>([]);
|
|
// const [wardOptions, setWardOptions] = useState<string[]>([]);
|
|
// const [jimuMapView, setJimuMapView] = useState<JimuMapView>();
|
|
// const [selectedLayer, setSelectedLayer] = useState<string>("");
|
|
// const [selectedUtilityLayer, setSelectedUtilityLayer] = useState<string>("All");
|
|
// const [utilityCount, setUtilityCount] = useState<number | null>(null);
|
|
// const [tableData, setTableData] = useState<any[]>([]);
|
|
// const [utilityLayerOptions, setUtilityLayerOptions] = useState<string[]>([]);
|
|
// const [selectedRoadName, setSelectedRoadName] = useState<string>("");
|
|
// const [roadNameOptions, setRoadNameOptions] = useState<string[]>([]);
|
|
// const [isLoading, setIsLoading] = useState(false);
|
|
// const [activeStep, setActiveStep] = useState<number>(0);
|
|
// const addedUtilityLayerRef = useRef<any | null>(null);
|
|
|
|
// // Refs for cleanup
|
|
// const highlightRef = useRef<any>(null);
|
|
// const graphicsLayerRef = useRef<any>(null);
|
|
|
|
// // URLs for layers
|
|
// const ZONE_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/GCC_Administration_Boundaries/FeatureServer/3";
|
|
// const WARD_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/GCC_Administration_Boundaries/FeatureServer/4";
|
|
// const UTILITY_GROUP_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Utility/FeatureServer";
|
|
// const ROAD_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Transporations/FeatureServer/0";
|
|
|
|
// // Utility layer configuration
|
|
// const utilityLayers = [
|
|
// { id: 1, title: "CCTV" },
|
|
// { id: 2, title: "Gantry Sign Boards" },
|
|
// { id: 3, title: "Garbage Bins" },
|
|
// { id: 4, title: "Hand Pump" },
|
|
// { id: 7, title: "Manhole" },
|
|
// { id: 8, title: "Permanent Bunkshop" },
|
|
// { id: 11, title: "Police Booth" },
|
|
// { id: 12, title: "Public Taps" },
|
|
// { id: 17, title: "Street Name Board" },
|
|
// { id: 19, title: "Traffic Signals" },
|
|
// { id: 21, title: "Transformer" },
|
|
// { id: 23, title: "Water Tanks" },
|
|
// { id: 30, title: "Bus Terminus" },
|
|
// { id: 33, title: "Footpath" },
|
|
// { id: 34, title: "Median" },
|
|
// { id: 36, title: "Parking Stretch" },
|
|
// { id: 40, title: "Traffic Island" }
|
|
// ];
|
|
|
|
// // Utility to escape single quotes in SQL
|
|
// const safe = (val: string) => val.replace(/'/g, "''");
|
|
|
|
// // Populate Zone dropdown
|
|
// const populateZone = async () => {
|
|
// try {
|
|
// const layer = new FeatureLayer({ url: ZONE_LAYER_URL });
|
|
// const query = layer.createQuery();
|
|
// query.returnDistinctValues = true;
|
|
// query.returnGeometry = false;
|
|
// query.outFields = ["zone"];
|
|
// query.where = "1=1";
|
|
// const result = await layer.queryFeatures(query);
|
|
// if (result) {
|
|
// const zones = Array.from(new Set(result.features.map(f => f.attributes.zone).filter(Boolean)));
|
|
// zones.sort((a, b) => Number(a) - Number(b));
|
|
// setZoneOptions(['All', ...zones]);
|
|
// }
|
|
// } catch (error) {
|
|
// console.error("Error fetching zones:", error);
|
|
// }
|
|
// };
|
|
|
|
// useEffect(() => {
|
|
// populateZone();
|
|
// }, []);
|
|
|
|
// // Populate Ward dropdown based on selected Zone
|
|
// const populateWard = async () => {
|
|
// try {
|
|
// const layer = new FeatureLayer({ url: WARD_LAYER_URL });
|
|
// const query = layer.createQuery();
|
|
// query.returnDistinctValues = true;
|
|
// query.returnGeometry = false;
|
|
// query.outFields = ["ward_no"];
|
|
// if (selectedZone && selectedZone !== 'All') {
|
|
// query.where = `zone_no = '${safe(selectedZone)}'`;
|
|
// } else {
|
|
// query.where = "1=1";
|
|
// }
|
|
// const result = await layer.queryFeatures(query);
|
|
// if (result) {
|
|
// const wards = Array.from(new Set(result.features.map(f => f.attributes.ward_no).filter(Boolean)));
|
|
// wards.sort((a, b) => Number(a) - Number(b));
|
|
// setWardOptions(['All', ...wards]);
|
|
// setSelectedWard(""); // Reset ward when zone changes
|
|
// setActiveStep(1); // Move to next step
|
|
// }
|
|
// } catch (error) {
|
|
// console.error("Error fetching wards:", error);
|
|
// }
|
|
// };
|
|
|
|
// useEffect(() => {
|
|
// if (selectedZone) {
|
|
// populateWard();
|
|
// }
|
|
// }, [selectedZone]);
|
|
|
|
// // Populate utility layer options
|
|
// useEffect(() => {
|
|
// if (selectedLayer === "Utility") {
|
|
// const titles = utilityLayers.map(l => l.title);
|
|
// setUtilityLayerOptions(["All", ...titles]);
|
|
// setSelectedUtilityLayer("All");
|
|
// setActiveStep(2); // Move to next step
|
|
// }
|
|
// }, [selectedLayer]);
|
|
|
|
// // Populate road names for selected utility layer
|
|
// const populateRoadNames = async (utilityLayer: string) => {
|
|
// if (!selectedZone || !selectedWard || !utilityLayer || utilityLayer === "All") return;
|
|
|
|
// const layerInfo = utilityLayers.find(l => l.title === utilityLayer);
|
|
// if (!layerInfo) return;
|
|
|
|
// const layerUrl = `${UTILITY_GROUP_URL}/${layerInfo.id}`;
|
|
// const layer = new FeatureLayer({ url: layerUrl });
|
|
|
|
// try {
|
|
// const query = layer.createQuery();
|
|
// query.returnDistinctValues = true;
|
|
// query.outFields = ["road_name"];
|
|
// query.returnGeometry = false;
|
|
|
|
// let whereClause = "";
|
|
// if (selectedZone && selectedZone !== 'All') {
|
|
// whereClause += `zone_number = '${safe(selectedZone)}'`;
|
|
// }
|
|
|
|
// if (selectedWard && selectedWard !== 'All') {
|
|
// whereClause += whereClause ? ` AND ward_number = '${safe(selectedWard)}'` : `ward_number = '${safe(selectedWard)}'`;
|
|
// }
|
|
|
|
// query.where = whereClause || "1=1";
|
|
|
|
// const result = await layer.queryFeatures(query);
|
|
// const names = Array.from(
|
|
// new Set(result.features.map(f => f.attributes.road_name).filter(Boolean))
|
|
// );
|
|
// setRoadNameOptions(names.sort());
|
|
// setActiveStep(3); // Move to next step
|
|
// } catch (err) {
|
|
// console.error("Failed to fetch road names:", err);
|
|
// setRoadNameOptions([]);
|
|
// }
|
|
// };
|
|
|
|
// // Build where clause for queries
|
|
// const buildWhereClause = () => {
|
|
// const clauses: string[] = [];
|
|
|
|
// if (selectedZone && selectedZone !== 'All') {
|
|
// clauses.push(`zone_number = '${safe(selectedZone)}'`);
|
|
// }
|
|
|
|
// if (selectedWard && selectedWard !== 'All') {
|
|
// clauses.push(`ward_number = '${safe(selectedWard)}'`);
|
|
// }
|
|
|
|
// if (selectedRoadName) {
|
|
// clauses.push(`road_name = '${safe(selectedRoadName)}'`);
|
|
// }
|
|
|
|
// return clauses.length > 0 ? clauses.join(" AND ") : "1=1";
|
|
// };
|
|
|
|
// // Zoom to zone geometry
|
|
// const zoomToZoneGeometry = async (zoneValue: string) => {
|
|
// if (!jimuMapView || !zoneValue || zoneValue === 'All') return;
|
|
// const view = jimuMapView.view;
|
|
// const zoneLayer = new FeatureLayer({ url: ZONE_LAYER_URL });
|
|
|
|
// try {
|
|
// await zoneLayer.load();
|
|
// const query = zoneLayer.createQuery();
|
|
// query.where = `zone = '${safe(zoneValue)}'`;
|
|
// query.outFields = ["*"];
|
|
// query.returnGeometry = true;
|
|
|
|
// const result = await zoneLayer.queryFeatures(query);
|
|
// if (result.features.length > 0) {
|
|
// const geometry = result.features[0].geometry;
|
|
// await view.goTo({
|
|
// target: geometry,
|
|
// padding: { top: 50, bottom: 50, left: 50, right: 50 },
|
|
// duration: 1000
|
|
// });
|
|
// }
|
|
// } catch (error) {
|
|
// console.error("Error zooming to zone:", error);
|
|
// }
|
|
// };
|
|
|
|
// // Zoom to ward geometry
|
|
// const zoomToWardGeometry = async (wardValue: string) => {
|
|
// if (!jimuMapView || !wardValue || wardValue === 'All') return;
|
|
// const view = jimuMapView.view;
|
|
// const wardLayer = new FeatureLayer({ url: WARD_LAYER_URL });
|
|
|
|
// try {
|
|
// await wardLayer.load();
|
|
// const query = wardLayer.createQuery();
|
|
// query.where = `ward_no = '${safe(wardValue)}'`;
|
|
// query.outFields = ["*"];
|
|
// query.returnGeometry = true;
|
|
|
|
// const result = await wardLayer.queryFeatures(query);
|
|
// if (result.features.length > 0) {
|
|
// const geometry = result.features[0].geometry;
|
|
// await view.goTo({
|
|
// target: geometry,
|
|
// padding: { top: 50, bottom: 50, left: 50, right: 50 },
|
|
// duration: 1000
|
|
// });
|
|
// }
|
|
// } catch (error) {
|
|
// console.error("Error zooming to ward:", error);
|
|
// }
|
|
// };
|
|
|
|
// // Get feature count for utility layer
|
|
// const getFeatureCount = async (layerUrl: string): Promise<number> => {
|
|
// const layer = new FeatureLayer({ url: layerUrl });
|
|
// const query = layer.createQuery();
|
|
// query.where = buildWhereClause();
|
|
// query.returnGeometry = false;
|
|
|
|
// try {
|
|
// return await layer.queryFeatureCount(query);
|
|
// } catch (error) {
|
|
// console.error("Error counting features:", error);
|
|
// return 0;
|
|
// }
|
|
// };
|
|
|
|
// // Update utility count when filters change
|
|
// useEffect(() => {
|
|
// const fetchCount = async () => {
|
|
// if (selectedUtilityLayer && selectedUtilityLayer !== "All") {
|
|
// const layerInfo = utilityLayers.find(l => l.title === selectedUtilityLayer);
|
|
// if (layerInfo) {
|
|
// const count = await getFeatureCount(`${UTILITY_GROUP_URL}/${layerInfo.id}`);
|
|
// setUtilityCount(count);
|
|
// }
|
|
// } else {
|
|
// setUtilityCount(null);
|
|
// }
|
|
// };
|
|
|
|
// if (selectedLayer === "Utility") {
|
|
// fetchCount();
|
|
// }
|
|
// }, [selectedZone, selectedWard, selectedUtilityLayer, selectedRoadName]);
|
|
|
|
// // Function to update ward boundary visibility
|
|
// const updateWardBoundaryVisibility = async (wardValue: string) => {
|
|
// if (!jimuMapView?.view) return;
|
|
// const map = jimuMapView.view.map;
|
|
|
|
// try {
|
|
// // Find the ward boundary layer in the map
|
|
// const wardLayer = map.layers.find(layer =>
|
|
// layer.url === WARD_LAYER_URL
|
|
// ) as FeatureLayer;
|
|
|
|
// if (wardLayer) {
|
|
// if (wardValue && wardValue !== "All") {
|
|
// // Show only the selected ward
|
|
// wardLayer.definitionExpression = `ward_no = '${safe(wardValue)}'`;
|
|
// wardLayer.visible = true;
|
|
// } else {
|
|
// // Show all wards when "All" is selected
|
|
// wardLayer.definitionExpression = "1=1";
|
|
// wardLayer.visible = true;
|
|
// }
|
|
// }
|
|
// } catch (error) {
|
|
// console.error("Error updating ward boundary visibility:", error);
|
|
// }
|
|
// };
|
|
|
|
// //Apply filters to utility layers
|
|
// const applyFilters = async () => {
|
|
// if (!jimuMapView?.view) return;
|
|
// const view = jimuMapView.view;
|
|
// const map = view.map;
|
|
// const defExp = buildWhereClause() || "1=1";
|
|
// const selected = (selectedUtilityLayer ?? "").toString().trim();
|
|
|
|
// // ✅ Instead of hiding Asset & Road groups, keep them visible and just apply filters
|
|
// const roadLayerTitles = ["Road", "CarraigeWay"];
|
|
// const assetLayerTitles = ["Asset Point", "Asset Building"];
|
|
|
|
// // --- Road group ---
|
|
// const roadGroup = map.layers.find(l => l.title === "Transporations") as __esri.GroupLayer;
|
|
// if (roadGroup && "layers" in roadGroup) {
|
|
// roadGroup.visible = true;
|
|
// roadGroup.layers.forEach((sub: any) => {
|
|
// if (roadLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
|
|
// // Road schema field is `ward`
|
|
// sub.definitionExpression = defExp.replace(/ward_number/gi, "ward");
|
|
// }
|
|
// sub.visible = true;
|
|
// });
|
|
// }
|
|
|
|
// // --- Asset group ---
|
|
// const assetGroup = map.layers.find(l => l.title === "GCC Asset Layers") as __esri.GroupLayer;
|
|
// if (assetGroup && "layers" in assetGroup) {
|
|
// assetGroup.visible = true;
|
|
// assetGroup.layers.forEach((sub: any) => {
|
|
// if (assetLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
|
|
// // Asset schema field is `ward_no`
|
|
// sub.definitionExpression = defExp.replace(/ward_number/gi, "ward_no");
|
|
// }
|
|
// sub.visible = true;
|
|
// });
|
|
// }
|
|
|
|
// // helper
|
|
// const safeTitle = (t?: string | null) => (t ?? "").toString().trim();
|
|
// const lower = (s?: string) => safeTitle(s).toLowerCase();
|
|
|
|
// try {
|
|
// // 1) Find Utility group and hide all its children (so we start clean)
|
|
// // const utilityGroup = map.layers.find(
|
|
// // (l: any) => safeTitle(l.title).toLowerCase().includes("utility")
|
|
// // );
|
|
// // if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
|
|
// // const children = utilityGroup.layers.toArray();
|
|
// // for (const child of children) {
|
|
// // try {
|
|
// // child.visible = false;
|
|
// // if ("definitionExpression" in child) {
|
|
// // try { child.definitionExpression = "1=0"; } catch (e) { /* some sublayers may not accept */ }
|
|
// // }
|
|
// // } catch (e) {
|
|
// // console.warn("Error hiding utility child", child, e);
|
|
// // }
|
|
// // }
|
|
// // // hide group itself to avoid stray visuals until we show the selected layer
|
|
// // utilityGroup.visible = false;
|
|
// // }
|
|
// // 1) Find Utility group and hide all its children (start clean)
|
|
// const utilityGroup = map.layers.find(
|
|
// (l: any) => safeTitle(l.title).toLowerCase().includes("utility")
|
|
// );
|
|
// if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
|
|
// const children = utilityGroup.layers.toArray();
|
|
// for (const child of children) {
|
|
// try {
|
|
// child.visible = false;
|
|
// if ("definitionExpression" in child) {
|
|
// child.definitionExpression = "1=0";
|
|
// }
|
|
// } catch (e) {
|
|
// console.warn("Error hiding utility child", child, e);
|
|
// }
|
|
// }
|
|
// // ❌ Don’t hide the group itself
|
|
// utilityGroup.visible = true; // keep group visible so Transportation & Assets stay untouched
|
|
// }
|
|
|
|
|
|
|
|
|
|
// // 2) Remove any previously added temporary FeatureLayer (we'll add a new one if needed)
|
|
// if (addedUtilityLayerRef.current) {
|
|
// try {
|
|
// // remove from map
|
|
// map.layers.remove(addedUtilityLayerRef.current);
|
|
// } catch (e) { /* ignore */ }
|
|
// addedUtilityLayerRef.current = null;
|
|
// }
|
|
|
|
// // If "All" -> show all relevant children (try to show children and apply defExp where possible)
|
|
// if (selected === "" || lower(selected) === "all") {
|
|
// if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
|
|
// const children = utilityGroup.layers.toArray();
|
|
// for (const child of children) {
|
|
// try {
|
|
// child.visible = true;
|
|
// if ("definitionExpression" in child) {
|
|
// try { child.definitionExpression = defExp; } catch (e) { /* ignore */ }
|
|
// }
|
|
// } catch (e) { console.warn("Error enabling child for All", child, e); }
|
|
// }
|
|
// utilityGroup.visible = true;
|
|
// }
|
|
// return;
|
|
// }
|
|
|
|
// // 3) Find the utilityLayers entry and service URL for the selected utility
|
|
// const layerInfo = utilityLayers.find(u => lower(u.title) === lower(selected));
|
|
// if (!layerInfo) {
|
|
// console.warn("Selected utility not found in utilityLayers config:", selected);
|
|
// return;
|
|
// }
|
|
// const serviceUrl = `${UTILITY_GROUP_URL}/${layerInfo.id}`;
|
|
|
|
// // 4) Try to find an existing layer in the map whose .url matches the service url
|
|
// // or whose sublayer has that url (some map configs attach FeatureLayer as child).
|
|
// let foundLayer: any = null;
|
|
|
|
// const walkAndFindByUrl = (top: any) => {
|
|
// // top-level url
|
|
// if (top?.url && top.url === serviceUrl) return top;
|
|
// // child layers (.layers)
|
|
// if ("layers" in top && typeof top.layers?.toArray === "function") {
|
|
// for (const c of top.layers.toArray()) {
|
|
// if (c?.url && c.url === serviceUrl) return c;
|
|
// }
|
|
// }
|
|
// // sublayers (.sublayers)
|
|
// if ("sublayers" in top && typeof top.sublayers?.toArray === "function") {
|
|
// for (const s of top.sublayers.toArray()) {
|
|
// if (s?.url && s.url === serviceUrl) return s;
|
|
// }
|
|
// }
|
|
// return null;
|
|
// };
|
|
|
|
// for (const topLayer of map.layers.toArray()) {
|
|
// try {
|
|
// const match = walkAndFindByUrl(topLayer);
|
|
// if (match) { foundLayer = match; break; }
|
|
// } catch (e) { /* ignore per-layer errors */ }
|
|
// }
|
|
|
|
// // 5) If found in map, show and apply definitionExpression (best-case)
|
|
// if (foundLayer) {
|
|
// try {
|
|
// foundLayer.visible = true;
|
|
// if ("definitionExpression" in foundLayer) {
|
|
// try { foundLayer.definitionExpression = defExp; } catch (e) { /* ignore */ }
|
|
// }
|
|
// // If it has a parent group, make parent visible too:
|
|
// // (some APIs provide foundLayer.parent or you can set utilityGroup.visible)
|
|
// if (utilityGroup) utilityGroup.visible = true;
|
|
// return;
|
|
// } catch (err) {
|
|
// console.warn("Error enabling existing layer:", err);
|
|
// }
|
|
// }
|
|
|
|
// // 6) If no matching layer found in map, add a new client FeatureLayer that points to the service endpoint.
|
|
// // Client FeatureLayer is guaranteed to allow definitionExpression (unless service denies).
|
|
// try {
|
|
// const fl = new FeatureLayer({
|
|
// url: serviceUrl,
|
|
// title: `${selected} (temp)`,
|
|
// visible: true,
|
|
// definitionExpression: defExp
|
|
// });
|
|
// await fl.load();
|
|
// map.layers.add(fl); // add top-level
|
|
// addedUtilityLayerRef.current = fl;
|
|
// // ensure Utility group is visible so user sees it in TOC if needed
|
|
// if (utilityGroup) utilityGroup.visible = true;
|
|
// return;
|
|
// } catch (err) {
|
|
// console.error("Failed to add FeatureLayer for selected utility:", err);
|
|
// }
|
|
|
|
// } catch (err) {
|
|
// console.error("applyFilters error:", err);
|
|
// }
|
|
// };
|
|
|
|
|
|
|
|
// const applyWardFilterToLayers = async (wardValue: string, viewMap: __esri.Map) => {
|
|
// if (!wardValue || wardValue === "All") {
|
|
// // Reset → show all features in all layers
|
|
// viewMap.layers.forEach(layer => {
|
|
// if ("layers" in layer) {
|
|
// layer.layers.forEach((sub: any) => {
|
|
// if ("definitionExpression" in sub) {
|
|
// sub.definitionExpression = "1=1"; // show all features
|
|
// }
|
|
// sub.visible = true; // keep all visible
|
|
// });
|
|
// }
|
|
// });
|
|
// return;
|
|
// }
|
|
|
|
// const roadLayerTitles = ["Road", "CarraigeWay"];
|
|
// const assetLayerTitles = ["Asset Point", "Asset Building"];
|
|
|
|
// // 🔹 Transportation group (filter by `ward`)
|
|
// const roadGroup = viewMap.layers.find(l => l.title === "Transporations") as __esri.GroupLayer;
|
|
// if (roadGroup) {
|
|
// roadGroup.visible = true;
|
|
// roadGroup.layers.forEach((sub: any) => {
|
|
// if (roadLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
|
|
// sub.definitionExpression = `ward = '${wardValue}'`; // ✅ correct field
|
|
// }
|
|
// sub.visible = true;
|
|
// });
|
|
// }
|
|
|
|
// // 🔹 Asset group (filter by `ward_no`)
|
|
// const assetGroup = viewMap.layers.find(l => l.title === "GCC Asset Layers") as __esri.GroupLayer;
|
|
// if (assetGroup) {
|
|
// assetGroup.visible = true;
|
|
// assetGroup.layers.forEach((sub: any) => {
|
|
// if (assetLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
|
|
// sub.definitionExpression = `ward_no = '${wardValue}'`; // ✅ correct field
|
|
// }
|
|
// sub.visible = true;
|
|
// });
|
|
// }
|
|
// };
|
|
|
|
// useEffect(() => {
|
|
// if (jimuMapView?.view && selectedWard) {
|
|
// applyWardFilterToLayers(selectedWard, jimuMapView.view.map);
|
|
// updateWardBoundaryVisibility(selectedWard); // Add this line to update ward boundary visibility
|
|
// }
|
|
// }, [selectedWard, jimuMapView]);
|
|
|
|
// // Apply filters when any filter changes
|
|
// useEffect(() => {
|
|
// if (jimuMapView && selectedLayer === "Utility") {
|
|
// applyFilters();
|
|
// }
|
|
// }, [selectedZone, selectedWard, selectedUtilityLayer, selectedRoadName, jimuMapView]);
|
|
|
|
// const handleSubmit = async (e: React.MouseEvent) => {
|
|
// e.preventDefault();
|
|
// setIsLoading(true);
|
|
|
|
// if (!selectedZone) {
|
|
// alert("Please select a Zone");
|
|
// setIsLoading(false);
|
|
// return;
|
|
// }
|
|
|
|
// if (!jimuMapView?.view) {
|
|
// setIsLoading(false);
|
|
// return;
|
|
// }
|
|
|
|
// try {
|
|
// // 1. Apply filters first
|
|
// await applyFilters();
|
|
|
|
// let zoomed = false;
|
|
|
|
// // 2. Query features only when a single utility is selected
|
|
// if (selectedLayer === "Utility" && selectedUtilityLayer && selectedUtilityLayer !== "All") {
|
|
// const layerInfo = utilityLayers.find(l => l.title === selectedUtilityLayer);
|
|
// if (layerInfo) {
|
|
// const layer = new FeatureLayer({ url: `${UTILITY_GROUP_URL}/${layerInfo.id}` });
|
|
// const query = layer.createQuery();
|
|
// query.where = buildWhereClause();
|
|
// query.outFields = ["*"];
|
|
// query.returnGeometry = true;
|
|
|
|
// const result = await layer.queryFeatures(query);
|
|
// setTableData(result.features);
|
|
|
|
// // Zoom to filtered features if any
|
|
// if (result.features.length > 0) {
|
|
// const extent = result.features.reduce(
|
|
// (acc, f) => acc ? acc.union(f.geometry.extent) : f.geometry.extent,
|
|
// null as __esri.Extent | null
|
|
// );
|
|
// if (extent) {
|
|
// await jimuMapView.view.goTo({ target: extent, padding: 100, duration: 1000 });
|
|
// zoomed = true;
|
|
// }
|
|
// }
|
|
// }
|
|
// } else if (selectedLayer === "Utility" && selectedUtilityLayer === "All") {
|
|
// // Clear table when all utilities are shown
|
|
// setTableData([]);
|
|
// }
|
|
|
|
// // 3. If nothing was zoomed yet, zoom to ward/zone
|
|
// if (!zoomed) {
|
|
// if (selectedWard && selectedWard !== "All") {
|
|
// await zoomToWardGeometry(selectedWard);
|
|
// } else if (selectedZone && selectedZone !== "All") {
|
|
// await zoomToZoneGeometry(selectedZone);
|
|
// }
|
|
// }
|
|
|
|
// } catch (error) {
|
|
// console.error("Error in handleSubmit:", error);
|
|
// } finally {
|
|
// setIsLoading(false);
|
|
// }
|
|
// };
|
|
|
|
// // Zoom to individual feature with highlight
|
|
// const zoomToFeature = async (feature: any) => {
|
|
// if (!jimuMapView?.view || !feature?.geometry) return;
|
|
// const view = jimuMapView.view;
|
|
|
|
// try {
|
|
// // Clear previous highlight
|
|
// if (highlightRef.current) {
|
|
// highlightRef.current.remove();
|
|
// highlightRef.current = null;
|
|
// }
|
|
|
|
// // Clear any existing graphics
|
|
// if (graphicsLayerRef.current) {
|
|
// view.graphics.remove(graphicsLayerRef.current);
|
|
// }
|
|
|
|
// // Create highlight symbol based on feature type
|
|
// let highlightSymbol;
|
|
// if (feature.geometry.type === "point") {
|
|
// highlightSymbol = {
|
|
// type: "simple-marker",
|
|
// color: [255, 0, 0, 0.8],
|
|
// size: 12,
|
|
// outline: {
|
|
// color: [255, 255, 255],
|
|
// width: 2
|
|
// }
|
|
// };
|
|
// } else if (feature.geometry.type === "polyline") {
|
|
// highlightSymbol = new SimpleLineSymbol({
|
|
// color: [255, 0, 0, 0.8],
|
|
// width: 4
|
|
// });
|
|
// } else {
|
|
// highlightSymbol = new SimpleFillSymbol({
|
|
// color: [255, 0, 0, 0.3],
|
|
// outline: {
|
|
// color: [255, 0, 0],
|
|
// width: 2
|
|
// }
|
|
// });
|
|
// }
|
|
|
|
// // Create highlight graphic
|
|
// const highlightGraphic = new Graphic({
|
|
// geometry: feature.geometry,
|
|
// symbol: highlightSymbol
|
|
// });
|
|
|
|
// // Add to map and store reference
|
|
// view.graphics.add(highlightGraphic);
|
|
// graphicsLayerRef.current = highlightGraphic;
|
|
|
|
// // Zoom to feature
|
|
// await view.goTo({
|
|
// target: feature.geometry,
|
|
// zoom: feature.geometry.type === "point" ? 18 : 14,
|
|
// duration: 1000
|
|
// });
|
|
// } catch (error) {
|
|
// console.error("Error zooming to feature:", error);
|
|
// }
|
|
// };
|
|
|
|
// // Reset button handler
|
|
// const handleReset = () => {
|
|
// setSelectedZone("");
|
|
// setSelectedWard("");
|
|
// setSelectedLayer("");
|
|
// setSelectedUtilityLayer("All");
|
|
// setSelectedRoadName("");
|
|
// setUtilityCount(null);
|
|
// setTableData([]);
|
|
// setRoadNameOptions([]);
|
|
// setActiveStep(0);
|
|
|
|
// // Clear graphics & highlights
|
|
// jimuMapView?.view?.graphics.removeAll();
|
|
// highlightRef.current?.remove();
|
|
// highlightRef.current = null;
|
|
|
|
// if (jimuMapView?.view) {
|
|
// const map = jimuMapView.view.map;
|
|
|
|
// // Reset all utility layers
|
|
// map.layers.forEach(layer => {
|
|
// if (layer.type === "feature") {
|
|
// (layer as __esri.FeatureLayer).definitionExpression = "1=1";
|
|
// layer.visible = true;
|
|
// } else if ("layers" in layer) {
|
|
// layer.layers.forEach((sub: any) => {
|
|
// sub.visible = true;
|
|
// if (sub.type === "feature") sub.definitionExpression = "1=1";
|
|
// });
|
|
// }
|
|
// });
|
|
|
|
// // Remove dynamically added utility layer
|
|
// if (addedUtilityLayerRef.current) {
|
|
// map.layers.remove(addedUtilityLayerRef.current);
|
|
// addedUtilityLayerRef.current = null;
|
|
// }
|
|
|
|
// // Restore GCC Asset Layers (and its subs)
|
|
// const assetGroup = map.layers.find(l => l.title === "GCC Asset Layers");
|
|
// if (assetGroup && "layers" in assetGroup) {
|
|
// assetGroup.visible = true;
|
|
// assetGroup.layers.forEach((sub: any) => sub.visible = true);
|
|
// }
|
|
|
|
// // Reset ward boundary layer to show all wards
|
|
// const wardLayer = map.layers.find(layer =>
|
|
// layer.url === WARD_LAYER_URL
|
|
// ) as FeatureLayer;
|
|
// if (wardLayer) {
|
|
// wardLayer.definitionExpression = "1=1";
|
|
// wardLayer.visible = true;
|
|
// }
|
|
// }
|
|
// };
|
|
|
|
// // Active view change handler
|
|
// const activeViewChangeHandler = (jmv: JimuMapView) => {
|
|
// if (jmv) setJimuMapView(jmv);
|
|
// };
|
|
|
|
// // Initialize map view
|
|
// useEffect(() => {
|
|
// const interval = setInterval(() => {
|
|
// const view = mapViewManager.getMapView();
|
|
// if (view) {
|
|
// setJimuMapView(view);
|
|
// clearInterval(interval);
|
|
// }
|
|
// }, 500);
|
|
// return () => clearInterval(interval);
|
|
// }, []);
|
|
|
|
// return (
|
|
// <div className="property-widget-parent">
|
|
// <div className="property-widget">
|
|
// <JimuMapViewComponent
|
|
// useMapWidgetId={props.useMapWidgetIds?.[0]}
|
|
// onActiveViewChange={activeViewChangeHandler}
|
|
// />
|
|
|
|
// <h3 className="widget-title">Utility Asset Filter</h3>
|
|
|
|
// {/* Zone selection */}
|
|
// <div className="property_form">
|
|
// <label>Zone:<span style={{ color: 'red' }}>*</span></label>
|
|
// <select
|
|
// value={selectedZone}
|
|
// onChange={e => {
|
|
// const value = e.target.value;
|
|
// setSelectedZone(value);
|
|
// setSelectedWard("");
|
|
// setSelectedRoadName("");
|
|
// setRoadNameOptions([]);
|
|
// if (value && value !== "All") {
|
|
// zoomToZoneGeometry(value);
|
|
// }
|
|
// }}
|
|
// required
|
|
// >
|
|
// <option value="">--Select Zone--</option>
|
|
// {zoneOptions.map(zone => (
|
|
// <option key={zone} value={zone}>{zone}</option>
|
|
// ))}
|
|
// </select>
|
|
// </div>
|
|
|
|
// {/* Ward selection */}
|
|
// <div className="property_form">
|
|
// <label>Ward:</label>
|
|
// <select
|
|
// value={selectedWard}
|
|
// onChange={e => {
|
|
// const value = e.target.value;
|
|
// setSelectedWard(value);
|
|
// setSelectedRoadName("");
|
|
// setRoadNameOptions([]);
|
|
|
|
// if (value && value !== "All") {
|
|
// zoomToWardGeometry(value);
|
|
// if (jimuMapView?.view) {
|
|
// applyWardFilterToLayers(value, jimuMapView.view.map);
|
|
// updateWardBoundaryVisibility(value); // ✅ finish call here
|
|
// }
|
|
// } else {
|
|
// // Reset when "All" is selected
|
|
// if (jimuMapView?.view) {
|
|
// applyWardFilterToLayers("All", jimuMapView.view.map);
|
|
// updateWardBoundaryVisibility("All"); // ✅ reset visibility
|
|
// }
|
|
// }
|
|
// }}
|
|
// >
|
|
// <option value="">--Select Ward--</option>
|
|
// {wardOptions.map(ward => (
|
|
// <option key={ward} value={ward}>{ward}</option>
|
|
// ))}
|
|
// </select>
|
|
// </div>
|
|
|
|
|
|
// {/* Layer selection */}
|
|
// <div className="property_form">
|
|
// <label>Select Layer:</label>
|
|
// <select
|
|
// value={selectedLayer}
|
|
// onChange={e => {
|
|
// const layer = e.target.value;
|
|
// setSelectedLayer(layer);
|
|
// setSelectedUtilityLayer("All");
|
|
// setSelectedRoadName("");
|
|
// setRoadNameOptions([]);
|
|
// setUtilityCount(null);
|
|
// }}
|
|
// >
|
|
// <option value="">--Select Layer--</option>
|
|
// <option value="Utility">Utility</option>
|
|
// </select>
|
|
// </div>
|
|
|
|
// {/* Show filtering options when Utility is selected */}
|
|
// {selectedLayer === "Utility" && (
|
|
// <>
|
|
// {/* Utility Layer selection */}
|
|
// <div className="property_form">
|
|
// <label>Select Utility Type:</label>
|
|
// <select
|
|
// value={selectedUtilityLayer}
|
|
// onChange={e => {
|
|
// const value = e.target.value;
|
|
// setSelectedUtilityLayer(value);
|
|
// setSelectedRoadName("");
|
|
// if (value !== "All") {
|
|
// populateRoadNames(value);
|
|
// } else {
|
|
// setRoadNameOptions([]);
|
|
// }
|
|
// }}
|
|
// disabled={!selectedZone || selectedZone === "All"}
|
|
// >
|
|
// <option value="All">All Utilities</option>
|
|
// {utilityLayerOptions.map(name => (
|
|
// <option key={name} value={name}>
|
|
// {name}{selectedUtilityLayer === name && utilityCount !== null ? ` (${utilityCount})` : ""}
|
|
// </option>
|
|
// ))}
|
|
// </select>
|
|
// </div>
|
|
|
|
// {/* Road Name selection */}
|
|
// {selectedUtilityLayer !== "All" && roadNameOptions.length > 0 && (
|
|
// <div className="property_form">
|
|
// <label>Select Road Name:</label>
|
|
// <select
|
|
// value={selectedRoadName}
|
|
// onChange={e => setSelectedRoadName(e.target.value)}
|
|
// >
|
|
// <option value="">--All Roads--</option>
|
|
// {roadNameOptions.map(name => (
|
|
// <option key={name} value={name}>{name}</option>
|
|
// ))}
|
|
// </select>
|
|
// </div>
|
|
// )}
|
|
// </>
|
|
// )}
|
|
|
|
// {/* Action buttons */}
|
|
// <div className="button-group">
|
|
// <button
|
|
// type="button"
|
|
// className={`submit-button ${isLoading ? 'loading' : ''}`}
|
|
// onClick={handleSubmit}
|
|
// disabled={isLoading || !selectedZone || !selectedLayer}
|
|
// >
|
|
// {isLoading ? <div className="spinner"></div> : "Apply Filters"}
|
|
// </button>
|
|
// <button
|
|
// type="button"
|
|
// className="reset-button"
|
|
// onClick={handleReset}
|
|
// >
|
|
// Reset All
|
|
// </button>
|
|
// </div>
|
|
|
|
// {/* Results table */}
|
|
// {tableData.length > 0 && (
|
|
// <div className="attribute-table">
|
|
// <h4>Asset Details ({tableData.length} features)</h4>
|
|
// <div className="table-container">
|
|
// <table>
|
|
// <thead>
|
|
// <tr>
|
|
// <th>ID</th>
|
|
// <th>Zone</th>
|
|
// <th>Ward</th>
|
|
// <th>Road Name</th>
|
|
// <th>Action</th>
|
|
// </tr>
|
|
// </thead>
|
|
// <tbody>
|
|
// {tableData.map((feature, index) => {
|
|
// const attr = feature.attributes;
|
|
// return (
|
|
// <tr key={feature.attributes.OBJECTID || index}>
|
|
// <td>{attr.OBJECTID || index + 1}</td>
|
|
// <td>{attr.zone_number || 'N/A'}</td>
|
|
// <td>{attr.ward_number || 'N/A'}</td>
|
|
// <td>{attr.road_name || 'N/A'}</td>
|
|
// <td>
|
|
// <button
|
|
// onClick={() => zoomToFeature(feature)}
|
|
// className="zoom-button"
|
|
// title="Zoom to feature"
|
|
// >
|
|
// <img src={zoomInIcon} alt="Zoom" width={16} height={16} />
|
|
// </button>
|
|
// </td>
|
|
// </tr>
|
|
// );
|
|
// })}
|
|
// </tbody>
|
|
// </table>
|
|
// </div>
|
|
// </div>
|
|
// )}
|
|
|
|
// {tableData.length === 0 && selectedLayer && (
|
|
// <div className="no-results">
|
|
// <p>No assets found with the current filters.</p>
|
|
// </div>
|
|
// )}
|
|
// </div>
|
|
// </div>
|
|
// );
|
|
// };
|
|
|
|
// export default RoadAssetFilter;
|
|
|
|
import React, { useState, useEffect, useRef } from "react";
|
|
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
|
|
import { type AllWidgetProps } from 'jimu-core';
|
|
import "./simple_widget.css";
|
|
import { JimuMapViewComponent, type JimuMapView } from 'jimu-arcgis';
|
|
import { mapViewManager } from '../../../utils/mapview';
|
|
import Graphic from "@arcgis/core/Graphic";
|
|
import SimpleFillSymbol from "@arcgis/core/symbols/SimpleFillSymbol";
|
|
import SimpleLineSymbol from "@arcgis/core/symbols/SimpleLineSymbol";
|
|
import zoomInIcon from './assets/zoom-in.svg';
|
|
import jsPDF from "jspdf";
|
|
import autoTable from "jspdf-autotable";
|
|
|
|
const RoadAssetFilter = (props: AllWidgetProps<any>) => {
|
|
// State variables
|
|
const [selectedZone, setSelectedZone] = useState<string>("");
|
|
const [selectedWard, setSelectedWard] = useState<string>("");
|
|
const [zoneOptions, setZoneOptions] = useState<string[]>([]);
|
|
const [wardOptions, setWardOptions] = useState<string[]>([]);
|
|
const [jimuMapView, setJimuMapView] = useState<JimuMapView>();
|
|
const [selectedLayer, setSelectedLayer] = useState<string>("");
|
|
const [selectedUtilityLayer, setSelectedUtilityLayer] = useState<string>("All");
|
|
const [utilityCount, setUtilityCount] = useState<number | null>(null);
|
|
const [tableData, setTableData] = useState<any[]>([]);
|
|
const [utilityLayerOptions, setUtilityLayerOptions] = useState<string[]>([]);
|
|
const [selectedRoadName, setSelectedRoadName] = useState<string>("");
|
|
const [roadNameOptions, setRoadNameOptions] = useState<string[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [activeStep, setActiveStep] = useState<number>(0);
|
|
const addedUtilityLayerRef = useRef<any | null>(null);
|
|
|
|
// Refs for cleanup
|
|
const highlightRef = useRef<any>(null);
|
|
const graphicsLayerRef = useRef<any>(null);
|
|
|
|
// URLs for layers
|
|
const ZONE_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/GCC_Administration_Boundaries/FeatureServer/3";
|
|
const WARD_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/GCC_Administration_Boundaries/FeatureServer/4";
|
|
const UTILITY_GROUP_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Utility/FeatureServer";
|
|
const ROAD_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Transporations/FeatureServer/0";
|
|
|
|
// Utility layer configuration
|
|
const utilityLayers = [
|
|
{ id: 1, title: "CCTV" },
|
|
{ id: 2, title: "Gantry Sign Boards" },
|
|
{ id: 3, title: "Garbage Bins" },
|
|
{ id: 4, title: "Hand Pump" },
|
|
{ id: 7, title: "Manhole" },
|
|
{ id: 8, title: "Permanent Bunkshop" },
|
|
{ id: 11, title: "Police Booth" },
|
|
{ id: 12, title: "Public Taps" },
|
|
{ id: 17, title: "Street Name Board" },
|
|
{ id: 19, title: "Traffic Signals" },
|
|
{ id: 21, title: "Transformer" },
|
|
{ id: 23, title: "Water Tanks" },
|
|
{ id: 30, title: "Bus Terminus" },
|
|
{ id: 33, title: "Footpath" },
|
|
{ id: 34, title: "Median" },
|
|
{ id: 36, title: "Parking Stretch" },
|
|
{ id: 40, title: "Traffic Island" }
|
|
];
|
|
|
|
// Utility to escape single quotes in SQL
|
|
const safe = (val: string) => val.replace(/'/g, "''");
|
|
// Apply selected utility layer visibility + filter
|
|
const applyUtilityLayerSelection = async (
|
|
map: __esri.Map,
|
|
selectedUtilityLayer: string,
|
|
defExp: string,
|
|
utilityLayers: { id: number; title: string }[],
|
|
UTILITY_GROUP_URL: string,
|
|
addedUtilityLayerRef: React.MutableRefObject<any | null>
|
|
) => {
|
|
const safeTitle = (t?: string | null) => (t ?? "").toString().trim();
|
|
const lower = (s?: string) => safeTitle(s).toLowerCase();
|
|
|
|
// 1) Find Utility group and hide all its children first
|
|
const utilityGroup = map.layers.find(
|
|
(l: any) => lower(l.title).includes("utility")
|
|
);
|
|
if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
|
|
const children = utilityGroup.layers.toArray();
|
|
for (const child of children) {
|
|
child.visible = false;
|
|
if ("definitionExpression" in child) {
|
|
child.definitionExpression = "1=0"; // hide
|
|
}
|
|
}
|
|
utilityGroup.visible = true;
|
|
}
|
|
|
|
// 2) Remove any previously added temporary FeatureLayer
|
|
if (addedUtilityLayerRef.current) {
|
|
try { map.layers.remove(addedUtilityLayerRef.current); } catch {}
|
|
addedUtilityLayerRef.current = null;
|
|
}
|
|
|
|
// 3) If "All" → show all children
|
|
if (!selectedUtilityLayer || lower(selectedUtilityLayer) === "all") {
|
|
if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
|
|
const children = utilityGroup.layers.toArray();
|
|
for (const child of children) {
|
|
child.visible = true;
|
|
if ("definitionExpression" in child) {
|
|
child.definitionExpression = defExp;
|
|
}
|
|
}
|
|
utilityGroup.visible = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 4) Find service url for selected utility
|
|
const layerInfo = utilityLayers.find(u => lower(u.title) === lower(selectedUtilityLayer));
|
|
if (!layerInfo) return;
|
|
const serviceUrl = `${UTILITY_GROUP_URL}/${layerInfo.id}`;
|
|
|
|
// 5) Try to find in map by URL
|
|
let foundLayer: any = null;
|
|
const walkAndFindByUrl = (top: any): any => {
|
|
if (top?.url && top.url === serviceUrl) return top;
|
|
if ("layers" in top && typeof top.layers?.toArray === "function") {
|
|
for (const c of top.layers.toArray()) {
|
|
const match = walkAndFindByUrl(c);
|
|
if (match) return match;
|
|
}
|
|
}
|
|
if ("sublayers" in top && typeof top.sublayers?.toArray === "function") {
|
|
for (const s of top.sublayers.toArray()) {
|
|
if (s?.url && s.url === serviceUrl) return s;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
for (const topLayer of map.layers.toArray()) {
|
|
const match = walkAndFindByUrl(topLayer);
|
|
if (match) { foundLayer = match; break; }
|
|
}
|
|
|
|
if (foundLayer) {
|
|
foundLayer.visible = true;
|
|
if ("definitionExpression" in foundLayer) {
|
|
foundLayer.definitionExpression = defExp;
|
|
}
|
|
if (utilityGroup) utilityGroup.visible = true;
|
|
return;
|
|
}
|
|
|
|
// 6) If not found → add as new FeatureLayer
|
|
const fl = new FeatureLayer({
|
|
url: serviceUrl,
|
|
title: `${selectedUtilityLayer} (temp)`,
|
|
visible: true,
|
|
definitionExpression: defExp
|
|
});
|
|
await fl.load();
|
|
map.layers.add(fl);
|
|
addedUtilityLayerRef.current = fl;
|
|
if (utilityGroup) utilityGroup.visible = true;
|
|
};
|
|
|
|
// Populate Zone dropdown
|
|
const populateZone = async () => {
|
|
try {
|
|
const layer = new FeatureLayer({ url: ZONE_LAYER_URL });
|
|
const query = layer.createQuery();
|
|
query.returnDistinctValues = true;
|
|
query.returnGeometry = false;
|
|
query.outFields = ["zone"];
|
|
query.where = "1=1";
|
|
const result = await layer.queryFeatures(query);
|
|
if (result) {
|
|
const zones = Array.from(new Set(result.features.map(f => f.attributes.zone).filter(Boolean)));
|
|
zones.sort((a, b) => Number(a) - Number(b));
|
|
setZoneOptions(['All', ...zones]);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching zones:", error);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
populateZone();
|
|
}, []);
|
|
|
|
// Populate Ward dropdown based on selected Zone
|
|
const populateWard = async () => {
|
|
try {
|
|
const layer = new FeatureLayer({ url: WARD_LAYER_URL });
|
|
const query = layer.createQuery();
|
|
query.returnDistinctValues = true;
|
|
query.returnGeometry = false;
|
|
query.outFields = ["ward_no"];
|
|
if (selectedZone && selectedZone !== 'All') {
|
|
query.where = `zone_no = '${safe(selectedZone)}'`;
|
|
} else {
|
|
query.where = "1=1";
|
|
}
|
|
const result = await layer.queryFeatures(query);
|
|
if (result) {
|
|
const wards = Array.from(new Set(result.features.map(f => f.attributes.ward_no).filter(Boolean)));
|
|
wards.sort((a, b) => Number(a) - Number(b));
|
|
setWardOptions(['All', ...wards]);
|
|
setSelectedWard(""); // Reset ward when zone changes
|
|
setActiveStep(1); // Move to next step
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching wards:", error);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (selectedZone) {
|
|
populateWard();
|
|
}
|
|
}, [selectedZone]);
|
|
|
|
// Populate utility layer options
|
|
useEffect(() => {
|
|
if (selectedLayer === "Utility") {
|
|
const titles = utilityLayers.map(l => l.title);
|
|
setUtilityLayerOptions(["All", ...titles]);
|
|
setSelectedUtilityLayer("All");
|
|
setActiveStep(2); // Move to next step
|
|
}
|
|
}, [selectedLayer]);
|
|
|
|
// Populate road names for selected utility layer
|
|
const populateRoadNames = async (utilityLayer: string) => {
|
|
if (!selectedZone || !selectedWard || !utilityLayer || utilityLayer === "All") return;
|
|
|
|
const layerInfo = utilityLayers.find(l => l.title === utilityLayer);
|
|
if (!layerInfo) return;
|
|
|
|
const layerUrl = `${UTILITY_GROUP_URL}/${layerInfo.id}`;
|
|
const layer = new FeatureLayer({ url: layerUrl });
|
|
|
|
try {
|
|
const query = layer.createQuery();
|
|
query.returnDistinctValues = true;
|
|
query.outFields = ["road_name"];
|
|
query.returnGeometry = false;
|
|
|
|
let whereClause = "";
|
|
if (selectedZone && selectedZone !== 'All') {
|
|
whereClause += `zone_number = '${safe(selectedZone)}'`;
|
|
}
|
|
|
|
if (selectedWard && selectedWard !== 'All') {
|
|
whereClause += whereClause ? ` AND ward_number = '${safe(selectedWard)}'` : `ward_number = '${safe(selectedWard)}'`;
|
|
}
|
|
|
|
query.where = whereClause || "1=1";
|
|
|
|
const result = await layer.queryFeatures(query);
|
|
const names = Array.from(
|
|
new Set(result.features.map(f => f.attributes.road_name).filter(Boolean))
|
|
);
|
|
setRoadNameOptions(names.sort());
|
|
setActiveStep(3); // Move to next step
|
|
} catch (err) {
|
|
console.error("Failed to fetch road names:", err);
|
|
setRoadNameOptions([]);
|
|
}
|
|
};
|
|
|
|
// Build where clause for queries
|
|
const buildWhereClause = () => {
|
|
const clauses: string[] = [];
|
|
|
|
if (selectedZone && selectedZone !== 'All') {
|
|
clauses.push(`zone_number = '${safe(selectedZone)}'`);
|
|
}
|
|
|
|
if (selectedWard && selectedWard !== 'All') {
|
|
clauses.push(`ward_number = '${safe(selectedWard)}'`);
|
|
}
|
|
|
|
if (selectedRoadName) {
|
|
clauses.push(`road_name = '${safe(selectedRoadName)}'`);
|
|
}
|
|
|
|
return clauses.length > 0 ? clauses.join(" AND ") : "1=1";
|
|
};
|
|
|
|
// Zoom to zone geometry
|
|
const zoomToZoneGeometry = async (zoneValue: string) => {
|
|
if (!jimuMapView || !zoneValue || zoneValue === 'All') return;
|
|
const view = jimuMapView.view;
|
|
const zoneLayer = new FeatureLayer({ url: ZONE_LAYER_URL });
|
|
|
|
try {
|
|
await zoneLayer.load();
|
|
const query = zoneLayer.createQuery();
|
|
query.where = `zone = '${safe(zoneValue)}'`;
|
|
query.outFields = ["*"];
|
|
query.returnGeometry = true;
|
|
|
|
const result = await zoneLayer.queryFeatures(query);
|
|
if (result.features.length > 0) {
|
|
const geometry = result.features[0].geometry;
|
|
await view.goTo({
|
|
target: geometry,
|
|
padding: { top: 50, bottom: 50, left: 50, right: 50 },
|
|
duration: 1000
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("Error zooming to zone:", error);
|
|
}
|
|
};
|
|
|
|
// Zoom to ward geometry
|
|
const zoomToWardGeometry = async (wardValue: string) => {
|
|
if (!jimuMapView || !wardValue || wardValue === 'All') return;
|
|
const view = jimuMapView.view;
|
|
const wardLayer = new FeatureLayer({ url: WARD_LAYER_URL });
|
|
|
|
try {
|
|
await wardLayer.load();
|
|
const query = wardLayer.createQuery();
|
|
query.where = `ward_no = '${safe(wardValue)}'`;
|
|
query.outFields = ["*"];
|
|
query.returnGeometry = true;
|
|
|
|
const result = await wardLayer.queryFeatures(query);
|
|
if (result.features.length > 0) {
|
|
const geometry = result.features[0].geometry;
|
|
await view.goTo({
|
|
target: geometry,
|
|
padding: { top: 50, bottom: 50, left: 50, right: 50 },
|
|
duration: 1000
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("Error zooming to ward:", error);
|
|
}
|
|
};
|
|
|
|
// Get feature count for utility layer
|
|
const getFeatureCount = async (layerUrl: string): Promise<number> => {
|
|
const layer = new FeatureLayer({ url: layerUrl });
|
|
const query = layer.createQuery();
|
|
query.where = buildWhereClause();
|
|
query.returnGeometry = false;
|
|
|
|
try {
|
|
return await layer.queryFeatureCount(query);
|
|
} catch (error) {
|
|
console.error("Error counting features:", error);
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
// Update utility count when filters change
|
|
useEffect(() => {
|
|
const fetchCount = async () => {
|
|
if (selectedUtilityLayer && selectedUtilityLayer !== "All") {
|
|
const layerInfo = utilityLayers.find(l => l.title === selectedUtilityLayer);
|
|
if (layerInfo) {
|
|
const count = await getFeatureCount(`${UTILITY_GROUP_URL}/${layerInfo.id}`);
|
|
setUtilityCount(count);
|
|
}
|
|
} else {
|
|
setUtilityCount(null);
|
|
}
|
|
};
|
|
|
|
if (selectedLayer === "Utility") {
|
|
fetchCount();
|
|
}
|
|
}, [selectedZone, selectedWard, selectedUtilityLayer, selectedRoadName]);
|
|
|
|
// Function to update ward boundary visibility
|
|
const updateWardBoundaryVisibility = async (wardValue: string) => {
|
|
if (!jimuMapView?.view) return;
|
|
const map = jimuMapView.view.map;
|
|
|
|
try {
|
|
// Find the ward boundary layer in the map
|
|
const wardLayer = map.layers.find(layer =>
|
|
layer.url === WARD_LAYER_URL
|
|
) as FeatureLayer;
|
|
|
|
if (wardLayer) {
|
|
if (wardValue && wardValue !== "All") {
|
|
// Show only the selected ward
|
|
wardLayer.definitionExpression = `ward_no = '${safe(wardValue)}'`;
|
|
wardLayer.visible = true;
|
|
} else {
|
|
// Show all wards when "All" is selected
|
|
wardLayer.definitionExpression = "1=1";
|
|
wardLayer.visible = true;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Error updating ward boundary visibility:", error);
|
|
}
|
|
};
|
|
const applyFilters = async () => {
|
|
if (!jimuMapView?.view) return;
|
|
const view = jimuMapView.view;
|
|
const map = view.map;
|
|
|
|
const defExp = buildWhereClause() || "1=1";
|
|
const selected = (selectedUtilityLayer ?? "").toString().trim().toLowerCase();
|
|
|
|
// --- Keep Road group visible & filtered ---
|
|
const roadGroup = map.layers.find(l => l.title === "Transporations") as __esri.GroupLayer;
|
|
if (roadGroup && "layers" in roadGroup) {
|
|
roadGroup.visible = true;
|
|
roadGroup.layers.forEach((sub: any) => {
|
|
if (["Road", "CarraigeWay"].includes(sub.title) && "definitionExpression" in sub) {
|
|
sub.definitionExpression = defExp.replace(/ward_number/gi, "ward");
|
|
}
|
|
sub.visible = true;
|
|
});
|
|
}
|
|
|
|
// --- Keep Asset group visible & filtered ---
|
|
const assetGroup = map.layers.find(l => l.title === "GCC Asset Layers") as __esri.GroupLayer;
|
|
if (assetGroup && "layers" in assetGroup) {
|
|
assetGroup.visible = true;
|
|
assetGroup.layers.forEach((sub: any) => {
|
|
if (["Asset Point", "Asset Building"].includes(sub.title) && "definitionExpression" in sub) {
|
|
sub.definitionExpression = defExp.replace(/ward_number/gi, "ward_no");
|
|
}
|
|
sub.visible = true;
|
|
});
|
|
}
|
|
|
|
// --- Handle Utility layers ---
|
|
const utilityGroup = map.layers.find(
|
|
(l: any) => (l.title ?? "").toString().toLowerCase().includes("utility")
|
|
) as __esri.GroupLayer;
|
|
|
|
if (!utilityGroup || !("layers" in utilityGroup)) return;
|
|
|
|
utilityGroup.visible = true;
|
|
const children = utilityGroup.layers.toArray();
|
|
|
|
for (const child of children) {
|
|
const layerTitle = (child.title ?? "").toLowerCase();
|
|
const layerInfo = utilityLayers.find(u => u.title.toLowerCase() === layerTitle);
|
|
|
|
if (!layerInfo) continue;
|
|
|
|
if (selected === "all" || layerTitle === selected) {
|
|
child.visible = true;
|
|
if (typeof child.definitionExpression !== "undefined") {
|
|
child.definitionExpression = defExp; // apply filters
|
|
}
|
|
} else {
|
|
child.visible = false; // hide all other utility layers
|
|
}
|
|
}
|
|
};
|
|
// --- Toggle Utility layer visibility (no filter change) ---
|
|
// const toggleUtilityLayerVisibility = (
|
|
// map: __esri.Map,
|
|
// selectedUtilityLayer: string
|
|
// ) => {
|
|
// const utilityGroup = map.layers.find(
|
|
// (l: any) => (l.title ?? "").toString().toLowerCase().includes("utility")
|
|
// ) as __esri.GroupLayer;
|
|
|
|
// if (!utilityGroup || !("layers" in utilityGroup)) return;
|
|
|
|
// utilityGroup.visible = true;
|
|
// const children = utilityGroup.layers.toArray();
|
|
|
|
// for (const child of children) {
|
|
// const layerTitle = (child.title ?? "").toLowerCase();
|
|
|
|
// // Case 1: "all" → show all utility layers
|
|
// if (selectedUtilityLayer === "all") {
|
|
// child.visible = true;
|
|
// }
|
|
// // Case 2: specific layer selected → only show that one
|
|
// else if (layerTitle === selectedUtilityLayer) {
|
|
// child.visible = true;
|
|
// }
|
|
// // Hide everything else
|
|
// else {
|
|
// child.visible = false;
|
|
// }
|
|
// }
|
|
// };
|
|
|
|
// const toggleUtilityLayerVisibility = (
|
|
// map: __esri.Map,
|
|
// selectedUtilityLayer: string
|
|
// ) => {
|
|
// // find the right group layer (adjust title match if needed)
|
|
// const utilityGroup = map.layers.find(
|
|
// (l: any) =>
|
|
// (l.title ?? "").toString().trim().toLowerCase() === "utility" ||
|
|
// (l.title ?? "").toString().toLowerCase().includes("utility")
|
|
// ) as __esri.GroupLayer;
|
|
|
|
// if (!utilityGroup || !("layers" in utilityGroup)) {
|
|
// console.warn("Utility group not found");
|
|
// return;
|
|
// }
|
|
|
|
// utilityGroup.visible = true;
|
|
// const children = utilityGroup.layers.toArray();
|
|
|
|
// children.forEach(child => {
|
|
// const layerTitle = (child.title ?? "").toLowerCase();
|
|
|
|
// if (selectedUtilityLayer === "all") {
|
|
// child.visible = true;
|
|
// } else if (layerTitle === selectedUtilityLayer.toLowerCase()) {
|
|
// child.visible = true;
|
|
// } else {
|
|
// child.visible = false;
|
|
// }
|
|
// });
|
|
// };
|
|
// const toggleUtilityLayerVisibility = (
|
|
// map: __esri.Map,
|
|
// selectedUtilityLayer: string
|
|
// ) => {
|
|
// const utilityGroup = map.layers.find(
|
|
// (l: any) =>
|
|
// (l.title ?? "").toString().trim().toLowerCase() === "utility" ||
|
|
// (l.title ?? "").toString().toLowerCase().includes("utility")
|
|
// ) as __esri.GroupLayer;
|
|
|
|
// if (!utilityGroup || !("layers" in utilityGroup)) {
|
|
// console.warn("Utility group not found");
|
|
// return;
|
|
// }
|
|
|
|
// utilityGroup.visible = true;
|
|
// const children = utilityGroup.layers.toArray();
|
|
|
|
// children.forEach(child => {
|
|
// const layerTitle = (child.title ?? "").toLowerCase();
|
|
// console.log("Comparing:", layerTitle, "with", selectedUtilityLayer.toLowerCase());
|
|
|
|
// if (selectedUtilityLayer === "all") {
|
|
// child.visible = true;
|
|
// } else if (layerTitle === selectedUtilityLayer.toLowerCase()) {
|
|
// child.visible = true;
|
|
// } else {
|
|
// // don't touch visibility if not matching
|
|
// console.log("Skipping:", layerTitle);
|
|
// }
|
|
// });
|
|
// };
|
|
// const toggleUtilityLayerVisibility = (
|
|
// map: __esri.Map,
|
|
// selectedUtilityLayer: string
|
|
// ) => {
|
|
// const utilityGroup = map.layers.find(
|
|
// (l: any) =>
|
|
// (l.title ?? "").toString().trim().toLowerCase() === "utility" ||
|
|
// (l.title ?? "").toLowerCase().includes("utility")
|
|
// ) as __esri.GroupLayer;
|
|
|
|
// if (!utilityGroup || !("layers" in utilityGroup)) {
|
|
// console.warn("Utility group not found");
|
|
// return;
|
|
// }
|
|
|
|
// utilityGroup.visible = true;
|
|
|
|
// // Recursive function to check children (FeatureLayers or GroupLayers)
|
|
// const toggleChildren = (layers: __esri.Collection<__esri.Layer>) => {
|
|
// layers.forEach((child: any) => {
|
|
// const childTitle = (child.title ?? "").toLowerCase();
|
|
|
|
// if ("layers" in child) {
|
|
// // child is a subgroup → recurse
|
|
// toggleChildren(child.layers);
|
|
// } else {
|
|
// if (selectedUtilityLayer === "all") {
|
|
// child.visible = true;
|
|
// console.log("Showing:", child.title);
|
|
// } else if (childTitle === selectedUtilityLayer.toLowerCase()) {
|
|
// child.visible = true;
|
|
// console.log("Matched:", child.title);
|
|
// } else {
|
|
// child.visible = false;
|
|
// console.log("Hiding:", child.title);
|
|
// }
|
|
// }
|
|
// });
|
|
// };
|
|
|
|
// toggleChildren(utilityGroup.layers);
|
|
// };
|
|
// const toggleUtilityLayerVisibility = (
|
|
// map: __esri.Map,
|
|
// selectedUtilityLayer: string
|
|
// ) => {
|
|
// const utilityGroup = map.layers.find(
|
|
// (l: any) =>
|
|
// (l.title ?? "").toString().trim().toLowerCase() === "utility" ||
|
|
// (l.title ?? "").toLowerCase().includes("utility")
|
|
// ) as __esri.GroupLayer;
|
|
|
|
// if (!utilityGroup || !("layers" in utilityGroup)) {
|
|
// console.warn("Utility group not found");
|
|
// return;
|
|
// }
|
|
|
|
// // keep the utility group itself visible
|
|
// utilityGroup.visible = true;
|
|
|
|
// // 🚨 Do NOT touch other top-level layers like Road, Asset, etc.
|
|
// // Only change children inside Utility group
|
|
// const toggleChildren = (layers: __esri.Collection<__esri.Layer>) => {
|
|
// layers.forEach((child: any) => {
|
|
// const childTitle = (child.title ?? "").trim().toLowerCase();
|
|
|
|
// if ("layers" in child) {
|
|
// // recurse if subgroup
|
|
// toggleChildren(child.layers);
|
|
// } else {
|
|
// if (selectedUtilityLayer === "all") {
|
|
// child.visible = true; // show all utility sublayers
|
|
// } else if (childTitle === selectedUtilityLayer.toLowerCase()) {
|
|
// child.visible = true; // show only selected
|
|
// } else {
|
|
// child.visible = false; // hide other utility sublayers
|
|
// }
|
|
// }
|
|
// });
|
|
// };
|
|
|
|
// toggleChildren(utilityGroup.layers);
|
|
// };
|
|
const toggleUtilityLayerVisibility = (
|
|
map: __esri.Map,
|
|
selectedUtilityLayer: string
|
|
) => {
|
|
const utilityGroup = map.layers.find(
|
|
(l: any) =>
|
|
(l.title ?? "").toString().trim().toLowerCase() === "utility" ||
|
|
(l.title ?? "").toLowerCase().includes("utility")
|
|
) as __esri.GroupLayer;
|
|
|
|
if (!utilityGroup || !("layers" in utilityGroup)) {
|
|
console.warn("Utility group not found");
|
|
return;
|
|
}
|
|
|
|
utilityGroup.visible = true;
|
|
|
|
// names of layers we DON'T want to affect
|
|
const exclude = ["road", "asset"];
|
|
|
|
const toggleChildren = (layers: __esri.Collection<__esri.Layer>) => {
|
|
layers.forEach((child: any) => {
|
|
const childTitle = (child.title ?? "").trim().toLowerCase();
|
|
|
|
if ("layers" in child) {
|
|
toggleChildren(child.layers);
|
|
} else {
|
|
if (exclude.includes(childTitle)) {
|
|
// 🚫 skip road/asset etc.
|
|
child.visible = true;
|
|
return;
|
|
}
|
|
|
|
if (selectedUtilityLayer === "all") {
|
|
child.visible = true;
|
|
} else if (childTitle === selectedUtilityLayer.toLowerCase()) {
|
|
child.visible = true;
|
|
} else {
|
|
child.visible = false;
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
toggleChildren(utilityGroup.layers);
|
|
};
|
|
|
|
//Apply filters to utility layers
|
|
// const applyFilters = async () => {
|
|
// if (!jimuMapView?.view) return;
|
|
// const view = jimuMapView.view;
|
|
// const map = view.map;
|
|
// const defExp = buildWhereClause() || "1=1";
|
|
// const selected = (selectedUtilityLayer ?? "").toString().trim();
|
|
|
|
// // Keep Road & Asset groups visible & filtered
|
|
// const roadLayerTitles = ["Road", "CarraigeWay"];
|
|
// const assetLayerTitles = ["Asset Point", "Asset Building"];
|
|
|
|
// // --- Road group ---
|
|
// const roadGroup = map.layers.find(l => l.title === "Transporations") as __esri.GroupLayer;
|
|
// if (roadGroup && "layers" in roadGroup) {
|
|
// roadGroup.visible = true;
|
|
// roadGroup.layers.forEach((sub: any) => {
|
|
// if (roadLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
|
|
// // Road schema field is `ward`
|
|
// sub.definitionExpression = defExp.replace(/ward_number/gi, "ward");
|
|
// }
|
|
// sub.visible = true;
|
|
// });
|
|
// }
|
|
|
|
// // --- Asset group ---
|
|
// const assetGroup = map.layers.find(l => l.title === "GCC Asset Layers") as __esri.GroupLayer;
|
|
// if (assetGroup && "layers" in assetGroup) {
|
|
// assetGroup.visible = true;
|
|
// assetGroup.layers.forEach((sub: any) => {
|
|
// if (assetLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
|
|
// // Asset schema field is `ward_no`
|
|
// sub.definitionExpression = defExp.replace(/ward_number/gi, "ward_no");
|
|
// }
|
|
// sub.visible = true;
|
|
// });
|
|
// }
|
|
|
|
// const safeTitle = (t?: string | null) => (t ?? "").toString().trim();
|
|
// const lower = (s?: string) => safeTitle(s).toLowerCase();
|
|
|
|
// try {
|
|
// // Find Utility group (do NOT hide it by default) and ensure it's visible
|
|
// const utilityGroup = map.layers.find(
|
|
// (l: any) => safeTitle(l.title).toLowerCase().includes("utility")
|
|
// );
|
|
// if (utilityGroup) utilityGroup.visible = true;
|
|
|
|
// // Remove any previously added temporary FeatureLayer (we'll add a new one if needed)
|
|
// if (addedUtilityLayerRef.current) {
|
|
// try {
|
|
// map.layers.remove(addedUtilityLayerRef.current);
|
|
// } catch (e) { /* ignore */ }
|
|
// addedUtilityLayerRef.current = null;
|
|
// }
|
|
|
|
// // If "All" -> show all relevant children and apply defExp where possible
|
|
// if (selected === "" || lower(selected) === "all") {
|
|
// if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
|
|
// const children = utilityGroup.layers.toArray();
|
|
// for (const child of children) {
|
|
// try {
|
|
// if ("definitionExpression" in child) {
|
|
// try { child.definitionExpression = defExp; } catch (e) { /* ignore */ }
|
|
// }
|
|
// child.visible = true;
|
|
// } catch (e) { console.warn("Error enabling child for All", child, e); }
|
|
// }
|
|
// utilityGroup.visible = true;
|
|
// }
|
|
// return;
|
|
// }
|
|
|
|
// // 3) Find the utilityLayers entry and service URL for the selected utility
|
|
// const layerInfo = utilityLayers.find(u => lower(u.title) === lower(selected));
|
|
// if (!layerInfo) {
|
|
// console.warn("Selected utility not found in utilityLayers config:", selected);
|
|
// return;
|
|
// }
|
|
// const serviceUrl = `${UTILITY_GROUP_URL}/${layerInfo.id}`;
|
|
|
|
// // 4) Try to find an existing layer in the map whose .url matches the service url
|
|
// let foundLayer: any = null;
|
|
|
|
// const walkAndFindByUrl = (top: any) => {
|
|
// if (top?.url && top.url === serviceUrl) return top;
|
|
// if ("layers" in top && typeof top.layers?.toArray === "function") {
|
|
// for (const c of top.layers.toArray()) {
|
|
// if (c?.url && c.url === serviceUrl) return c;
|
|
// }
|
|
// }
|
|
// if ("sublayers" in top && typeof top.sublayers?.toArray === "function") {
|
|
// for (const s of top.sublayers.toArray()) {
|
|
// if (s?.url && s.url === serviceUrl) return s;
|
|
// }
|
|
// }
|
|
// return null;
|
|
// };
|
|
|
|
// for (const topLayer of map.layers.toArray()) {
|
|
// try {
|
|
// const match = walkAndFindByUrl(topLayer);
|
|
// if (match) { foundLayer = match; break; }
|
|
// } catch (e) { /* ignore per-layer errors */ }
|
|
// }
|
|
|
|
// // 5) If found in map, show and apply definitionExpression (best-case)
|
|
// // if (foundLayer) {
|
|
// // try {
|
|
// // // Make sure road & asset groups remain visible (don't hide them)
|
|
// // foundLayer.visible = true;
|
|
// // if ("definitionExpression" in foundLayer) {
|
|
// // try { foundLayer.definitionExpression = defExp; } catch (e) { /* ignore */ }
|
|
// // }
|
|
// // if (utilityGroup) utilityGroup.visible = true;
|
|
|
|
// // // Hide other utility children (optional) but keep ROAD/ASSET visible. We'll hide other utilities so user sees selected one clearly.
|
|
// // if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
|
|
// // const children = utilityGroup.layers.toArray();
|
|
// // for (const child of children) {
|
|
// // try {
|
|
// // if (child?.url && child.url === serviceUrl) {
|
|
// // child.visible = true;
|
|
// // if ("definitionExpression" in child) child.definitionExpression = defExp;
|
|
// // } else {
|
|
// // // hide other utility types (still keep groups for Road/Asset untouched)
|
|
// // try { child.visible = false; } catch (e) { /* ignore */ }
|
|
// // }
|
|
// // } catch (e) { /* ignore */ }
|
|
// // }
|
|
// // }
|
|
// // return;
|
|
// // } catch (err) {
|
|
// // console.warn("Error enabling existing layer:", err);
|
|
// // }
|
|
// // }
|
|
// if (foundLayer) {
|
|
// try {
|
|
// foundLayer.visible = true;
|
|
// const where = buildWhereClause(); // uses ward_number by design
|
|
// // apply to the found layer
|
|
// try { if (typeof foundLayer.definitionExpression !== 'undefined') foundLayer.definitionExpression = where; } catch(e){}
|
|
// // if it has sublayers (MapImageLayer), apply to sublayers too
|
|
// if (foundLayer.sublayers && typeof foundLayer.sublayers.toArray === 'function') {
|
|
// foundLayer.sublayers.toArray().forEach((sl: any) => {
|
|
// try { if (typeof sl.definitionExpression !== 'undefined') sl.definitionExpression = where; } catch(e){}
|
|
// });
|
|
// }
|
|
// if (utilityGroup) utilityGroup.visible = true;
|
|
|
|
// // Optionally hide other utility children so the selected one stands out (comment/uncomment)
|
|
// if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === "function") {
|
|
// const children = utilityGroup.layers.toArray();
|
|
// for (const child of children) {
|
|
// try {
|
|
// if (child?.url && child.url === serviceUrl) {
|
|
// child.visible = true;
|
|
// if (typeof child.definitionExpression !== 'undefined') child.definitionExpression = where;
|
|
// } else {
|
|
// // hide other utility children (if you want)
|
|
// try { child.visible = false; } catch(e) {}
|
|
// }
|
|
// } catch (e) { /* ignore */ }
|
|
// }
|
|
// }
|
|
// return;
|
|
// } catch (err) {
|
|
// console.warn("Error enabling existing layer:", err);
|
|
// }
|
|
// }
|
|
|
|
// // 6) If no matching layer found in map, add a new client FeatureLayer that points to the service endpoint.
|
|
// try {
|
|
// // const fl = new FeatureLayer({
|
|
// // url: serviceUrl,
|
|
// // title: `${selected} (temp)`,
|
|
// // visible: true,
|
|
// // definitionExpression: defExp
|
|
// // });
|
|
// // await fl.load();
|
|
// // map.layers.add(fl); // add top-level
|
|
// // addedUtilityLayerRef.current = fl;
|
|
// const fl = new FeatureLayer({
|
|
// url: serviceUrl,
|
|
// title: `${selected} (temp)`,
|
|
// visible: true
|
|
// });
|
|
// await fl.load();
|
|
// // apply the same ward-level where clause (buildWhereClause uses ward_number)
|
|
// try { fl.definitionExpression = buildWhereClause(); } catch(e) {}
|
|
// map.layers.add(fl);
|
|
// addedUtilityLayerRef.current = fl;
|
|
// if (utilityGroup) utilityGroup.visible = true;
|
|
// return;
|
|
// } catch (err) {
|
|
// console.error("Failed to add FeatureLayer for selected utility:", err);
|
|
// }
|
|
|
|
// } catch (err) {
|
|
// console.error("applyFilters error:", err);
|
|
// }
|
|
// };
|
|
|
|
// Replace your existing applyWardFilterToLayers with this implementation
|
|
// const applyWardFilterToLayers = async (wardValue: string, viewMap: __esri.Map) => {
|
|
// if (!viewMap) return;
|
|
|
|
// // Reset: show everything and clear any dynamically added utility layer
|
|
// if (!wardValue || wardValue === "All") {
|
|
// const topLayers: any[] = (viewMap.layers && typeof (viewMap.layers as any).toArray === "function")
|
|
// ? (viewMap.layers as any).toArray()
|
|
// : Array.from((viewMap.layers as any) || []);
|
|
|
|
// topLayers.forEach((layer: any) => {
|
|
// try {
|
|
// // Group layers with children
|
|
// if (layer.layers && typeof layer.layers.toArray === 'function') {
|
|
// const children = layer.layers.toArray();
|
|
// children.forEach((child: any) => {
|
|
// try {
|
|
// if (typeof child.definitionExpression !== 'undefined') {
|
|
// child.definitionExpression = "1=1";
|
|
// } else if (child.sublayers && typeof child.sublayers.toArray === 'function') {
|
|
// child.sublayers.toArray().forEach((sl: any) => {
|
|
// try { if (typeof sl.definitionExpression !== 'undefined') sl.definitionExpression = "1=1"; } catch(e) {}
|
|
// });
|
|
// }
|
|
// try { child.visible = true; } catch(e) {}
|
|
// } catch(e) {}
|
|
// });
|
|
// return;
|
|
// }
|
|
|
|
// // Top-level feature layers
|
|
// try { if (typeof (layer as any).definitionExpression !== 'undefined') (layer as any).definitionExpression = "1=1"; } catch(e) {}
|
|
// try { layer.visible = true; } catch(e) {}
|
|
// } catch(e) {}
|
|
// });
|
|
|
|
// // Remove any temporary utility layer we added before
|
|
// if (addedUtilityLayerRef.current) {
|
|
// try { viewMap.layers.remove(addedUtilityLayerRef.current); } catch(e) {}
|
|
// addedUtilityLayerRef.current = null;
|
|
// }
|
|
// return;
|
|
// }
|
|
|
|
// // Specific ward -> apply ward filter to each group/sub-layer.
|
|
// const wardFilter = `ward_number = '${safe(wardValue)}'`;
|
|
|
|
// const topLayers: any[] = (viewMap.layers && typeof (viewMap.layers as any).toArray === "function")
|
|
// ? (viewMap.layers as any).toArray()
|
|
// : Array.from((viewMap.layers as any) || []);
|
|
|
|
// for (const layer of topLayers) {
|
|
// try {
|
|
// // Transportation group: filter by ward (field name is ward)
|
|
// if ((layer.title || '').toString().toLowerCase().includes('transpor') && layer.layers && typeof layer.layers.toArray === 'function') {
|
|
// layer.visible = true;
|
|
// layer.layers.toArray().forEach((sub: any) => {
|
|
// try {
|
|
// if (typeof sub.definitionExpression !== 'undefined') {
|
|
// // Road layer uses 'ward' field in your schema
|
|
// sub.definitionExpression = wardFilter.replace(/ward_number/gi, 'ward');
|
|
// }
|
|
// sub.visible = true;
|
|
// } catch (e) {}
|
|
// });
|
|
// continue;
|
|
// }
|
|
|
|
// // Asset group: filter by ward_no (if required)
|
|
// if ((layer.title || '').toString().toLowerCase().includes('asset') && layer.layers && typeof layer.layers.toArray === 'function') {
|
|
// layer.visible = true;
|
|
// layer.layers.toArray().forEach((sub: any) => {
|
|
// try {
|
|
// if (typeof sub.definitionExpression !== 'undefined') {
|
|
// sub.definitionExpression = wardFilter.replace(/ward_number/gi, 'ward_no');
|
|
// }
|
|
// sub.visible = true;
|
|
// } catch (e) {}
|
|
// });
|
|
// continue;
|
|
// }
|
|
|
|
// // Utility group: apply ward_number filter to every child (they all use ward_number)
|
|
// if ((layer.title || '').toString().toLowerCase().includes('utility')) {
|
|
// layer.visible = true;
|
|
// // If the utility layer is a GroupLayer with .layers
|
|
// if (layer.layers && typeof layer.layers.toArray === 'function') {
|
|
// const children = layer.layers.toArray();
|
|
// for (const child of children) {
|
|
// try {
|
|
// if (typeof child.definitionExpression !== 'undefined') {
|
|
// // APPLY ward_number directly (you said all utility layers have ward_number)
|
|
// child.definitionExpression = wardFilter;
|
|
// } else if (child.sublayers && typeof child.sublayers.toArray === 'function') {
|
|
// // MapImageLayer sublayers
|
|
// child.sublayers.toArray().forEach((sl: any) => {
|
|
// try { if (typeof sl.definitionExpression !== 'undefined') sl.definitionExpression = wardFilter; } catch(e) {}
|
|
// });
|
|
// } else {
|
|
// // fallback: just make sure child is visible
|
|
// try { child.visible = true; } catch(e) {}
|
|
// }
|
|
// } catch (e) {
|
|
// console.warn('applyWardFilterToLayers: error applying to utility child', e);
|
|
// }
|
|
// }
|
|
// } else {
|
|
// // If the utility layer itself is a FeatureLayer
|
|
// try { if (typeof (layer as any).definitionExpression !== 'undefined') (layer as any).definitionExpression = wardFilter; } catch(e) {}
|
|
// try { layer.visible = true; } catch(e) {}
|
|
// }
|
|
// }
|
|
// } catch (err) {
|
|
// console.warn('applyWardFilterToLayers: loop error', err);
|
|
// }
|
|
// } // end for
|
|
// };
|
|
const applyWardFilterToLayers = async (wardValue: string, viewMap: __esri.Map) => {
|
|
if (!viewMap) return;
|
|
|
|
// Helper to safely apply ward filter recursively
|
|
const applyWardToLayer = (layer: any, filter: string) => {
|
|
try {
|
|
// Skip ZoneBoundary and WardBoundary layers
|
|
const title = (layer.title || '').toLowerCase();
|
|
if (title.includes('zoneboundary') || title.includes('wardboundary')) {
|
|
layer.visible = true;
|
|
return;
|
|
}
|
|
|
|
// FeatureLayer
|
|
if (layer.type === 'feature' && typeof layer.definitionExpression !== 'undefined') {
|
|
layer.definitionExpression = filter;
|
|
layer.visible = true;
|
|
}
|
|
// MapImageLayer or GroupLayer with sublayers
|
|
else if (layer.sublayers && typeof layer.sublayers.toArray === 'function') {
|
|
layer.sublayers.toArray().forEach((sl: any) => applyWardToLayer(sl, filter));
|
|
}
|
|
else if (layer.layers && typeof layer.layers.toArray === 'function') {
|
|
layer.layers.toArray().forEach((child: any) => applyWardToLayer(child, filter));
|
|
}
|
|
else {
|
|
layer.visible = true; // fallback
|
|
}
|
|
} catch (e) {
|
|
console.warn('applyWardToLayer error', e);
|
|
}
|
|
};
|
|
|
|
// Reset all layers if "All" is selected
|
|
if (!wardValue || wardValue === "All") {
|
|
const topLayers: any[] = (viewMap.layers && typeof (viewMap.layers as any).toArray === "function")
|
|
? (viewMap.layers as any).toArray()
|
|
: Array.from((viewMap.layers as any) || []);
|
|
|
|
topLayers.forEach((layer: any) => applyWardToLayer(layer, "1=1"));
|
|
|
|
if (addedUtilityLayerRef.current) {
|
|
try { viewMap.layers.remove(addedUtilityLayerRef.current); } catch(e) {}
|
|
addedUtilityLayerRef.current = null;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Specific ward filter
|
|
const wardFilter = `ward_number = '${safe(wardValue)}'`;
|
|
|
|
const topLayers: any[] = (viewMap.layers && typeof (viewMap.layers as any).toArray === "function")
|
|
? (viewMap.layers as any).toArray()
|
|
: Array.from((viewMap.layers as any) || []);
|
|
|
|
for (const layer of topLayers) {
|
|
try {
|
|
const title = (layer.title || '').toLowerCase();
|
|
|
|
// Skip zone/ward boundary layers
|
|
if (title.includes('zoneboundary') || title.includes('wardboundary')) continue;
|
|
|
|
// Transportation group: field is 'ward'
|
|
if (title.includes('transpor')) {
|
|
layer.visible = true;
|
|
if (layer.layers && typeof layer.layers.toArray === 'function') {
|
|
layer.layers.toArray().forEach((sub: any) => {
|
|
applyWardToLayer(sub, wardFilter.replace(/ward_number/gi, 'ward'));
|
|
});
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Asset group: field is 'ward_no'
|
|
if (title.includes('asset')) {
|
|
layer.visible = true;
|
|
if (layer.layers && typeof layer.layers.toArray === 'function') {
|
|
layer.layers.toArray().forEach((sub: any) => {
|
|
applyWardToLayer(sub, wardFilter.replace(/ward_number/gi, 'ward_no'));
|
|
});
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Utility group: all use 'ward_number'
|
|
if (title.includes('utility')) {
|
|
layer.visible = true;
|
|
applyWardToLayer(layer, wardFilter);
|
|
continue;
|
|
}
|
|
|
|
// Other top-level layers: fallback
|
|
applyWardToLayer(layer, wardFilter);
|
|
} catch (err) {
|
|
console.warn('applyWardFilterToLayers loop error', err);
|
|
}
|
|
}
|
|
};
|
|
|
|
// const applyWardFilterToLayers = async (wardValue: string, viewMap: __esri.Map) => {
|
|
// if (!wardValue || wardValue === "All") {
|
|
// // Reset → show all features in all layers
|
|
// viewMap.layers.forEach(layer => {
|
|
// if ("layers" in layer) {
|
|
// layer.layers.forEach((sub: any) => {
|
|
// if ("definitionExpression" in sub) {
|
|
// sub.definitionExpression = "1=1"; // show all features
|
|
// }
|
|
// sub.visible = true; // keep all visible
|
|
// });
|
|
// } else if ((layer as any).type === 'feature') {
|
|
// try { (layer as any).definitionExpression = "1=1"; } catch(e){}
|
|
// layer.visible = true;
|
|
// }
|
|
// });
|
|
|
|
// // Also reset any dynamically added utility layer
|
|
// if (addedUtilityLayerRef.current) {
|
|
// try {
|
|
// viewMap.layers.remove(addedUtilityLayerRef.current);
|
|
// } catch (e) { /* ignore */ }
|
|
// addedUtilityLayerRef.current = null;
|
|
// }
|
|
// return;
|
|
// }
|
|
|
|
// const roadLayerTitles = ["Road", "CarraigeWay"];
|
|
// const assetLayerTitles = ["Asset Point", "Asset Building"];
|
|
|
|
// // 🔹 Transportation group (filter by `ward`)
|
|
// const roadGroup = viewMap.layers.find(l => l.title === "Transporations") as __esri.GroupLayer;
|
|
// if (roadGroup) {
|
|
// roadGroup.visible = true;
|
|
// roadGroup.layers.forEach((sub: any) => {
|
|
// if (roadLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
|
|
// sub.definitionExpression = `ward = '${wardValue}'`; // ✅ correct field
|
|
// }
|
|
// sub.visible = true;
|
|
// });
|
|
// }
|
|
|
|
// // 🔹 Asset group (filter by `ward_no`)
|
|
// const assetGroup = viewMap.layers.find(l => l.title === "GCC Asset Layers") as __esri.GroupLayer;
|
|
// if (assetGroup) {
|
|
// assetGroup.visible = true;
|
|
// assetGroup.layers.forEach((sub: any) => {
|
|
// if (assetLayerTitles.includes(sub.title) && "definitionExpression" in sub) {
|
|
// sub.definitionExpression = `ward_no = '${wardValue}'`; // ✅ correct field
|
|
// }
|
|
// sub.visible = true;
|
|
// });
|
|
// }
|
|
|
|
// // 🔹 Utility group (filter utilities by ward_number) — ensure we DON'T hide the group
|
|
// const utilityGroup = viewMap.layers.find((l: any) => (l.title ?? '').toString().toLowerCase().includes('utility')) as __esri.GroupLayer;
|
|
// if (utilityGroup && "layers" in utilityGroup && typeof utilityGroup.layers?.toArray === 'function') {
|
|
// utilityGroup.visible = true;
|
|
// const children = utilityGroup.layers.toArray();
|
|
// for (const child of children) {
|
|
// try {
|
|
// if ("definitionExpression" in child) {
|
|
// // apply ward filter on utility children using ward_number
|
|
// child.definitionExpression = `ward_number = '${safe(wardValue)}'`;
|
|
// }
|
|
// // Keep utilities visible so user can see them filtered by ward
|
|
// child.visible = true;
|
|
// } catch (e) {
|
|
// console.warn('Error applying ward filter to utility child', child, e);
|
|
// }
|
|
// }
|
|
// }
|
|
// };
|
|
|
|
useEffect(() => {
|
|
if (jimuMapView?.view && selectedWard) {
|
|
applyWardFilterToLayers(selectedWard, jimuMapView.view.map);
|
|
updateWardBoundaryVisibility(selectedWard); // Add this line to update ward boundary visibility
|
|
}
|
|
}, [selectedWard, jimuMapView]);
|
|
|
|
// Apply filters when utility-related filters change
|
|
useEffect(() => {
|
|
if (jimuMapView && selectedLayer === "Utility") {
|
|
applyFilters();
|
|
}
|
|
}, [selectedZone, selectedWard, selectedUtilityLayer, selectedRoadName, jimuMapView]);
|
|
|
|
const handleSubmit = async (e: React.MouseEvent) => {
|
|
e.preventDefault();
|
|
setIsLoading(true);
|
|
|
|
if (!selectedZone) {
|
|
alert("Please select a Zone");
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
|
|
if (!jimuMapView?.view) {
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 1. Apply filters first
|
|
await applyFilters();
|
|
|
|
let zoomed = false;
|
|
|
|
// 2. Query features only when a single utility is selected
|
|
if (selectedLayer === "Utility" && selectedUtilityLayer && selectedUtilityLayer !== "All") {
|
|
const layerInfo = utilityLayers.find(l => l.title === selectedUtilityLayer);
|
|
if (layerInfo) {
|
|
const layer = new FeatureLayer({ url: `${UTILITY_GROUP_URL}/${layerInfo.id}` });
|
|
const query = layer.createQuery();
|
|
query.where = buildWhereClause();
|
|
query.outFields = ["*"];
|
|
query.returnGeometry = true;
|
|
|
|
const result = await layer.queryFeatures(query);
|
|
setTableData(result.features);
|
|
|
|
// Zoom to filtered features if any
|
|
if (result.features.length > 0) {
|
|
const extent = result.features.reduce(
|
|
(acc, f) => acc ? acc.union(f.geometry.extent) : f.geometry.extent,
|
|
null as __esri.Extent | null
|
|
);
|
|
if (extent) {
|
|
await jimuMapView.view.goTo({ target: extent, padding: 100, duration: 1000 });
|
|
zoomed = true;
|
|
}
|
|
}
|
|
}
|
|
} else if (selectedLayer === "Utility" && selectedUtilityLayer === "All") {
|
|
// Clear table when all utilities are shown
|
|
setTableData([]);
|
|
}
|
|
|
|
// 3. If nothing was zoomed yet, zoom to ward/zone
|
|
if (!zoomed) {
|
|
if (selectedWard && selectedWard !== "All") {
|
|
await zoomToWardGeometry(selectedWard);
|
|
} else if (selectedZone && selectedZone !== "All") {
|
|
await zoomToZoneGeometry(selectedZone);
|
|
}
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error("Error in handleSubmit:", error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
// Zoom to individual feature with highlight
|
|
const zoomToFeature = async (feature: any) => {
|
|
if (!jimuMapView?.view || !feature?.geometry) return;
|
|
const view = jimuMapView.view;
|
|
|
|
try {
|
|
// Clear previous highlight
|
|
if (highlightRef.current) {
|
|
highlightRef.current.remove();
|
|
highlightRef.current = null;
|
|
}
|
|
|
|
// Clear any existing graphics
|
|
if (graphicsLayerRef.current) {
|
|
view.graphics.remove(graphicsLayerRef.current);
|
|
}
|
|
|
|
// Create highlight symbol based on feature type
|
|
let highlightSymbol;
|
|
if (feature.geometry.type === "point") {
|
|
highlightSymbol = {
|
|
type: "simple-marker",
|
|
color: [255, 0, 0, 0.8],
|
|
size: 12,
|
|
outline: {
|
|
color: [255, 255, 255],
|
|
width: 2
|
|
}
|
|
};
|
|
} else if (feature.geometry.type === "polyline") {
|
|
highlightSymbol = new SimpleLineSymbol({
|
|
color: [255, 0, 0, 0.8],
|
|
width: 4
|
|
});
|
|
} else {
|
|
highlightSymbol = new SimpleFillSymbol({
|
|
color: [255, 0, 0, 0.3],
|
|
outline: {
|
|
color: [255, 0, 0],
|
|
width: 2
|
|
}
|
|
});
|
|
}
|
|
|
|
// Create highlight graphic
|
|
const highlightGraphic = new Graphic({
|
|
geometry: feature.geometry,
|
|
symbol: highlightSymbol
|
|
});
|
|
|
|
// Add to map and store reference
|
|
view.graphics.add(highlightGraphic);
|
|
graphicsLayerRef.current = highlightGraphic;
|
|
|
|
// Zoom to feature
|
|
await view.goTo({
|
|
target: feature.geometry,
|
|
zoom: feature.geometry.type === "point" ? 18 : 14,
|
|
duration: 1000
|
|
});
|
|
} catch (error) {
|
|
console.error("Error zooming to feature:", error);
|
|
}
|
|
};
|
|
|
|
// Reset button handler
|
|
const handleReset = () => {
|
|
setSelectedZone("");
|
|
setSelectedWard("");
|
|
setSelectedLayer("");
|
|
setSelectedUtilityLayer("All");
|
|
setSelectedRoadName("");
|
|
setUtilityCount(null);
|
|
setTableData([]);
|
|
setRoadNameOptions([]);
|
|
setActiveStep(0);
|
|
|
|
// Clear graphics & highlights
|
|
jimuMapView?.view?.graphics.removeAll();
|
|
highlightRef.current?.remove();
|
|
highlightRef.current = null;
|
|
|
|
if (jimuMapView?.view) {
|
|
const map = jimuMapView.view.map;
|
|
|
|
// Reset all utility layers and other groups
|
|
map.layers.forEach(layer => {
|
|
if (layer.type === "feature") {
|
|
try { (layer as __esri.FeatureLayer).definitionExpression = "1=1"; } catch(e){}
|
|
layer.visible = true;
|
|
} else if ("layers" in layer) {
|
|
layer.layers.forEach((sub: any) => {
|
|
sub.visible = true;
|
|
if (sub.type === "feature") try { sub.definitionExpression = "1=1"; } catch(e){}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Remove dynamically added utility layer
|
|
if (addedUtilityLayerRef.current) {
|
|
try { map.layers.remove(addedUtilityLayerRef.current); } catch(e){}
|
|
addedUtilityLayerRef.current = null;
|
|
}
|
|
|
|
// Restore GCC Asset Layers (and its subs)
|
|
const assetGroup = map.layers.find(l => l.title === "GCC Asset Layers");
|
|
if (assetGroup && "layers" in assetGroup) {
|
|
assetGroup.visible = true;
|
|
assetGroup.layers.forEach((sub: any) => sub.visible = true);
|
|
}
|
|
|
|
// Reset ward boundary layer to show all wards
|
|
const wardLayer = map.layers.find(layer =>
|
|
layer.url === WARD_LAYER_URL
|
|
) as FeatureLayer;
|
|
if (wardLayer) {
|
|
wardLayer.definitionExpression = "1=1";
|
|
wardLayer.visible = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Active view change handler
|
|
const activeViewChangeHandler = (jmv: JimuMapView) => {
|
|
if (jmv) setJimuMapView(jmv);
|
|
};
|
|
|
|
// Initialize map view
|
|
useEffect(() => {
|
|
const interval = setInterval(() => {
|
|
const view = mapViewManager.getMapView();
|
|
if (view) {
|
|
setJimuMapView(view);
|
|
clearInterval(interval);
|
|
}
|
|
}, 500);
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="property-widget-parent">
|
|
<div className="property-widget">
|
|
<JimuMapViewComponent
|
|
useMapWidgetId={props.useMapWidgetIds?.[0]}
|
|
onActiveViewChange={activeViewChangeHandler}
|
|
/>
|
|
|
|
<h3 className="widget-title">Utility Asset Filter</h3>
|
|
|
|
{/* Zone selection */}
|
|
<div className="property_form">
|
|
<label>Zone:<span style={{ color: 'red' }}>*</span></label>
|
|
<select
|
|
value={selectedZone}
|
|
onChange={e => {
|
|
const value = e.target.value;
|
|
setSelectedZone(value);
|
|
setSelectedWard("");
|
|
setSelectedRoadName("");
|
|
setRoadNameOptions([]);
|
|
if (value && value !== "All") {
|
|
zoomToZoneGeometry(value);
|
|
}
|
|
}}
|
|
required
|
|
>
|
|
<option value="">--Select Zone--</option>
|
|
{zoneOptions.map(zone => (
|
|
<option key={zone} value={zone}>{zone}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* Ward selection */}
|
|
<div className="property_form">
|
|
<label>Ward:</label>
|
|
<select
|
|
value={selectedWard}
|
|
onChange={e => {
|
|
const value = e.target.value;
|
|
setSelectedWard(value);
|
|
setSelectedRoadName("");
|
|
setRoadNameOptions([]);
|
|
|
|
if (value && value !== "All") {
|
|
zoomToWardGeometry(value);
|
|
if (jimuMapView?.view) {
|
|
applyWardFilterToLayers(value, jimuMapView.view.map);
|
|
updateWardBoundaryVisibility(value); // ✅ finish call here
|
|
}
|
|
} else {
|
|
// Reset when "All" is selected
|
|
if (jimuMapView?.view) {
|
|
applyWardFilterToLayers("All", jimuMapView.view.map);
|
|
updateWardBoundaryVisibility("All"); // ✅ reset visibility
|
|
}
|
|
}
|
|
}}
|
|
>
|
|
<option value="">--Select Ward--</option>
|
|
{wardOptions.map(ward => (
|
|
<option key={ward} value={ward}>{ward}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
|
|
{/* Layer selection */}
|
|
<div className="property_form">
|
|
<label>Select Layer:</label>
|
|
<select
|
|
value={selectedLayer}
|
|
onChange={e => {
|
|
const layer = e.target.value;
|
|
setSelectedLayer(layer);
|
|
setSelectedUtilityLayer("All");
|
|
setSelectedRoadName("");
|
|
setRoadNameOptions([]);
|
|
setUtilityCount(null);
|
|
}}
|
|
>
|
|
<option value="">--Select Layer--</option>
|
|
<option value="Utility">Utility</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Show filtering options when Utility is selected */}
|
|
{selectedLayer === "Utility" && (
|
|
<>
|
|
{/* Utility Layer selection */}
|
|
<div className="property_form">
|
|
<label>Select Utility Type:</label>
|
|
<select
|
|
value={selectedUtilityLayer}
|
|
onChange={e => {
|
|
const value = e.target.value;
|
|
setSelectedUtilityLayer(value);
|
|
setSelectedRoadName("");
|
|
if (value !== "All") {
|
|
populateRoadNames(value);
|
|
} else {
|
|
setRoadNameOptions([]);
|
|
}
|
|
if (jimuMapView?.view) {
|
|
const map = jimuMapView.view.map;
|
|
toggleUtilityLayerVisibility(map, value.toLowerCase()); // function from earlier
|
|
}
|
|
}}
|
|
disabled={!selectedZone || selectedZone === "All"}
|
|
>
|
|
<option value="All">All Utilities</option>
|
|
{utilityLayerOptions.map(name => (
|
|
<option key={name} value={name}>
|
|
{name}{selectedUtilityLayer === name && utilityCount !== null ? ` (${utilityCount})` : ""}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* Road Name selection */}
|
|
{selectedUtilityLayer !== "All" && roadNameOptions.length > 0 && (
|
|
<div className="property_form">
|
|
<label>Select Road Name:</label>
|
|
<select
|
|
value={selectedRoadName}
|
|
onChange={e => setSelectedRoadName(e.target.value)}
|
|
>
|
|
<option value="">--All Roads--</option>
|
|
{roadNameOptions.map(name => (
|
|
<option key={name} value={name}>{name}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{/* Action buttons */}
|
|
<div className="button-group">
|
|
<button
|
|
type="button"
|
|
className={`submit-button ${isLoading ? 'loading' : ''}`}
|
|
onClick={handleSubmit}
|
|
disabled={isLoading || !selectedZone || !selectedLayer}
|
|
>
|
|
{isLoading ? <div className="spinner"></div> : "Apply Filters"}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="reset-button"
|
|
onClick={handleReset}
|
|
>
|
|
Reset All
|
|
</button>
|
|
</div>
|
|
|
|
{/* Results table */}
|
|
{tableData.length > 0 && (
|
|
<div className="attribute-table">
|
|
<h4>Asset Details ({tableData.length} features)</h4>
|
|
<div className="table-container">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Zone</th>
|
|
<th>Ward</th>
|
|
<th>Road Name</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{tableData.map((feature, index) => {
|
|
const attr = feature.attributes;
|
|
return (
|
|
<tr key={feature.attributes.OBJECTID || index}>
|
|
<td>{attr.OBJECTID || index + 1}</td>
|
|
<td>{attr.zone_number || 'N/A'}</td>
|
|
<td>{attr.ward_number || 'N/A'}</td>
|
|
<td>{attr.road_name || 'N/A'}</td>
|
|
<td>
|
|
<button
|
|
onClick={() => zoomToFeature(feature)}
|
|
className="zoom-button"
|
|
title="Zoom to feature"
|
|
>
|
|
<img src={zoomInIcon} alt="Zoom" width={16} height={16} />
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{tableData.length === 0 && selectedLayer && (
|
|
<div className="no-results">
|
|
<p>No assets found with the current filters.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default RoadAssetFilter;
|