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

1717 lines
31 KiB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
  1. import React, { useEffect, useState } from "react";
  2. import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
  3. import {
  4. Box,
  5. Button,
  6. MenuItem,
  7. Paper,
  8. Select,
  9. Table,
  10. TableBody,
  11. TableCell,
  12. TableContainer,
  13. TableHead,
  14. TableRow,
  15. Typography
  16. } from "@mui/material";
  17. import {
  18. Download,
  19. FileText,
  20. RefreshCcw,
  21. BarChart3
  22. } from "lucide-react";
  23. import * as XLSX from "xlsx";
  24. import jsPDF from "jspdf";
  25. import "jspdf-autotable";
  26. import "./styles.css";
  27. /* =========================================================
  28. STATUS CONFIGURATION
  29. ========================================================= */
  30. const STATUS_MAP: Record<string, string> = {
  31. "01": "Active",
  32. "02": "Inactive"
  33. };
  34. /* =========================================================
  35. LAYER CONFIGURATION
  36. ========================================================= */
  37. const LAYER_CONFIG: Record<string, any> = {
  38. "Disposal Location": {
  39. url:
  40. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/3",
  41. zoneField: "zone",
  42. statusField: "status"
  43. },
  44. "NDRF": {
  45. url:
  46. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/7",
  47. zoneField: "zone_no"
  48. },
  49. "Police Station": {
  50. url:
  51. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/8",
  52. zoneField: "zone_no"
  53. },
  54. "Fire Station": {
  55. url:
  56. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/4",
  57. zoneField: "zone_no"
  58. },
  59. "CMWSSB": {
  60. url:
  61. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/2",
  62. zoneField: "zone_no"
  63. },
  64. "Amma Unavagam": {
  65. url:
  66. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/0",
  67. zoneField: "zone_no"
  68. },
  69. "SWD": {
  70. url:
  71. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/SWD_OLD/FeatureServer/0",
  72. zoneField: "zone",
  73. statusField: "operational_status"
  74. },
  75. "Motor Location": {
  76. url:
  77. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/6",
  78. zoneField: "zone",
  79. statusField: "operational_status"
  80. },
  81. "Boat Location": {
  82. url:
  83. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/1",
  84. zoneField: "zone",
  85. statusField: "operational_status"
  86. },
  87. "Cooking Center": {
  88. url:
  89. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/5",
  90. zoneField: "zone_no",
  91. statusField: "status"
  92. },
  93. "Relief Center": {
  94. url:
  95. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/9",
  96. zoneField: "zone",
  97. statusField: "status"
  98. },
  99. "Water Inundation Point": {
  100. url:
  101. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/VULNERABLE_INUNDATION/FeatureServer/1",
  102. zoneField: "zone",
  103. statusField: "operational_status"
  104. },
  105. "Water Inundation Line": {
  106. url:
  107. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/12",
  108. zoneField: "zone",
  109. statusField: "operational_status"
  110. },
  111. "Vulnerable Point": {
  112. url:
  113. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/VULNERABLE_INUNDATION/FeatureServer/0",
  114. zoneField: "new_zone",
  115. statusField: "operational_status"
  116. },
  117. "Vulnerable Line": {
  118. url:
  119. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/WaterInundationLayers/FeatureServer/11",
  120. zoneField: "new_zone",
  121. statusField: "operational_status"
  122. },
  123. "Sump": {
  124. url:
  125. "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Sump_and_Pump_Location/FeatureServer/0",
  126. zoneField: "zone",
  127. statusField: "operational_status"
  128. }
  129. };
  130. const LAYERS = Object.keys(LAYER_CONFIG);
  131. /* =========================================================
  132. TYPES
  133. ========================================================= */
  134. interface LayerStatus {
  135. Active: number;
  136. Inactive: number;
  137. Blank: number;
  138. Total: number;
  139. }
  140. interface ZoneSummary {
  141. zone: string;
  142. layers: Record<string, LayerStatus>;
  143. active: number;
  144. inactive: number;
  145. blank: number;
  146. total: number;
  147. }
  148. /* =========================================================
  149. EMPTY LAYER
  150. ========================================================= */
  151. const emptyLayerStatus = (): LayerStatus => ({
  152. Active: 0,
  153. Inactive: 0,
  154. Blank: 0,
  155. Total: 0
  156. });
  157. /* =========================================================
  158. EMPTY ZONE
  159. ========================================================= */
  160. const createZone = (zone: string): ZoneSummary => {
  161. const layers: Record<string, LayerStatus> = {};
  162. LAYERS.forEach(layer => {
  163. layers[layer] = emptyLayerStatus();
  164. });
  165. return {
  166. zone,
  167. layers,
  168. active: 0,
  169. inactive: 0,
  170. blank: 0,
  171. total: 0
  172. };
  173. };
  174. /* =========================================================
  175. COMPONENT
  176. ========================================================= */
  177. const ZoneSummaryWidget = () => {
  178. const [zoneOptions, setZoneOptions] =
  179. useState<string[]>([]);
  180. const [zoneFilter, setZoneFilter] =
  181. useState<string>("All");
  182. const [filteredData, setFilteredData] =
  183. useState<ZoneSummary[]>([]);
  184. const [loading, setLoading] =
  185. useState(false);
  186. const [overall, setOverall] = useState({
  187. totalZones: 0,
  188. totalAssets: 0,
  189. active: 0,
  190. inactive: 0,
  191. blank: 0,
  192. layerWise: {} as Record<
  193. string,
  194. LayerStatus
  195. >
  196. });
  197. /* =========================================================
  198. LOAD ZONES
  199. ========================================================= */
  200. useEffect(() => {
  201. loadZones();
  202. }, []);
  203. const loadZones = async () => {
  204. const zoneSet = new Set<string>();
  205. for (const layerName of LAYERS) {
  206. try {
  207. const config =
  208. LAYER_CONFIG[layerName];
  209. const layer =
  210. new FeatureLayer({
  211. url: config.url
  212. });
  213. const query =
  214. layer.createQuery();
  215. query.where = "1=1";
  216. query.outFields = [
  217. config.zoneField
  218. ];
  219. query.returnGeometry = false;
  220. query.returnDistinctValues = true;
  221. const result =
  222. await layer.queryFeatures(query);
  223. result.features.forEach(
  224. feature => {
  225. const value =
  226. feature.attributes[
  227. config.zoneField
  228. ];
  229. if (
  230. value !== null &&
  231. value !== undefined &&
  232. `${value}`.trim() !== ""
  233. ) {
  234. zoneSet.add(
  235. `${value}`
  236. );
  237. }
  238. }
  239. );
  240. } catch (error) {
  241. console.error(
  242. `Zone loading error - ${layerName}`,
  243. error
  244. );
  245. }
  246. }
  247. const zones =
  248. Array.from(zoneSet).sort(
  249. (a, b) =>
  250. Number(a) - Number(b)
  251. );
  252. setZoneOptions(zones);
  253. };
  254. /* =========================================================
  255. BUILD WHERE
  256. ========================================================= */
  257. const getWhere = (
  258. layerName: string
  259. ) => {
  260. const config =
  261. LAYER_CONFIG[layerName];
  262. if (
  263. zoneFilter === "All" ||
  264. !zoneFilter
  265. ) {
  266. return "1=1";
  267. }
  268. return `${config.zoneField} = '${zoneFilter.replace(
  269. /'/g,
  270. "''"
  271. )}'`;
  272. };
  273. /* =========================================================
  274. FETCH SUMMARY
  275. ========================================================= */
  276. const fetchSummary = async () => {
  277. setLoading(true);
  278. try {
  279. const zoneData:
  280. Record<string, ZoneSummary> = {};
  281. const createIfMissing = (
  282. zone: string
  283. ) => {
  284. if (!zoneData[zone]) {
  285. zoneData[zone] =
  286. createZone(zone);
  287. }
  288. };
  289. /* -----------------------------------------------------
  290. PROCESS EACH LAYER
  291. ----------------------------------------------------- */
  292. for (
  293. const layerName of LAYERS
  294. ) {
  295. const config =
  296. LAYER_CONFIG[layerName];
  297. try {
  298. const layer =
  299. new FeatureLayer({
  300. url: config.url
  301. });
  302. const query =
  303. layer.createQuery();
  304. query.where =
  305. getWhere(layerName);
  306. query.returnGeometry =
  307. false;
  308. /* =================================================
  309. STATUS LAYER
  310. ================================================= */
  311. if (config.statusField) {
  312. query.outFields = [
  313. config.zoneField,
  314. config.statusField
  315. ];
  316. query.outStatistics = [
  317. {
  318. statisticType: "count",
  319. onStatisticField: "OBJECTID",
  320. outStatisticFieldName: "feature_count"
  321. }
  322. ];
  323. query.groupByFieldsForStatistics = [
  324. config.zoneField,
  325. config.statusField
  326. ];
  327. const result =
  328. await layer.queryFeatures(query);
  329. result.features.forEach(
  330. feature => {
  331. const attributes =
  332. feature.attributes;
  333. const zoneValue =
  334. attributes[
  335. config.zoneField
  336. ];
  337. if (
  338. zoneValue === null ||
  339. zoneValue === undefined
  340. ) {
  341. return;
  342. }
  343. const zone =
  344. `${zoneValue}`;
  345. createIfMissing(zone);
  346. const count =
  347. Number(
  348. attributes.feature_count
  349. ) || 0;
  350. const rawStatus =
  351. attributes[
  352. config.statusField
  353. ];
  354. const status =
  355. `${rawStatus ?? ""}`.trim();
  356. let statusName:
  357. keyof LayerStatus =
  358. "Blank";
  359. /* Standard operational status */
  360. if (
  361. STATUS_MAP[status]
  362. ) {
  363. statusName =
  364. STATUS_MAP[
  365. status
  366. ] as keyof LayerStatus;
  367. }
  368. /* Yes / Newly Added */
  369. else if (
  370. status.toLowerCase() ===
  371. "yes" ||
  372. status.toLowerCase() ===
  373. "newly added"
  374. ) {
  375. statusName =
  376. "Active";
  377. }
  378. zoneData[zone]
  379. .layers[layerName]
  380. [statusName] += count;
  381. zoneData[zone]
  382. .layers[layerName]
  383. .Total += count;
  384. if (
  385. statusName === "Active"
  386. ) {
  387. zoneData[zone]
  388. .active += count;
  389. }
  390. else if (
  391. statusName === "Inactive"
  392. ) {
  393. zoneData[zone]
  394. .inactive += count;
  395. }
  396. else {
  397. zoneData[zone]
  398. .blank += count;
  399. }
  400. zoneData[zone]
  401. .total += count;
  402. }
  403. );
  404. }
  405. /* =================================================
  406. NORMAL TOTAL-ONLY LAYER
  407. ================================================= */
  408. else {
  409. query.outStatistics = [
  410. {
  411. statisticType: "count",
  412. onStatisticField: "OBJECTID",
  413. outStatisticFieldName: "feature_count"
  414. }
  415. ];
  416. query.groupByFieldsForStatistics = [
  417. config.zoneField
  418. ];
  419. const result =
  420. await layer.queryFeatures(query);
  421. result.features.forEach(
  422. feature => {
  423. const attributes =
  424. feature.attributes;
  425. const zoneValue =
  426. attributes[
  427. config.zoneField
  428. ];
  429. if (
  430. zoneValue === null ||
  431. zoneValue === undefined
  432. ) {
  433. return;
  434. }
  435. const zone =
  436. `${zoneValue}`;
  437. createIfMissing(zone);
  438. const count =
  439. Number(
  440. attributes.feature_count
  441. ) || 0;
  442. zoneData[zone]
  443. .layers[layerName]
  444. .Total += count;
  445. zoneData[zone]
  446. .total += count;
  447. }
  448. );
  449. }
  450. } catch (error) {
  451. console.error(
  452. `Error querying ${layerName}`,
  453. error
  454. );
  455. }
  456. }
  457. /* =====================================================
  458. SORT
  459. ===================================================== */
  460. const data =
  461. Object.values(zoneData).sort(
  462. (a, b) =>
  463. Number(a.zone) -
  464. Number(b.zone)
  465. );
  466. /* =====================================================
  467. OVERALL
  468. ===================================================== */
  469. const layerWise:
  470. Record<string, LayerStatus> = {};
  471. LAYERS.forEach(layer => {
  472. layerWise[layer] =
  473. emptyLayerStatus();
  474. });
  475. let totalAssets = 0;
  476. let active = 0;
  477. let inactive = 0;
  478. let blank = 0;
  479. data.forEach(zone => {
  480. totalAssets += zone.total;
  481. active += zone.active;
  482. inactive += zone.inactive;
  483. blank += zone.blank;
  484. LAYERS.forEach(layer => {
  485. layerWise[layer].Active +=
  486. zone.layers[layer].Active;
  487. layerWise[layer].Inactive +=
  488. zone.layers[layer].Inactive;
  489. layerWise[layer].Blank +=
  490. zone.layers[layer].Blank;
  491. layerWise[layer].Total +=
  492. zone.layers[layer].Total;
  493. });
  494. });
  495. setFilteredData(data);
  496. setOverall({
  497. totalZones:
  498. data.length,
  499. totalAssets,
  500. active,
  501. inactive,
  502. blank,
  503. layerWise
  504. });
  505. } catch (error) {
  506. console.error(
  507. "Summary error:",
  508. error
  509. );
  510. } finally {
  511. setLoading(false);
  512. }
  513. };
  514. /* =========================================================
  515. RESET
  516. ========================================================= */
  517. const resetDashboard = () => {
  518. setZoneFilter("All");
  519. setFilteredData([]);
  520. setOverall({
  521. totalZones: 0,
  522. totalAssets: 0,
  523. active: 0,
  524. inactive: 0,
  525. blank: 0,
  526. layerWise: {}
  527. });
  528. };
  529. /* =========================================================
  530. EXCEL
  531. ========================================================= */
  532. const exportExcel = () => {
  533. if (!filteredData.length) {
  534. return;
  535. }
  536. const rows: any[] = [];
  537. const header = [
  538. "Zone"
  539. ];
  540. LAYERS.forEach(layer => {
  541. header.push(
  542. `${layer} Active`
  543. );
  544. header.push(
  545. `${layer} Inactive`
  546. );
  547. header.push(
  548. `${layer} Blank`
  549. );
  550. header.push(
  551. `${layer} Total`
  552. );
  553. });
  554. header.push(
  555. "Active Total"
  556. );
  557. header.push(
  558. "Inactive Total"
  559. );
  560. header.push(
  561. "Blank Total"
  562. );
  563. header.push(
  564. "Zone Total"
  565. );
  566. rows.push(header);
  567. filteredData.forEach(zone => {
  568. const row: any[] = [
  569. zone.zone
  570. ];
  571. LAYERS.forEach(layer => {
  572. const data =
  573. zone.layers[layer];
  574. row.push(
  575. data.Active
  576. );
  577. row.push(
  578. data.Inactive
  579. );
  580. row.push(
  581. data.Blank
  582. );
  583. row.push(
  584. data.Total
  585. );
  586. });
  587. row.push(zone.active);
  588. row.push(zone.inactive);
  589. row.push(zone.blank);
  590. row.push(zone.total);
  591. rows.push(row);
  592. });
  593. const worksheet =
  594. XLSX.utils.aoa_to_sheet(rows);
  595. worksheet["!cols"] =
  596. header.map(() => ({
  597. wch: 16
  598. }));
  599. const workbook =
  600. XLSX.utils.book_new();
  601. XLSX.utils.book_append_sheet(
  602. workbook,
  603. worksheet,
  604. "Zone Summary"
  605. );
  606. XLSX.writeFile(
  607. workbook,
  608. `Zone_Wise_Summary_${zoneFilter}.xlsx`
  609. );
  610. };
  611. /* =========================================================
  612. PDF
  613. ========================================================= */
  614. const exportPDF = () => {
  615. if (!filteredData.length) {
  616. return;
  617. }
  618. const doc =
  619. new jsPDF(
  620. "landscape",
  621. "mm",
  622. "a3"
  623. );
  624. doc.setFontSize(18);
  625. doc.text(
  626. "Zone-wise Operational Summary",
  627. 15,
  628. 15
  629. );
  630. doc.setFontSize(9);
  631. doc.text(
  632. `Zone: ${zoneFilter}`,
  633. 15,
  634. 22
  635. );
  636. const head = [
  637. [
  638. "Zone",
  639. ...LAYERS.flatMap(
  640. layer => [
  641. `${layer} A`,
  642. `${layer} I`,
  643. `${layer} B`,
  644. `${layer} T`
  645. ]
  646. ),
  647. "Active",
  648. "Inactive",
  649. "Blank",
  650. "Total"
  651. ]
  652. ];
  653. const body =
  654. filteredData.map(
  655. zone => [
  656. zone.zone,
  657. ...LAYERS.flatMap(
  658. layer => {
  659. const data =
  660. zone.layers[layer];
  661. return [
  662. data.Active,
  663. data.Inactive,
  664. data.Blank,
  665. data.Total
  666. ];
  667. }
  668. ),
  669. zone.active,
  670. zone.inactive,
  671. zone.blank,
  672. zone.total
  673. ]
  674. );
  675. (doc as any).autoTable({
  676. head,
  677. body,
  678. startY: 28,
  679. theme: "grid",
  680. styles: {
  681. fontSize: 4,
  682. cellPadding: 1
  683. },
  684. headStyles: {
  685. fontSize: 4,
  686. fontStyle: "bold"
  687. }
  688. });
  689. doc.save(
  690. `Zone_Wise_Summary_${zoneFilter}.pdf`
  691. );
  692. };
  693. /* =========================================================
  694. RENDER
  695. ========================================================= */
  696. return (
  697. <div className="zone-dashboard">
  698. {/* =====================================================
  699. TOP HEADER
  700. ===================================================== */}
  701. <div className="dashboard-header">
  702. <div className="header-left">
  703. <div className="header-icon">
  704. <BarChart3 size={24} />
  705. </div>
  706. <div>
  707. <div className="dashboard-title">
  708. Zone-wise Summary
  709. </div>
  710. <div className="dashboard-subtitle">
  711. Operational Asset Monitoring
  712. </div>
  713. </div>
  714. </div>
  715. <div className="header-layer-count">
  716. {LAYERS.length} Layers
  717. </div>
  718. </div>
  719. {/* =====================================================
  720. FILTER BAR
  721. ===================================================== */}
  722. <div className="filter-card">
  723. <div className="filter-title">
  724. Zone Selection
  725. </div>
  726. <Select
  727. value={zoneFilter}
  728. onChange={(e) =>
  729. setZoneFilter(
  730. e.target.value
  731. )
  732. }
  733. className="modern-zone-select"
  734. size="small"
  735. >
  736. <MenuItem value="All">
  737. All Zones
  738. </MenuItem>
  739. {zoneOptions.map(
  740. zone => (
  741. <MenuItem
  742. key={zone}
  743. value={zone}
  744. >
  745. Zone {zone}
  746. </MenuItem>
  747. )
  748. )}
  749. </Select>
  750. <Button
  751. variant="contained"
  752. className="modern-fetch-button"
  753. onClick={fetchSummary}
  754. disabled={loading}
  755. startIcon={
  756. <BarChart3 size={17} />
  757. }
  758. >
  759. {loading
  760. ? "Loading..."
  761. : "View Summary"}
  762. </Button>
  763. <Button
  764. variant="outlined"
  765. className="modern-reset-button"
  766. onClick={resetDashboard}
  767. startIcon={
  768. <RefreshCcw size={16} />
  769. }
  770. >
  771. Reset
  772. </Button>
  773. <div className="export-area">
  774. <Button
  775. variant="outlined"
  776. className="excel-button"
  777. onClick={exportExcel}
  778. disabled={
  779. filteredData.length === 0
  780. }
  781. startIcon={
  782. <Download size={16} />
  783. }
  784. >
  785. Excel
  786. </Button>
  787. <Button
  788. variant="outlined"
  789. className="pdf-button"
  790. onClick={exportPDF}
  791. disabled={
  792. filteredData.length === 0
  793. }
  794. startIcon={
  795. <FileText size={16} />
  796. }
  797. >
  798. PDF
  799. </Button>
  800. </div>
  801. </div>
  802. {/* =====================================================
  803. KPI CARDS
  804. ===================================================== */}
  805. {filteredData.length > 0 && (
  806. <div className="kpi-grid">
  807. <div className="kpi-card kpi-zone">
  808. <div className="kpi-label">
  809. Zones
  810. </div>
  811. <div className="kpi-value">
  812. {overall.totalZones}
  813. </div>
  814. </div>
  815. <div className="kpi-card kpi-total">
  816. <div className="kpi-label">
  817. Total Assets
  818. </div>
  819. <div className="kpi-value">
  820. {overall.totalAssets.toLocaleString()}
  821. </div>
  822. </div>
  823. <div className="kpi-card kpi-active">
  824. <div className="kpi-label">
  825. Active
  826. </div>
  827. <div className="kpi-value">
  828. {overall.active.toLocaleString()}
  829. </div>
  830. </div>
  831. <div className="kpi-card kpi-inactive">
  832. <div className="kpi-label">
  833. Inactive
  834. </div>
  835. <div className="kpi-value">
  836. {overall.inactive.toLocaleString()}
  837. </div>
  838. </div>
  839. <div className="kpi-card kpi-blank">
  840. <div className="kpi-label">
  841. Blank
  842. </div>
  843. <div className="kpi-value">
  844. {overall.blank.toLocaleString()}
  845. </div>
  846. </div>
  847. </div>
  848. )}
  849. {/* =====================================================
  850. TABLE
  851. ===================================================== */}
  852. <div className="table-card">
  853. <div className="table-title-row">
  854. <div>
  855. <div className="table-title">
  856. Zone-wise Layer Count
  857. </div>
  858. <div className="table-subtitle">
  859. Active, inactive, blank and total
  860. feature counts
  861. </div>
  862. </div>
  863. <div className="record-count">
  864. {filteredData.length} Zones
  865. </div>
  866. </div>
  867. <TableContainer
  868. component={Paper}
  869. className="modern-table-container"
  870. >
  871. <Table
  872. stickyHeader
  873. className="modern-table"
  874. >
  875. <TableHead>
  876. <TableRow>
  877. <TableCell className="zone-header">
  878. Zone
  879. </TableCell>
  880. {LAYERS.map(
  881. layer => (
  882. <React.Fragment
  883. key={layer}
  884. >
  885. <TableCell className="active-header">
  886. {layer}
  887. <span>
  888. Active
  889. </span>
  890. </TableCell>
  891. <TableCell className="inactive-header">
  892. {layer}
  893. <span>
  894. Inactive
  895. </span>
  896. </TableCell>
  897. <TableCell className="blank-header">
  898. {layer}
  899. <span>
  900. Blank
  901. </span>
  902. </TableCell>
  903. <TableCell className="total-header">
  904. {layer}
  905. <span>
  906. Total
  907. </span>
  908. </TableCell>
  909. </React.Fragment>
  910. )
  911. )}
  912. <TableCell className="active-header">
  913. Active
  914. </TableCell>
  915. <TableCell className="inactive-header">
  916. Inactive
  917. </TableCell>
  918. <TableCell className="blank-header">
  919. Blank
  920. </TableCell>
  921. <TableCell className="total-header">
  922. Total
  923. </TableCell>
  924. </TableRow>
  925. </TableHead>
  926. <TableBody>
  927. {filteredData.map(
  928. zone => (
  929. <TableRow
  930. key={zone.zone}
  931. className="data-row"
  932. >
  933. <TableCell className="zone-value">
  934. <span className="zone-pill">
  935. {zone.zone}
  936. </span>
  937. </TableCell>
  938. {LAYERS.map(
  939. layer => {
  940. const data =
  941. zone.layers[layer];
  942. return (
  943. <React.Fragment
  944. key={layer}
  945. >
  946. <TableCell className="active-cell">
  947. {data.Active}
  948. </TableCell>
  949. <TableCell className="inactive-cell">
  950. {data.Inactive}
  951. </TableCell>
  952. <TableCell className="blank-cell">
  953. {data.Blank}
  954. </TableCell>
  955. <TableCell className="total-cell">
  956. {data.Total}
  957. </TableCell>
  958. </React.Fragment>
  959. );
  960. }
  961. )}
  962. <TableCell className="active-cell main-total">
  963. {zone.active}
  964. </TableCell>
  965. <TableCell className="inactive-cell main-total">
  966. {zone.inactive}
  967. </TableCell>
  968. <TableCell className="blank-cell main-total">
  969. {zone.blank}
  970. </TableCell>
  971. <TableCell className="total-cell main-total">
  972. {zone.total}
  973. </TableCell>
  974. </TableRow>
  975. )
  976. )}
  977. {filteredData.length === 0 && (
  978. <TableRow>
  979. <TableCell
  980. colSpan={
  981. 1 +
  982. LAYERS.length * 4 +
  983. 4
  984. }
  985. className="empty-table"
  986. >
  987. <div className="empty-icon">
  988. <BarChart3 size={32} />
  989. </div>
  990. <div>
  991. Select a zone and click
  992. <strong>
  993. {" "}View Summary
  994. </strong>
  995. </div>
  996. </TableCell>
  997. </TableRow>
  998. )}
  999. </TableBody>
  1000. </Table>
  1001. </TableContainer>
  1002. </div>
  1003. {/* =====================================================
  1004. OVERALL SUMMARY
  1005. ===================================================== */}
  1006. {filteredData.length > 0 && (
  1007. <div className="overall-card">
  1008. <div className="overall-header">
  1009. <div>
  1010. <div className="overall-title">
  1011. Overall Summary
  1012. </div>
  1013. <div className="overall-subtitle">
  1014. Consolidated layer-wise operational status
  1015. </div>
  1016. </div>
  1017. </div>
  1018. <div className="layer-summary-grid">
  1019. {LAYERS.map(
  1020. layer => {
  1021. const data =
  1022. overall.layerWise[
  1023. layer
  1024. ] || emptyLayerStatus();
  1025. return (
  1026. <div
  1027. className="layer-summary-card"
  1028. key={layer}
  1029. >
  1030. <div className="layer-card-title">
  1031. {layer}
  1032. </div>
  1033. <div className="layer-total">
  1034. {data.Total.toLocaleString()}
  1035. </div>
  1036. {LAYER_CONFIG[
  1037. layer
  1038. ].statusField && (
  1039. <div className="status-mini-row">
  1040. <span className="mini-active">
  1041. Active
  1042. <b>
  1043. {data.Active}
  1044. </b>
  1045. </span>
  1046. <span className="mini-inactive">
  1047. Inactive
  1048. <b>
  1049. {data.Inactive}
  1050. </b>
  1051. </span>
  1052. <span className="mini-blank">
  1053. Blank
  1054. <b>
  1055. {data.Blank}
  1056. </b>
  1057. </span>
  1058. </div>
  1059. )}
  1060. {!LAYER_CONFIG[
  1061. layer
  1062. ].statusField && (
  1063. <div className="total-only-label">
  1064. Total Features
  1065. </div>
  1066. )}
  1067. </div>
  1068. );
  1069. }
  1070. )}
  1071. </div>
  1072. </div>
  1073. )}
  1074. </div>
  1075. );
  1076. };
  1077. export default ZoneSummaryWidget;