import React, { useEffect, useState } from "react"; import FeatureLayer from "@arcgis/core/layers/FeatureLayer"; import { Box, Button, MenuItem, Paper, Select, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material"; import { Download, FileText, RefreshCcw, BarChart3 } from "lucide-react"; import * as XLSX from "xlsx"; import jsPDF from "jspdf"; import "jspdf-autotable"; import "./styles.css"; /* ========================================================= STATUS CONFIGURATION ========================================================= */ const STATUS_MAP: Record = { "01": "Active", "02": "Inactive" }; /* ========================================================= LAYER CONFIGURATION ========================================================= */ const LAYER_CONFIG: Record = { "Disposal Location": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/3", zoneField: "zone", statusField: "status" }, "NDRF": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/7", zoneField: "zone_no" }, "Police Station": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/8", zoneField: "zone_no" }, "Fire Station": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/4", zoneField: "zone_no" }, "CMWSSB": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/2", zoneField: "zone_no" }, "Amma Unavagam": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/0", zoneField: "zone_no" }, "SWD": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/SWD_OLD/FeatureServer/0", zoneField: "zone", statusField: "operational_status" }, "Motor Location": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/6", zoneField: "zone", statusField: "operational_status" }, "Boat Location": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/1", zoneField: "zone", statusField: "operational_status" }, "Cooking Center": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/5", zoneField: "zone_no", statusField: "status" }, "Relief Center": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/9", zoneField: "zone", statusField: "status" }, "Water Inundation Point": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/VULNERABLE_INUNDATION/FeatureServer/1", zoneField: "zone", statusField: "operational_status" }, "Water Inundation Line": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/12", zoneField: "zone", statusField: "operational_status" }, "Vulnerable Point": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/VULNERABLE_INUNDATION/FeatureServer/0", zoneField: "new_zone", statusField: "operational_status" }, "Vulnerable Line": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/11", zoneField: "new_zone", statusField: "operational_status" }, "Sump": { url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Sump_and_Pump_Location/FeatureServer/0", zoneField: "zone", statusField: "operational_status" } }; const LAYERS = Object.keys(LAYER_CONFIG); /* ========================================================= TYPES ========================================================= */ interface LayerStatus { Active: number; Inactive: number; Blank: number; Total: number; } interface ZoneSummary { zone: string; layers: Record; active: number; inactive: number; blank: number; total: number; } /* ========================================================= EMPTY LAYER ========================================================= */ const emptyLayerStatus = (): LayerStatus => ({ Active: 0, Inactive: 0, Blank: 0, Total: 0 }); /* ========================================================= EMPTY ZONE ========================================================= */ const createZone = (zone: string): ZoneSummary => { const layers: Record = {}; LAYERS.forEach(layer => { layers[layer] = emptyLayerStatus(); }); return { zone, layers, active: 0, inactive: 0, blank: 0, total: 0 }; }; /* ========================================================= COMPONENT ========================================================= */ const ZoneSummaryWidget = () => { const [zoneOptions, setZoneOptions] = useState([]); const [zoneFilter, setZoneFilter] = useState("All"); const [filteredData, setFilteredData] = useState([]); const [loading, setLoading] = useState(false); const [overall, setOverall] = useState({ totalZones: 0, totalAssets: 0, active: 0, inactive: 0, blank: 0, layerWise: {} as Record< string, LayerStatus > }); /* ========================================================= LOAD ZONES ========================================================= */ useEffect(() => { loadZones(); }, []); const loadZones = async () => { const zoneSet = new Set(); for (const layerName of LAYERS) { try { const config = LAYER_CONFIG[layerName]; const layer = new FeatureLayer({ url: config.url }); const query = layer.createQuery(); query.where = "1=1"; query.outFields = [ config.zoneField ]; query.returnGeometry = false; query.returnDistinctValues = true; const result = await layer.queryFeatures(query); result.features.forEach( feature => { const value = feature.attributes[ config.zoneField ]; if ( value !== null && value !== undefined && `${value}`.trim() !== "" ) { zoneSet.add( `${value}` ); } } ); } catch (error) { console.error( `Zone loading error - ${layerName}`, error ); } } const zones = Array.from(zoneSet).sort( (a, b) => Number(a) - Number(b) ); setZoneOptions(zones); }; /* ========================================================= BUILD WHERE ========================================================= */ const getWhere = ( layerName: string ) => { const config = LAYER_CONFIG[layerName]; if ( zoneFilter === "All" || !zoneFilter ) { return "1=1"; } return `${config.zoneField} = '${zoneFilter.replace( /'/g, "''" )}'`; }; /* ========================================================= FETCH SUMMARY ========================================================= */ const fetchSummary = async () => { setLoading(true); try { const zoneData: Record = {}; const createIfMissing = ( zone: string ) => { if (!zoneData[zone]) { zoneData[zone] = createZone(zone); } }; /* ----------------------------------------------------- PROCESS EACH LAYER ----------------------------------------------------- */ for ( const layerName of LAYERS ) { const config = LAYER_CONFIG[layerName]; try { const layer = new FeatureLayer({ url: config.url }); const query = layer.createQuery(); query.where = getWhere(layerName); query.returnGeometry = false; /* ================================================= STATUS LAYER ================================================= */ if (config.statusField) { query.outFields = [ config.zoneField, config.statusField ]; query.outStatistics = [ { statisticType: "count", onStatisticField: "OBJECTID", outStatisticFieldName: "feature_count" } ]; query.groupByFieldsForStatistics = [ config.zoneField, config.statusField ]; const result = await layer.queryFeatures(query); result.features.forEach( feature => { const attributes = feature.attributes; const zoneValue = attributes[ config.zoneField ]; if ( zoneValue === null || zoneValue === undefined ) { return; } const zone = `${zoneValue}`; createIfMissing(zone); const count = Number( attributes.feature_count ) || 0; const rawStatus = attributes[ config.statusField ]; const status = `${rawStatus ?? ""}`.trim(); let statusName: keyof LayerStatus = "Blank"; /* Standard operational status */ if ( STATUS_MAP[status] ) { statusName = STATUS_MAP[ status ] as keyof LayerStatus; } /* Yes / Newly Added */ else if ( status.toLowerCase() === "yes" || status.toLowerCase() === "newly added" ) { statusName = "Active"; } zoneData[zone] .layers[layerName] [statusName] += count; zoneData[zone] .layers[layerName] .Total += count; if ( statusName === "Active" ) { zoneData[zone] .active += count; } else if ( statusName === "Inactive" ) { zoneData[zone] .inactive += count; } else { zoneData[zone] .blank += count; } zoneData[zone] .total += count; } ); } /* ================================================= NORMAL TOTAL-ONLY LAYER ================================================= */ else { query.outStatistics = [ { statisticType: "count", onStatisticField: "OBJECTID", outStatisticFieldName: "feature_count" } ]; query.groupByFieldsForStatistics = [ config.zoneField ]; const result = await layer.queryFeatures(query); result.features.forEach( feature => { const attributes = feature.attributes; const zoneValue = attributes[ config.zoneField ]; if ( zoneValue === null || zoneValue === undefined ) { return; } const zone = `${zoneValue}`; createIfMissing(zone); const count = Number( attributes.feature_count ) || 0; zoneData[zone] .layers[layerName] .Total += count; zoneData[zone] .total += count; } ); } } catch (error) { console.error( `Error querying ${layerName}`, error ); } } /* ===================================================== SORT ===================================================== */ const data = Object.values(zoneData).sort( (a, b) => Number(a.zone) - Number(b.zone) ); /* ===================================================== OVERALL ===================================================== */ const layerWise: Record = {}; LAYERS.forEach(layer => { layerWise[layer] = emptyLayerStatus(); }); let totalAssets = 0; let active = 0; let inactive = 0; let blank = 0; data.forEach(zone => { totalAssets += zone.total; active += zone.active; inactive += zone.inactive; blank += zone.blank; LAYERS.forEach(layer => { layerWise[layer].Active += zone.layers[layer].Active; layerWise[layer].Inactive += zone.layers[layer].Inactive; layerWise[layer].Blank += zone.layers[layer].Blank; layerWise[layer].Total += zone.layers[layer].Total; }); }); setFilteredData(data); setOverall({ totalZones: data.length, totalAssets, active, inactive, blank, layerWise }); } catch (error) { console.error( "Summary error:", error ); } finally { setLoading(false); } }; /* ========================================================= RESET ========================================================= */ const resetDashboard = () => { setZoneFilter("All"); setFilteredData([]); setOverall({ totalZones: 0, totalAssets: 0, active: 0, inactive: 0, blank: 0, layerWise: {} }); }; /* ========================================================= EXCEL ========================================================= */ const exportExcel = () => { if (!filteredData.length) { return; } const rows: any[] = []; const header = [ "Zone" ]; LAYERS.forEach(layer => { header.push( `${layer} Active` ); header.push( `${layer} Inactive` ); header.push( `${layer} Blank` ); header.push( `${layer} Total` ); }); header.push( "Active Total" ); header.push( "Inactive Total" ); header.push( "Blank Total" ); header.push( "Zone Total" ); rows.push(header); filteredData.forEach(zone => { const row: any[] = [ zone.zone ]; LAYERS.forEach(layer => { const data = zone.layers[layer]; row.push( data.Active ); row.push( data.Inactive ); row.push( data.Blank ); row.push( data.Total ); }); row.push(zone.active); row.push(zone.inactive); row.push(zone.blank); row.push(zone.total); rows.push(row); }); const worksheet = XLSX.utils.aoa_to_sheet(rows); worksheet["!cols"] = header.map(() => ({ wch: 16 })); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet( workbook, worksheet, "Zone Summary" ); XLSX.writeFile( workbook, `Zone_Wise_Summary_${zoneFilter}.xlsx` ); }; /* ========================================================= PDF ========================================================= */ const exportPDF = () => { if (!filteredData.length) { return; } const doc = new jsPDF( "landscape", "mm", "a3" ); doc.setFontSize(18); doc.text( "Zone-wise Operational Summary", 15, 15 ); doc.setFontSize(9); doc.text( `Zone: ${zoneFilter}`, 15, 22 ); const head = [ [ "Zone", ...LAYERS.flatMap( layer => [ `${layer} A`, `${layer} I`, `${layer} B`, `${layer} T` ] ), "Active", "Inactive", "Blank", "Total" ] ]; const body = filteredData.map( zone => [ zone.zone, ...LAYERS.flatMap( layer => { const data = zone.layers[layer]; return [ data.Active, data.Inactive, data.Blank, data.Total ]; } ), zone.active, zone.inactive, zone.blank, zone.total ] ); (doc as any).autoTable({ head, body, startY: 28, theme: "grid", styles: { fontSize: 4, cellPadding: 1 }, headStyles: { fontSize: 4, fontStyle: "bold" } }); doc.save( `Zone_Wise_Summary_${zoneFilter}.pdf` ); }; /* ========================================================= RENDER ========================================================= */ return (
{/* ===================================================== TOP HEADER ===================================================== */}
Zone-wise Summary
Operational Asset Monitoring
{LAYERS.length} Layers
{/* ===================================================== FILTER BAR ===================================================== */}
Zone Selection
{/* ===================================================== KPI CARDS ===================================================== */} {filteredData.length > 0 && (
Zones
{overall.totalZones}
Total Assets
{overall.totalAssets.toLocaleString()}
Active
{overall.active.toLocaleString()}
Inactive
{overall.inactive.toLocaleString()}
Blank
{overall.blank.toLocaleString()}
)} {/* ===================================================== TABLE ===================================================== */}
Zone-wise Layer Count
Active, inactive, blank and total feature counts
{filteredData.length} Zones
Zone {LAYERS.map( layer => ( {layer} Active {layer} Inactive {layer} Blank {layer} Total ) )} Active Inactive Blank Total {filteredData.map( zone => ( {zone.zone} {LAYERS.map( layer => { const data = zone.layers[layer]; return ( {data.Active} {data.Inactive} {data.Blank} {data.Total} ); } )} {zone.active} {zone.inactive} {zone.blank} {zone.total} ) )} {filteredData.length === 0 && (
Select a zone and click {" "}View Summary
)}
{/* ===================================================== OVERALL SUMMARY ===================================================== */} {filteredData.length > 0 && (
Overall Summary
Consolidated layer-wise operational status
{LAYERS.map( layer => { const data = overall.layerWise[ layer ] || emptyLayerStatus(); return (
{layer}
{data.Total.toLocaleString()}
{LAYER_CONFIG[ layer ].statusField && (
Active {data.Active} Inactive {data.Inactive} Blank {data.Blank}
)} {!LAYER_CONFIG[ layer ].statusField && (
Total Features
)}
); } )}
)}
); }; export default ZoneSummaryWidget;