Browse Source

Add all custom widgets

custommwidgets
Anitha_M 4 weeks ago
parent
commit
a6f3f61620
  1. 3
      AssetUpdation_Remarks/config.json
  2. 12
      AssetUpdation_Remarks/icon.svg
  3. 29
      AssetUpdation_Remarks/manifest.json
  4. 7
      AssetUpdation_Remarks/src/config.ts
  5. 637
      AssetUpdation_Remarks/src/index.tsx
  6. 8
      AssetUpdation_Remarks/src/runtime/widget.tsx
  7. 24
      AssetUpdation_Remarks/src/styles.css
  8. 14
      AssetUpdation_Remarks/tests/simple-widget.test.tsx
  9. 5
      Asset_AC_Filter/config.json
  10. 25
      Asset_AC_Filter/dist/runtime/assets/zoom-in.svg
  11. 2
      Asset_AC_Filter/dist/runtime/widget.js
  12. 311
      Asset_AC_Filter/dist/runtime/widget.js.LICENSE.txt
  13. 1
      Asset_AC_Filter/dist/setting/setting.js
  14. 25
      Asset_AC_Filter/icon.svg
  15. 19
      Asset_AC_Filter/manifest.json
  16. 7
      Asset_AC_Filter/src/config.ts
  17. 25
      Asset_AC_Filter/src/runtime/assets/zoom-in.svg
  18. 45
      Asset_AC_Filter/src/runtime/components/LoadingPopup.css
  19. 23
      Asset_AC_Filter/src/runtime/components/LoadingPopup.tsx
  20. 23
      Asset_AC_Filter/src/runtime/components/LoadingSpinner.jsx
  21. 123
      Asset_AC_Filter/src/runtime/simple_widget.css
  22. 1331
      Asset_AC_Filter/src/runtime/widget.tsx
  23. 20
      Asset_AC_Filter/src/setting/setting.tsx
  24. BIN
      Asset_AC_Filter/src/zoom-in.png
  25. 14
      Asset_AC_Filter/tests/simple-widget.test.tsx
  26. 1
      Asset_Remarks_GIS/config.json
  27. 47
      Asset_Remarks_GIS/icon.svg
  28. 19
      Asset_Remarks_GIS/manifest.json
  29. 9
      Asset_Remarks_GIS/src/config.ts
  30. 204
      Asset_Remarks_GIS/src/runtime/components/BufferQueryComponent.tsx
  31. 150
      Asset_Remarks_GIS/src/runtime/components/FieldExpressions.tsx
  32. 9
      Asset_Remarks_GIS/src/runtime/style.css
  33. 811
      Asset_Remarks_GIS/src/runtime/widget.tsx
  34. 72
      Asset_Remarks_GIS/src/setting/setting.tsx
  35. 3
      Asset_Road_Summary/config.json
  36. 9
      Asset_Road_Summary/icon.svg
  37. 29
      Asset_Road_Summary/manifest.json
  38. 7
      Asset_Road_Summary/src/config.ts
  39. 317
      Asset_Road_Summary/src/index.tsx
  40. 8
      Asset_Road_Summary/src/runtime/widget.tsx
  41. 24
      Asset_Road_Summary/src/styles.css
  42. 14
      Asset_Road_Summary/tests/simple-widget.test.tsx
  43. 3
      Asset_Road_Summary_view/config.json
  44. 9
      Asset_Road_Summary_view/icon.svg
  45. 29
      Asset_Road_Summary_view/manifest.json
  46. 7
      Asset_Road_Summary_view/src/config.ts
  47. 242
      Asset_Road_Summary_view/src/index.tsx
  48. 8
      Asset_Road_Summary_view/src/runtime/widget.tsx
  49. 24
      Asset_Road_Summary_view/src/styles.css
  50. 14
      Asset_Road_Summary_view/tests/simple-widget.test.tsx
  51. 5
      Asset_SWD_Filter/config.json
  52. 25
      Asset_SWD_Filter/dist/runtime/assets/zoom-in.svg
  53. 2
      Asset_SWD_Filter/dist/runtime/widget.js
  54. 311
      Asset_SWD_Filter/dist/runtime/widget.js.LICENSE.txt
  55. 1
      Asset_SWD_Filter/dist/setting/setting.js
  56. 3
      Asset_SWD_Filter/icon.svg
  57. 19
      Asset_SWD_Filter/manifest.json
  58. 7
      Asset_SWD_Filter/src/config.ts
  59. 25
      Asset_SWD_Filter/src/runtime/assets/zoom-in.svg
  60. 45
      Asset_SWD_Filter/src/runtime/components/LoadingPopup.css
  61. 23
      Asset_SWD_Filter/src/runtime/components/LoadingPopup.tsx
  62. 23
      Asset_SWD_Filter/src/runtime/components/LoadingSpinner.jsx
  63. 123
      Asset_SWD_Filter/src/runtime/simple_widget.css
  64. 1274
      Asset_SWD_Filter/src/runtime/widget.tsx
  65. 20
      Asset_SWD_Filter/src/setting/setting.tsx
  66. BIN
      Asset_SWD_Filter/src/zoom-in.png
  67. 14
      Asset_SWD_Filter/tests/simple-widget.test.tsx
  68. 3
      Asset_Summary/config.json
  69. 4
      Asset_Summary/icon.svg
  70. 29
      Asset_Summary/manifest.json
  71. 7
      Asset_Summary/src/config.ts
  72. 41
      Asset_Summary/src/index.tsx
  73. 151
      Asset_Summary/src/runtime/widget.tsx
  74. 61
      Asset_Summary/src/styles.css
  75. 14
      Asset_Summary/tests/simple-widget.test.tsx
  76. 3
      Asset_Updation_Attachment_status/config.json
  77. 2
      Asset_Updation_Attachment_status/icon.svg
  78. 29
      Asset_Updation_Attachment_status/manifest.json
  79. 7
      Asset_Updation_Attachment_status/src/config.ts
  80. 898
      Asset_Updation_Attachment_status/src/index.tsx
  81. 8
      Asset_Updation_Attachment_status/src/runtime/widget.tsx
  82. 24
      Asset_Updation_Attachment_status/src/styles.css
  83. 14
      Asset_Updation_Attachment_status/tests/simple-widget.test.tsx
  84. 3
      Asset_Updation_Summary/config.json
  85. 26
      Asset_Updation_Summary/icon.svg
  86. 29
      Asset_Updation_Summary/manifest.json
  87. 7
      Asset_Updation_Summary/src/config.ts
  88. 381
      Asset_Updation_Summary/src/index.tsx
  89. 8
      Asset_Updation_Summary/src/runtime/widget.tsx
  90. 24
      Asset_Updation_Summary/src/styles.css
  91. 14
      Asset_Updation_Summary/tests/simple-widget.test.tsx
  92. 3
      Attachment_ShopComplex/config.json
  93. 2
      Attachment_ShopComplex/icon.svg
  94. 29
      Attachment_ShopComplex/manifest.json
  95. 7
      Attachment_ShopComplex/src/config.ts
  96. 482
      Attachment_ShopComplex/src/index.tsx
  97. 8
      Attachment_ShopComplex/src/runtime/widget.tsx
  98. 24
      Attachment_ShopComplex/src/styles.css
  99. 14
      Attachment_ShopComplex/tests/simple-widget.test.tsx
  100. 5
      Attribute_Transfer/config.json

3
AssetUpdation_Remarks/config.json

@ -0,0 +1,3 @@
{
"exampleConfigProperty": "test"
}

12
AssetUpdation_Remarks/icon.svg

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>upload-document-note</title>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="icon" fill="#000000" transform="translate(64.000000, 42.666667)">
<path d="M298.642975,268.433404 L392.812864,362.603293 L362.642975,392.773183 L319.976085,350.117404 L319.976308,447.956959 L277.309641,447.956959 L277.309085,350.116404 L234.642975,392.773183 L204.473085,362.603293 L298.642975,268.433404 Z M234.666667,7.10542736e-15 L341.333333,106.666667 L341.333,280.469 L298.779055,237.915875 L298.666,238.028 L298.666667,124.339779 L216.993555,42.6666667 L42.6666667,42.6666667 L42.6666667,384 L196.184,384 L234.773299,422.589676 L256.093414,401.26939 L256.101,426.666 L1.42108547e-14,426.666667 L1.42108547e-14,7.10542736e-15 L234.666667,7.10542736e-15 Z M213.333333,298.666667 L213.333,323.361 L195.361,341.333 L64,341.333333 L64,298.666667 L213.333333,298.666667 Z M196,85.3333333 L256,145.333333 L124,277.333333 L64,277.333333 L64,217.333333 L196,85.3333333 Z M195.989333,130.581333 L96,230.570667 L96,245.333333 L110.72,245.333333 L210.730667,145.322667 L195.989333,130.581333 Z" id="Combined-Shape">
</path>
</g>
</g>
</svg>

29
AssetUpdation_Remarks/manifest.json

@ -0,0 +1,29 @@
{
"name": "AssetUpdation_Remarks",
"label": "AssetUpdation_Remarks",
"type": "widget",
"version": "1.16.0",
"exbVersion": "1.16.0",
"author": "Esri R&D Center Beijing",
"description": "This is a barebones, simple widget. It includes an example of how to use the config file, and it includes a placeholder SVG icon. It intentionally does not include the following so you can use this to start from scratch: i18n translations, a Settings panel, unit tests. See the documentation for more information on how to implement these features.",
"copyright": "",
"license": "http://www.apache.org/licenses/LICENSE-2.0",
"properties": {
"hasSettingPage": false
},
"widgets": [
{
"uri": "AssetUpdation_Remarks",
"label": "AssetUpdation_Remarks",
"thumbnail": "thumbnail.png",
"description": "Summarizes Asset and Road."
}
],
"framework": "react",
"translatedLocales": ["en"],
"dataSources": [],
"elements": []
}

7
AssetUpdation_Remarks/src/config.ts

@ -0,0 +1,7 @@
import { type ImmutableObject } from 'seamless-immutable'
export interface Config {
exampleConfigProperty: string
}
export type IMConfig = ImmutableObject<Config>

637
AssetUpdation_Remarks/src/index.tsx

@ -0,0 +1,637 @@
import React, { useEffect, useMemo, useState, useCallback, useRef } from "react";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Typography,
Grid,
Button,
FormControl,
InputLabel,
Select,
MenuItem,
CircularProgress,
Alert,
} from "@mui/material";
import { saveAs } from "file-saver";
import jsPDF from "jspdf";
import "jspdf-autotable";
// ===================== CONFIGURATION =====================
const SUMMARY_LAYER_URL =
"https://gisgcc.chennaicorporation.gov.in/server/rest/services/gccdeptfield/Summary_Report/FeatureServer/1";
const DETAIL_LAYER_URL =
"https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Asset_2026_final/FeatureServer/0";
const PAGE_SIZE = 2000;
// ===================== HELPERS =====================
const normalizeText = (value: unknown): string => {
if (value === null || value === undefined) return "";
return String(value).trim();
};
const normalizeCategory = (value: unknown): string => {
const v = normalizeText(value);
return v === "" ? "" : v.toUpperCase();
};
const normalizeZone = (value: unknown): string => {
const v = normalizeText(value);
if (v === "") return "";
const num = parseInt(v, 10);
if (!Number.isNaN(num)) return num.toString().padStart(2, "0");
return v.toUpperCase();
};
const normalizeWard = (value: unknown): string => {
const v = normalizeText(value);
if (v === "") return "";
const num = parseInt(v, 10);
if (!Number.isNaN(num)) return num.toString().padStart(3, "0");
return v.toUpperCase();
};
const sortAlpha = (values: string[]) =>
[...values].sort((a, b) =>
a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" })
);
const sortNumericLikeStrings = (values: string[]) =>
[...values].sort((a, b) => {
const na = parseInt(a, 10);
const nb = parseInt(b, 10);
const aNum = Number.isNaN(na) ? Number.MAX_SAFE_INTEGER : na;
const bNum = Number.isNaN(nb) ? Number.MAX_SAFE_INTEGER : nb;
if (aNum !== bNum) return aNum - bNum;
return a.localeCompare(b);
});
const joinUniqueRemark = (existing: string, next: string): string => {
const a = normalizeText(existing);
const b = normalizeText(next);
if (!a) return b;
if (!b) return a;
if (a.split(" | ").includes(b)) return a;
return `${a} | ${b}`;
};
const buildWhere = (zone?: string, ward?: string): string => {
const clauses: string[] = ["1=1"];
if (zone) clauses.push(`zone = '${zone.replace(/'/g, "''")}'`);
if (ward) clauses.push(`ward = '${ward.replace(/'/g, "''")}'`);
return clauses.join(" AND ");
};
type CachedZoneData = {
detailFeatures: any[];
summaryFeatures: any[];
};
const CategoryWardSummary: React.FC = () => {
const [detailLayerFields, setDetailLayerFields] = useState<any[]>([]);
const [zoneOptions, setZoneOptions] = useState<string[]>([]);
const [allCategories, setAllCategories] = useState<string[]>([]);
const [selectedZone, setSelectedZone] = useState<string>("");
const [selectedWard, setSelectedWard] = useState<string>("");
const [zoneData, setZoneData] = useState<CachedZoneData>({
detailFeatures: [],
summaryFeatures: [],
});
const [loading, setLoading] = useState<boolean>(false);
const [metaLoading, setMetaLoading] = useState<boolean>(false);
const [error, setError] = useState<string>("");
const cacheRef = useRef<Map<string, CachedZoneData>>(new Map());
const fetchAllFeaturesPaged = async (featureLayer: FeatureLayer, baseQuery: any) => {
const all: any[] = [];
let start = 0;
while (true) {
const q = featureLayer.createQuery();
Object.assign(q, baseQuery);
q.start = start;
q.num = PAGE_SIZE;
const result = await featureLayer.queryFeatures(q);
if (!result || !result.features) break;
all.push(...result.features);
if (result.features.length < PAGE_SIZE) break;
start += PAGE_SIZE;
}
return all;
};
// Load only metadata + category domain + distinct zone list
const loadMetadata = useCallback(async () => {
setMetaLoading(true);
setError("");
try {
const detailLayer = new FeatureLayer({ url: DETAIL_LAYER_URL });
const summaryLayer = new FeatureLayer({ url: SUMMARY_LAYER_URL });
await Promise.all([detailLayer.load(), summaryLayer.load()]);
setDetailLayerFields(detailLayer.fields || []);
// Categories from the asset layer domain
const categoryField = detailLayer.fields?.find(
(f) => String(f.name).toLowerCase() === "category"
);
const domainCategories: string[] = [];
const codedValues = categoryField?.domain?.codedValues || [];
codedValues.forEach((cv: any) => {
const value = normalizeCategory(cv?.code ?? cv?.name);
if (value) domainCategories.push(value);
});
setAllCategories(sortAlpha(Array.from(new Set(domainCategories))));
// Distinct zones from asset layer
const zoneQuery = detailLayer.createQuery();
zoneQuery.where = "1=1";
zoneQuery.returnGeometry = false;
zoneQuery.outFields = ["zone"];
zoneQuery.returnDistinctValues = true;
const zoneResult = await detailLayer.queryFeatures(zoneQuery);
const zones = Array.from(
new Set(
(zoneResult.features || [])
.map((f: any) => normalizeZone(f.attributes?.zone))
.filter(Boolean)
)
);
setZoneOptions(sortNumericLikeStrings(zones));
} catch (err) {
console.error("Metadata load error:", err);
setError("Failed to load metadata.");
} finally {
setMetaLoading(false);
}
}, []);
useEffect(() => {
loadMetadata();
}, [loadMetadata]);
// Load only selected zone data
const loadZoneData = useCallback(async (zone: string) => {
if (!zone) {
setZoneData({ detailFeatures: [], summaryFeatures: [] });
return;
}
const cached = cacheRef.current.get(zone);
if (cached) {
setZoneData(cached);
return;
}
setLoading(true);
setError("");
try {
const summaryLayer = new FeatureLayer({ url: SUMMARY_LAYER_URL });
const detailLayer = new FeatureLayer({ url: DETAIL_LAYER_URL });
const summaryQuery = summaryLayer.createQuery();
summaryQuery.where = buildWhere(zone);
summaryQuery.returnGeometry = false;
summaryQuery.outFields = ["zone", "category", "remarks_by_gis_expert"];
if (summaryLayer.objectIdField) {
summaryQuery.orderByFields = [summaryLayer.objectIdField];
}
const detailQuery = detailLayer.createQuery();
detailQuery.where = buildWhere(zone);
detailQuery.returnGeometry = false;
detailQuery.outFields = ["zone", "ward", "category"];
if (detailLayer.objectIdField) {
detailQuery.orderByFields = [detailLayer.objectIdField];
}
const [summaryResult, detailResult] = await Promise.all([
fetchAllFeaturesPaged(summaryLayer, summaryQuery),
fetchAllFeaturesPaged(detailLayer, detailQuery),
]);
const payload = {
detailFeatures: detailResult,
summaryFeatures: summaryResult,
};
cacheRef.current.set(zone, payload);
setZoneData(payload);
} catch (err) {
console.error("Zone load error:", err);
setError("Failed to load zone data.");
setZoneData({ detailFeatures: [], summaryFeatures: [] });
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
loadZoneData(selectedZone);
}, [selectedZone, loadZoneData]);
// Categories shown for the selected zone.
// Domain categories stay visible even when count is 0.
const mergedCategories = useMemo(() => {
const set = new Set<string>();
allCategories.forEach((c) => {
if (c) set.add(c);
});
zoneData.detailFeatures.forEach((feature) => {
const attrs = feature.attributes || {};
const category = normalizeCategory(attrs.category);
if (category) set.add(category);
});
zoneData.summaryFeatures.forEach((feature) => {
const attrs = feature.attributes || {};
const category = normalizeCategory(attrs.category);
if (category) set.add(category);
});
return sortAlpha(Array.from(set));
}, [allCategories, zoneData.detailFeatures, zoneData.summaryFeatures]);
// Only wards from the selected zone
const wardOptions = useMemo(() => {
if (!selectedZone) return [];
const wards = new Set<string>();
zoneData.detailFeatures.forEach((feature) => {
const attrs = feature.attributes || {};
const zone = normalizeZone(attrs.zone);
const ward = normalizeWard(attrs.ward);
if (zone === selectedZone && ward) {
wards.add(ward);
}
});
return sortNumericLikeStrings(Array.from(wards));
}, [zoneData.detailFeatures, selectedZone]);
const visibleWards = useMemo(() => {
if (!selectedZone) return [];
if (selectedWard) return [selectedWard];
return wardOptions;
}, [selectedZone, selectedWard, wardOptions]);
const summaryLookup = useMemo(() => {
const map = new Map<string, string>();
zoneData.summaryFeatures.forEach((feature) => {
const attrs = feature.attributes || {};
const zone = normalizeZone(attrs.zone);
const category = normalizeCategory(attrs.category);
const remarks = normalizeText(attrs.remarks_by_gis_expert);
if (!zone || !category) return;
const key = `${zone}__${category}`;
const existing = map.get(key) || "";
map.set(key, joinUniqueRemark(existing, remarks));
});
return map;
}, [zoneData.summaryFeatures]);
const currentRows = useMemo(() => {
if (!selectedZone) return [];
const map = new Map<string, any>();
// Pre-create every category row for the selected zone so zero counts show up
mergedCategories.forEach((category) => {
const key = `${selectedZone}__${category}`;
const row: any = {
zone: selectedZone,
category,
total: 0,
remarks: summaryLookup.get(key) || "",
};
visibleWards.forEach((ward) => {
row[ward] = 0;
});
map.set(key, row);
});
// Fill counts from the selected zone only
zoneData.detailFeatures.forEach((feature) => {
const attrs = feature.attributes || {};
const zone = normalizeZone(attrs.zone);
const ward = normalizeWard(attrs.ward);
const category = normalizeCategory(attrs.category);
if (!zone || !ward || !category) return;
if (zone !== selectedZone) return;
if (selectedWard && ward !== selectedWard) return;
const key = `${zone}__${category}`;
if (!map.has(key)) {
const row: any = {
zone,
category,
total: 0,
remarks: summaryLookup.get(key) || "",
};
visibleWards.forEach((w) => {
row[w] = 0;
});
map.set(key, row);
}
const row = map.get(key);
row.total = Number(row.total || 0) + 1;
if (visibleWards.includes(ward)) {
row[ward] = Number(row[ward] || 0) + 1;
}
});
// Ensure remarks always come from summary layer
for (const row of map.values()) {
const key = `${row.zone}__${row.category}`;
row.remarks = summaryLookup.get(key) || row.remarks || "";
visibleWards.forEach((ward) => {
if (row[ward] === undefined) row[ward] = 0;
});
}
return Array.from(map.values()).sort((a, b) =>
String(a.category).localeCompare(String(b.category), undefined, {
sensitivity: "base",
})
);
}, [selectedZone, selectedWard, mergedCategories, summaryLookup, zoneData.detailFeatures, visibleWards]);
const totals = useMemo(() => {
const acc: any = { total: 0 };
visibleWards.forEach((w) => (acc[w] = 0));
currentRows.forEach((row: any) => {
visibleWards.forEach((w) => {
acc[w] += Number(row[w] || 0);
});
acc.total += Number(row.total || 0);
});
return acc;
}, [currentRows, visibleWards]);
const escapeCsvValue = (val: any): string => {
if (val === null || val === undefined) return "";
const s = String(val);
if (s.includes(",") || s.includes('"') || s.includes("\n")) {
return `"${s.replace(/"/g, '""')}"`;
}
return s;
};
const exportToCSV = () => {
const headers = ["Zone", "Category", ...visibleWards, "Grand Total", "Remarks by GIS Expert"];
const body = currentRows.map((row: any) => [
row.zone,
row.category,
...visibleWards.map((w) => row[w] || 0),
row.total || 0,
row.remarks || "",
]);
const totalRow = ["Total", "", ...visibleWards.map((w) => totals[w] || 0), totals.total || 0, ""];
let csv = headers.map(escapeCsvValue).join(",") + "\n";
[...body, totalRow].forEach((row) => {
csv += row.map(escapeCsvValue).join(",") + "\n";
});
saveAs(new Blob([csv], { type: "text/csv;charset=utf-8;" }), "zone_category_ward_detail.csv");
};
const exportToPDF = () => {
const doc = new (jsPDF as any)("landscape");
const headers = [["Zone", "Category", ...visibleWards, "Grand Total", "Remarks by GIS Expert"]];
const body = currentRows.map((row: any) => [
row.zone,
row.category,
...visibleWards.map((w) => row[w] || 0),
row.total || 0,
row.remarks || "",
]);
body.push(["Total", "", ...visibleWards.map((w) => totals[w] || 0), totals.total || 0, ""]);
doc.text("Zone / Category / Ward Detail", 14, 14);
doc.autoTable({
head: headers,
body,
startY: 20,
styles: { fontSize: 7, cellWidth: "wrap" },
headStyles: { fillColor: [30, 30, 30] },
margin: { top: 18, left: 6, right: 6 },
});
doc.save("zone_category_ward_detail.pdf");
};
const handleReset = () => {
setSelectedZone("");
setSelectedWard("");
};
return (
<>
<Typography variant="h6" sx={{ mb: 2 }}>
Zone / Ward / Category Wise Asset Count
</Typography>
<Typography variant="body2" sx={{ mb: 2, color: "text.secondary" }}>
Select a zone first. Only that zone&apos;s wards will be loaded and displayed.
</Typography>
<Grid container spacing={2} sx={{ mb: 2 }} alignItems="center">
<Grid item>
<FormControl sx={{ minWidth: 140 }} size="small">
<InputLabel id="zone-select-label">Zone</InputLabel>
<Select
labelId="zone-select-label"
value={selectedZone}
label="Zone"
onChange={(e) => {
setSelectedZone(e.target.value as string);
setSelectedWard("");
}}
>
<MenuItem value="">Select Zone</MenuItem>
{zoneOptions.map((zone) => (
<MenuItem key={zone} value={zone}>
Zone {zone}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item>
<FormControl sx={{ minWidth: 140 }} size="small" disabled={!selectedZone}>
<InputLabel id="ward-select-label">Ward</InputLabel>
<Select
labelId="ward-select-label"
value={selectedWard}
label="Ward"
onChange={(e) => setSelectedWard(e.target.value as string)}
>
<MenuItem value="">All Wards</MenuItem>
{wardOptions.map((ward) => (
<MenuItem key={ward} value={ward}>
{ward}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item>
<Button variant="outlined" onClick={handleReset} sx={{ mr: 1 }}>
Reset Filters
</Button>
<Button variant="contained" color="success" onClick={exportToCSV} sx={{ mr: 1 }}>
Export CSV
</Button>
<Button variant="contained" color="error" onClick={exportToPDF}>
Export PDF
</Button>
</Grid>
</Grid>
{metaLoading && (
<Grid container spacing={2} alignItems="center" sx={{ mb: 1 }}>
<Grid item>
<CircularProgress size={20} />
</Grid>
<Grid item>
<Typography>Loading metadata…</Typography>
</Grid>
</Grid>
)}
{loading && (
<Grid container spacing={2} alignItems="center" sx={{ mb: 1 }}>
<Grid item>
<CircularProgress size={20} />
</Grid>
<Grid item>
<Typography>Loading zone data…</Typography>
</Grid>
</Grid>
)}
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
{!selectedZone ? (
<Alert severity="info" sx={{ mb: 2 }}>
Please select a zone to load ward-wise details.
</Alert>
) : null}
<TableContainer
component={Paper}
sx={{ maxHeight: 560, overflow: "auto", whiteSpace: "nowrap" }}
>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>Zone</TableCell>
<TableCell>Category</TableCell>
{visibleWards.map((ward) => (
<TableCell key={ward} align="center">
{ward}
</TableCell>
))}
<TableCell align="center">Grand Total</TableCell>
<TableCell>Remarks by GIS Expert</TableCell>
</TableRow>
</TableHead>
<TableBody>
{selectedZone && currentRows.length === 0 && !loading ? (
<TableRow>
<TableCell colSpan={visibleWards.length + 4} align="center">
No records to display
</TableCell>
</TableRow>
) : (
<>
{currentRows.map((row: any, idx: number) => (
<TableRow key={`${row.zone}_${row.category}_${idx}`}>
<TableCell>{row.zone}</TableCell>
<TableCell>{row.category}</TableCell>
{visibleWards.map((ward) => (
<TableCell key={ward} align="center">
{row[ward] || 0}
</TableCell>
))}
<TableCell align="center">{row.total || 0}</TableCell>
<TableCell>{row.remarks || ""}</TableCell>
</TableRow>
))}
{selectedZone && currentRows.length > 0 && (
<TableRow sx={{ "& td": { fontWeight: "bold" } }}>
<TableCell>Total</TableCell>
<TableCell />
{visibleWards.map((ward) => (
<TableCell key={ward} align="center">
{totals[ward] || 0}
</TableCell>
))}
<TableCell align="center">{totals.total || 0}</TableCell>
<TableCell />
</TableRow>
)}
</>
)}
</TableBody>
</Table>
</TableContainer>
</>
);
};
export default CategoryWardSummary;

8
AssetUpdation_Remarks/src/runtime/widget.tsx

@ -0,0 +1,8 @@
import { React, AllWidgetProps } from 'jimu-core';
import ZoneAbstractWidget from '../index';
const Widget = (props: AllWidgetProps<any>) => {
return <ZoneAbstractWidget {...props} />;
};
export default Widget;

24
AssetUpdation_Remarks/src/styles.css

@ -0,0 +1,24 @@
.widget-container {
padding: 15px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
h3 {
font-size: 18px;
margin-bottom: 10px;
}
.export-button {
margin-top: 10px;
background-color: #0078d4;
color: white;
}
.table-container {
max-height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
}

14
AssetUpdation_Remarks/tests/simple-widget.test.tsx

@ -0,0 +1,14 @@
import { React } from 'jimu-core'
import _Widget from '../src/runtime/widget'
import { widgetRender, wrapWidget } from 'jimu-for-test'
const render = widgetRender()
describe('test simple widget', () => {
it('simple test', () => {
const Widget = wrapWidget(_Widget, {
config: { exampleConfigProperty: 'a' }
})
const { queryByText } = render(<Widget widgetId="Widget_1" />)
expect(queryByText('exampleConfigProperty: a').tagName).toBe('P')
})
})

5
Asset_AC_Filter/config.json

@ -0,0 +1,5 @@
{
"exampleConfigProperty": "test",
"useMapWidget": true
}

25
Asset_AC_Filter/dist/runtime/assets/zoom-in.svg

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 488.409 488.409" xml:space="preserve">
<circle style="fill:#91C7C9;" cx="160.296" cy="328.409" r="136.8"/>
<circle style="fill:#B3DEE0;" cx="177.896" cy="346.009" r="119.2"/>
<path style="fill:#13BCCE;" d="M478.696,10.009c16,16.8,16,43.2-35.2,94.4s-168.8,142.4-184.8,125.6s74.4-133.6,125.6-185.6
C435.496-6.791,462.696-6.791,478.696,10.009z"/>
<path style="fill:#0E94AA;" d="M478.696,10.009c16,16.8,16,43.2-35.2,94.4s-168.8,142.4-184.8,125.6s80.8-124.8,132.8-176.8
C442.696,2.009,462.696-6.791,478.696,10.009z"/>
<path style="fill:#096E89;" d="M478.696,10.009c16,16.8,16,43.2-35.2,94.4s-168.8,142.4-184.8,125.6"/>
<path style="fill:#F98AC1;" d="M164.296,488.409c-91.2,0-164-72.8-164-164c0-90.4,73.6-164,164-164s164,73.6,164,164
C328.296,415.609,254.696,488.409,164.296,488.409z M164.296,200.409c-68,0-124,56-124,124s56,124,124,124s124-56,124-124
S232.296,200.409,164.296,200.409z"/>
<path style="fill:#EF3388;" d="M164.296,488.409c-90.4,0-156-69.6-156-160s72.8-163.2,163.2-163.2s156.8,68.8,156.8,159.2
C328.296,415.609,254.696,488.409,164.296,488.409z M164.296,200.409c-68,0-124,56-124,124s56,124,124,124s124-56,124-124
S232.296,200.409,164.296,200.409z"/>
<path style="fill:#BC086B;" d="M306.696,240.409c-3.2,21.6-11.2,43.2-20.8,62.4c1.6,7.2,1.6,14.4,1.6,21.6c0,68-56,124-124,124
c-20.8,0-40-4.8-57.6-14.4c-11.2,1.6-22.4,2.4-33.6,2.4c-10.4,0-20-0.8-30.4-1.6c30.4,32.8,72.8,53.6,120.8,53.6
c90.4,0,165.6-73.6,165.6-164C329.896,294.009,321.096,265.209,306.696,240.409z"/>
<path style="fill:#EF3388;" d="M229.096,308.409h-48.8v-48.8c0-8-8-13.6-16-13.6s-16,6.4-16,13.6v48.8h-49.6c-8,0-14.4,8-14.4,16
s6.4,16,14.4,16h49.6v49.6c0,8,8,14.4,16,14.4s16-6.4,16-14.4v-49.6h48.8c8,0,14.4-8,14.4-16S237.096,308.409,229.096,308.409z"/>
<path style="fill:#BC086B;" d="M229.096,308.409h-48.8l0,0l-32,32l0,0v49.6c0,8,8,14.4,16,14.4s16-6.4,16-14.4v-49.6h48.8
c8,0,14.4-8,14.4-16S237.096,308.409,229.096,308.409z"/>
</svg>

2
Asset_AC_Filter/dist/runtime/widget.js
File diff suppressed because it is too large
View File

311
Asset_AC_Filter/dist/runtime/widget.js.LICENSE.txt

@ -0,0 +1,311 @@
/**
* @license
Copyright (c) 2008, Adobe Systems Incorporated
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Adobe Systems Incorporated nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @license
*
* Copyright (c) 2014 James Robb, https://github.com/jamesbrobb
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/
/**
* @license
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* (c) Dean McNamee <dean@gmail.com>, 2013.
*
* https://github.com/deanm/omggif
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* omggif is a JavaScript implementation of a GIF 89a encoder and decoder,
* including animation and compression. It does not rely on any specific
* underlying system, so should run in the browser, Node, or Plask.
*/
/**
* @license
* Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Copyright (c) 2017 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Copyright (c) 2018 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Copyright (c) 2019 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* FPDF is released under a permissive license: there is no usage restriction.
* You may embed it freely in your application (commercial or not), with or
* without modifications.
*
* Reference: http://www.fpdf.org/en/script/script37.php
*/
/**
* @license
* Joseph Myers does not specify a particular license for his work.
*
* Author: Joseph Myers
* Accessed from: http://www.myersdaily.org/joseph/javascript/md5.js
*
* Modified by: Owen Leong
*/
/**
* @license
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
* Author: Owen Leong (@owenl131)
* Date: 15 Oct 2020
* References:
* https://www.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt
* https://github.com/foliojs/pdfkit/blob/master/lib/security.js
* http://www.fpdf.org/en/script/script37.php
*/
/**
* @license
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Unicode Bidi Engine based on the work of Alex Shensis (@asthensis)
* MIT License
*/
/**
* @license
* jsPDF fileloading PlugIn
* Copyright (c) 2018 Aras Abbasi (aras.abbasi@gmail.com)
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* jsPDF filters PlugIn
* Copyright (c) 2014 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* jsPDF virtual FileSystem functionality
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* A class to parse color values
* @author Stoyan Stefanov <sstoo@gmail.com>
* {@link http://www.phpied.com/rgb-color-parser-in-javascript/}
* @license Use it if you like it
*/
/** ====================================================================
* @license
* jsPDF XMP metadata plugin
* Copyright (c) 2016 Jussi Utunen, u-jussi@suomi24.fi
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/
/** @license
* Copyright (c) 2017 Dominik Homberger
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
https://webpjs.appspot.com
WebPRiffParser dominikhlbg@gmail.com
*/
/** @license
*
* jsPDF - PDF Document creation from JavaScript
* Version 3.0.1 Built on 2025-03-17T14:19:36.873Z
* CommitID 00000000
*
* Copyright (c) 2010-2021 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF
* 2015-2021 yWorks GmbH, http://www.yworks.com
* 2015-2021 Lukas Holländer <lukas.hollaender@yworks.com>, https://github.com/HackbrettXXX
* 2016-2018 Aras Abbasi <aras.abbasi@gmail.com>
* 2010 Aaron Spike, https://github.com/acspike
* 2012 Willow Systems Corporation, https://github.com/willowsystems
* 2012 Pablo Hess, https://github.com/pablohess
* 2012 Florian Jenett, https://github.com/fjenett
* 2013 Warren Weckesser, https://github.com/warrenweckesser
* 2013 Youssef Beddad, https://github.com/lifof
* 2013 Lee Driscoll, https://github.com/lsdriscoll
* 2013 Stefan Slonevskiy, https://github.com/stefslon
* 2013 Jeremy Morel, https://github.com/jmorel
* 2013 Christoph Hartmann, https://github.com/chris-rock
* 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria
* 2014 James Makes, https://github.com/dollaruw
* 2014 Diego Casorran, https://github.com/diegocr
* 2014 Steven Spungin, https://github.com/Flamenco
* 2014 Kenneth Glassey, https://github.com/Gavvers
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Contributor(s):
* siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango,
* kim3er, mfo, alnorth, Flamenco
*/
/** @license
* Copyright (c) 2012 Willow Systems Corporation, https://github.com/willowsystems
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/

1
Asset_AC_Filter/dist/setting/setting.js

@ -0,0 +1 @@
System.register(["jimu-core","jimu-ui/advanced/setting-components"],(function(e,t){var r={},o={};return{setters:[function(e){r.React=e.React},function(e){o.MapWidgetSelector=e.MapWidgetSelector}],execute:function(){e((()=>{var e={79244:e=>{"use strict";e.exports=r},79298:e=>{"use strict";e.exports=o}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var i=t[r]={exports:{}};return e[r](i,i.exports,n),i.exports}n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.p="";var i={};return n.p=window.jimuConfig.baseUrl,(()=>{"use strict";n.r(i),n.d(i,{__set_webpack_public_path__:()=>o,default:()=>r});var e=n(79244),t=n(79298);const r=r=>e.React.createElement("div",{className:"widget-setting-demo"},e.React.createElement(t.MapWidgetSelector,{useMapWidgetIds:r.useMapWidgetIds,onSelect:e=>{console.log("Selected Map Widget IDs:",e),r.onSettingChange({id:r.id,useMapWidgetIds:e})}}));function o(e){n.p=e}})(),i})())}}}));

25
Asset_AC_Filter/icon.svg

@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="#111827">
<path d="M 12,2 L 21,5 L 19,16 L 11,22 L 3,17 Z"
fill="none"
stroke="#111827"
stroke-width="1.5"
stroke-linejoin="round" />
<circle cx="12" cy="2" r="1.25" />
<circle cx="21" cy="5" r="1.25" />
<circle cx="19" cy="16" r="1.25" />
<circle cx="11" cy="22" r="1.25" />
<circle cx="3" cy="17" r="1.25" />
<rect x="7" y="14" width="10" height="1.5" rx="0.5" />
<rect x="8" y="12.5" width="8" height="1.5" rx="0.25" fill="#ffffff" stroke="#111827" stroke-width="1" />
<line x1="9" y1="10" x2="9" y2="12.5" stroke="#111827" stroke-width="1" />
<line x1="11" y1="10" x2="11" y2="12.5" stroke="#111827" stroke-width="1" />
<line x1="13" y1="10" x2="13" y2="12.5" stroke="#111827" stroke-width="1" />
<line x1="15" y1="10" x2="15" y2="12.5" stroke="#111827" stroke-width="1" />
<path d="M 7.5,10 A 4.5,4.5 0 0,1 16.5,10 Z" />
<line x1="12" y1="5.5" x2="12" y2="3.5" stroke="#111827" stroke-width="1.2" stroke-linecap="round" />
</svg>

19
Asset_AC_Filter/manifest.json

@ -0,0 +1,19 @@
{
"name": "Asset_AC_Filter",
"label": "Asset_AC_Filter",
"type": "widget",
"version": "1.16.0",
"exbVersion": "1.16.0",
"author": "Esri R&D Center Beijing",
"description": "This is a barebones, simple widget. It includes an example of how to use the config file, and it includes a placeholder SVG icon. It intentionally does not include the following so you can use this to start from scratch: i18n translations, a Settings panel, unit tests. See the documentation for more information on how to implement these features.",
"copyright": "",
"license": "http://www.apache.org/licenses/LICENSE-2.0",
"properties": {},
"translatedLocales": [
"en"
],
"defaultSize": {
"width": 800,
"height": 500
}
}

7
Asset_AC_Filter/src/config.ts

@ -0,0 +1,7 @@
import { type ImmutableObject } from 'seamless-immutable'
export interface Config {
exampleConfigProperty: string
}
export type IMConfig = ImmutableObject<Config>

25
Asset_AC_Filter/src/runtime/assets/zoom-in.svg

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 488.409 488.409" xml:space="preserve">
<circle style="fill:#91C7C9;" cx="160.296" cy="328.409" r="136.8"/>
<circle style="fill:#B3DEE0;" cx="177.896" cy="346.009" r="119.2"/>
<path style="fill:#13BCCE;" d="M478.696,10.009c16,16.8,16,43.2-35.2,94.4s-168.8,142.4-184.8,125.6s74.4-133.6,125.6-185.6
C435.496-6.791,462.696-6.791,478.696,10.009z"/>
<path style="fill:#0E94AA;" d="M478.696,10.009c16,16.8,16,43.2-35.2,94.4s-168.8,142.4-184.8,125.6s80.8-124.8,132.8-176.8
C442.696,2.009,462.696-6.791,478.696,10.009z"/>
<path style="fill:#096E89;" d="M478.696,10.009c16,16.8,16,43.2-35.2,94.4s-168.8,142.4-184.8,125.6"/>
<path style="fill:#F98AC1;" d="M164.296,488.409c-91.2,0-164-72.8-164-164c0-90.4,73.6-164,164-164s164,73.6,164,164
C328.296,415.609,254.696,488.409,164.296,488.409z M164.296,200.409c-68,0-124,56-124,124s56,124,124,124s124-56,124-124
S232.296,200.409,164.296,200.409z"/>
<path style="fill:#EF3388;" d="M164.296,488.409c-90.4,0-156-69.6-156-160s72.8-163.2,163.2-163.2s156.8,68.8,156.8,159.2
C328.296,415.609,254.696,488.409,164.296,488.409z M164.296,200.409c-68,0-124,56-124,124s56,124,124,124s124-56,124-124
S232.296,200.409,164.296,200.409z"/>
<path style="fill:#BC086B;" d="M306.696,240.409c-3.2,21.6-11.2,43.2-20.8,62.4c1.6,7.2,1.6,14.4,1.6,21.6c0,68-56,124-124,124
c-20.8,0-40-4.8-57.6-14.4c-11.2,1.6-22.4,2.4-33.6,2.4c-10.4,0-20-0.8-30.4-1.6c30.4,32.8,72.8,53.6,120.8,53.6
c90.4,0,165.6-73.6,165.6-164C329.896,294.009,321.096,265.209,306.696,240.409z"/>
<path style="fill:#EF3388;" d="M229.096,308.409h-48.8v-48.8c0-8-8-13.6-16-13.6s-16,6.4-16,13.6v48.8h-49.6c-8,0-14.4,8-14.4,16
s6.4,16,14.4,16h49.6v49.6c0,8,8,14.4,16,14.4s16-6.4,16-14.4v-49.6h48.8c8,0,14.4-8,14.4-16S237.096,308.409,229.096,308.409z"/>
<path style="fill:#BC086B;" d="M229.096,308.409h-48.8l0,0l-32,32l0,0v49.6c0,8,8,14.4,16,14.4s16-6.4,16-14.4v-49.6h48.8
c8,0,14.4-8,14.4-16S237.096,308.409,229.096,308.409z"/>
</svg>

45
Asset_AC_Filter/src/runtime/components/LoadingPopup.css

@ -0,0 +1,45 @@
.loading-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
}
.loading-box {
background: #fff;
padding: 24px;
border-radius: 8px;
width: 280px;
text-align: center;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
}
.loading-title {
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 16px;
}
.spinner {
border: 4px solid #e0e0e0;
border-top: 4px solid #009688;
border-radius: 50%;
width: 32px;
height: 32px;
animation: spin 1s linear infinite;
margin: 0 auto 12px auto;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading-message {
color: #555;
}

23
Asset_AC_Filter/src/runtime/components/LoadingPopup.tsx

@ -0,0 +1,23 @@
import React from 'react';
import './LoadingPopup.css';
interface LoadingPopupProps {
show: boolean;
message?: string;
}
const LoadingPopup: React.FC<LoadingPopupProps> = ({ show, message = "Preparing to download ..." }) => {
if (!show) return null;
return (
<div className="loading-overlay">
<div className="loading-box">
<h2 className="loading-title">Please Wait..</h2>
<div className="spinner" />
<p className="loading-message">{message}</p>
</div>
</div>
);
};
export default LoadingPopup;

23
Asset_AC_Filter/src/runtime/components/LoadingSpinner.jsx

@ -0,0 +1,23 @@
import React from 'react';
import './LoadingPopup.css';
interface LoadingPopupProps {
show: boolean;
message?: string;
}
const LoadingPopup: React.FC<LoadingPopupProps> = ({ show, message = "Preparing to download ..." }) => {
if (!show) return null;
return (
<div className="loading-overlay">
<div className="loading-box">
<h2 className="loading-title">Please Wait..</h2>
<div className="spinner" />
<p className="loading-message">{message}</p>
</div>
</div>
);
};
export default LoadingPopup;

123
Asset_AC_Filter/src/runtime/simple_widget.css

@ -0,0 +1,123 @@
.property-widget-parent {
padding: 0.7rem;
font-family: "Arial", sans-serif;
height: 100%;
overflow: hidden;
}
.property-widget {
background: #fff;
border-radius: 6px;
padding: 0.7rem;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.08);
height: 100%;
max-height: 100%;
overflow-y: auto;
box-sizing: border-box;
}
.widget-title {
font-size: 1rem;
font-weight: bold;
margin-bottom: 0.7rem;
}
#countDiv {
background: #f7f7f7;
border-radius: 4px;
padding: 4px 8px; /* less padding */
margin-bottom: 0.3rem;
font-size: 0.75rem; /* smaller text */
color: #333;
line-height: 1.2;
display: inline-block; /* shrink width to content */
}
.property_form {
display: flex;
flex-direction: column;
margin-bottom: 0.7rem;
}
.property_form label {
font-weight: 600;
margin-bottom: 2px;
font-size: 0.7rem;
}
.property_form select {
padding: 4px 6px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 0.7rem;
}
button {
width: 100%;
padding: 6px 8px;
margin-top: 0.3rem;
border-radius: 4px;
font-weight: bold;
font-size: 0.7rem;
border: none;
cursor: pointer;
}
button.property_form {
background-color: #0078d4;
color: white;
}
button[property_form]:hover {
background-color: #005ea0;
}
button[variant="outlined"] {
background-color: white;
color: #0078d4;
border: 1px solid #0078d4;
}
button[variant="outlined"]:hover {
background-color: #f0f0f0;
}
.attribute-table {
margin-top: 0.7rem;
overflow-x: auto;
}
.attribute-table table {
table-layout: fixed;
width: 100%;
border-collapse: collapse;
}
.attribute-table th,
.attribute-table td {
max-width: 150px;
padding: 6px;
border: 1px solid #ddd;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
font-size: 0.7rem;
}
.attribute-table th {
background-color: #f2f2f2;
font-weight: bold;
position: sticky;
top: 0;
z-index: 10;
text-align: left;
border-bottom: 1px solid #ccc;
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1);
}
.attribute-table tr:hover {
background-color: #f9f9f9;
cursor: pointer;
}

1331
Asset_AC_Filter/src/runtime/widget.tsx
File diff suppressed because it is too large
View File

20
Asset_AC_Filter/src/setting/setting.tsx

@ -0,0 +1,20 @@
import { React } from 'jimu-core'
import { type AllWidgetSettingProps } from 'jimu-for-builder'
import { MapWidgetSelector } from 'jimu-ui/advanced/setting-components'
const Setting = (props: AllWidgetSettingProps<any>) => {
const onMapWidgetSelected = (useMapWidgetIds: string[]) => {
console.log("Selected Map Widget IDs:", useMapWidgetIds);
props.onSettingChange({
id: props.id,
useMapWidgetIds: useMapWidgetIds
})
}
return <div className="widget-setting-demo">
<MapWidgetSelector useMapWidgetIds={props.useMapWidgetIds} onSelect={onMapWidgetSelected} />
</div>
}
export default Setting

BIN
Asset_AC_Filter/src/zoom-in.png

After

Width: 512  |  Height: 512  |  Size: 23 KiB

14
Asset_AC_Filter/tests/simple-widget.test.tsx

@ -0,0 +1,14 @@
import { React } from 'jimu-core'
import _Widget from '../src/runtime/widget'
import { widgetRender, wrapWidget } from 'jimu-for-test'
const render = widgetRender()
describe('test simple widget', () => {
it('simple test', () => {
const Widget = wrapWidget(_Widget, {
config: { exampleConfigProperty: 'a' }
})
const { queryByText } = render(<Widget widgetId="Widget_1" />)
expect(queryByText('exampleConfigProperty: a').tagName).toBe('P')
})
})

1
Asset_Remarks_GIS/config.json

@ -0,0 +1 @@
{}

47
Asset_Remarks_GIS/icon.svg

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 511.999 511.999" xml:space="preserve">
<path style="fill:#3C9684;" d="M275.963,475.985c26.784,1.738,53.728,2.074,80.491-0.208c25.93-2.21,52.325-6.581,76.471-16.629
c5.821-2.423,11.467-5.213,16.979-8.272c5.407-2.999,10.779-6.184,15.676-9.973c9.402-7.277,17.321-16.523,22.666-27.172
c5.646-11.247,8.406-23.721,9.025-36.241c0.593-11.988-0.916-23.983-7.246-34.409c-11.982-19.735-35.578-29.264-56.794-35.294
c-25.158-7.15-51.368-10.368-77.352-12.743c-20.945-1.913-41.953-3.107-62.957-4.15c-9.288-0.462-18.576-0.912-27.864-1.361
c-7.372-0.355-14.756-0.308-22.134-0.485c-13.322-0.32-26.642-0.869-39.945-1.648c-26.597-1.559-53.252-3.874-79.543-8.284
c-10.88-1.825-21.654-3.888-32.17-7.283c-12.219-3.946-24.111-9.04-35.33-15.289c-21.09-11.746-41.189-28.455-50.479-51.358
c-4.73-11.659-6.5-24.832-4.856-37.319c1.564-11.872,6.791-22.898,14.689-31.864c10.398-11.807,24.492-19.384,38.899-25.161
c23.855-9.567,49.93-13.778,75.486-15.185c16.103-0.887,32.353-0.721,48.431,0.633c21.926,1.848,43.782,4.437,65.635,6.999
c26.699,3.131,53.394,6.581,80.179,8.901c13.315,1.154,26.552,2.947,39.843,4.337c13.285,1.389,26.605,2.489,39.951,3.056
c13.23,0.562,26.517,0.684,39.733-0.276c12.787-0.929,25.799-2.851,37.844-7.397c10.325-3.897,20.093-10.136,26.006-19.69
c-5.671,11.5-16.563,19.058-28.026,24.075c-12.14,5.312-25.364,8.103-38.431,9.962c-27.073,3.853-54.614,3.773-81.888,2.87
c-6.858-0.227-13.717-0.499-20.569-0.876c-6.85-0.377-13.668-1.159-20.482-1.929c-5.166-0.584-10.336-1.126-15.504-1.688
c-21.562-2.343-43.097-4.929-64.639-7.446c-21.016-2.456-42.059-5.118-63.169-6.645c-19.578-1.416-39.368-1.3-58.89,0.772
c-13.32,1.414-26.655,3.737-39.547,7.41c-9.79,2.789-19.558,6.256-28.59,11.001c-12.781,6.712-24.639,16.055-30.014,29.866
c-4.489,11.535-3.778,25.223,0.118,36.82c3.738,11.127,10.935,20.816,19.378,28.845c8.823,8.39,19.156,15.146,29.922,20.767
c11.577,6.045,23.872,10.883,36.476,14.302c4.777,1.295,9.633,2.147,14.484,3.095c6.737,1.315,13.538,2.349,20.335,3.308
c13.597,1.915,27.266,3.32,40.948,4.458c25.967,2.162,52.037,3.339,78.092,3.606c8.86,0.091,17.722,0.694,26.571,1.134
c18.434,0.915,36.867,1.845,55.276,3.198c27.318,2.006,54.774,4.726,81.567,10.594c12.684,2.778,25.294,6.282,37.305,11.259
c11.706,4.852,23.046,11.16,32.343,19.864c9.052,8.476,15.821,19.125,19.073,31.137c3.534,13.055,3.038,27.2,0.787,40.437
c-2.129,12.516-6.549,24.741-13.363,35.476c-6.746,10.627-15.626,19.694-25.741,27.149c-21.023,15.496-47.443,22.125-72.977,25.477
c-27.277,3.579-55.088,3.227-82.442,0.677C302.396,479.951,289.132,478.249,275.963,475.985z"/>
<path style="fill:#F2C07E;" d="M245.005,471.344c-69.22-23.323-94.851-71.48-96.386-74.465c-0.036-0.06-0.048-0.109-0.073-0.133
v-0.012l42.066-18.586l64.181-28.35C254.793,349.798,275.313,402.305,245.005,471.344z"/>
<path style="fill:#F23488;" d="M215.084,403.634c-3.758,1.656-7.517,3.045-11.239,4.181c-0.834,0.266-1.668,0.508-2.501,0.737
c-22.405,6.199-42.924,2.973-50.96-8.556c-0.012,0-0.012-0.012-0.012-0.024c-0.713-1.003-1.317-2.091-1.825-3.227v-0.012
L13.345,90.706L55.411,72.12l64.181-28.362l135.201,306.04C262.213,366.571,244.425,390.68,215.084,403.634z"/>
<path style="fill:#D6A86F;" d="M245.005,471.344c-69.22-23.323-94.851-71.48-96.386-74.465c-0.024-0.048-0.048-0.085-0.073-0.133
v-0.012l42.066-18.586l7.819,17.692C200.232,399.912,225.44,458.28,245.005,471.344z"/>
<path style="fill:#DB2A78;" d="M203.845,407.815c-0.834,0.266-1.668,0.508-2.501,0.737c-22.405,6.199-42.924,2.973-50.96-8.556
c-0.012,0-0.012-0.012-0.012-0.024c-0.991-1.643-1.559-2.719-1.752-3.094c-0.024-0.048-0.048-0.085-0.073-0.133v-0.012
L13.345,90.706L55.411,72.12l135.201,306.028l7.819,17.692C198.89,396.879,200.848,401.411,203.845,407.815z"/>
<ellipse transform="matrix(-0.4041 -0.9147 0.9147 -0.4041 31.8246 155.1859)" style="fill:#D6A86F;" cx="66.461" cy="67.227" rx="33.201" ry="58.077"/>
<g>
<path style="fill:#3C9684;" d="M47.829,84.521c-3.564,0-6.828-2.1-8.286-5.401c-1.556-3.521-0.717-7.642,2.092-10.275
c5.328-4.995,12.202-9.426,19.879-12.818c7.677-3.391,15.583-5.489,22.861-6.065c3.83-0.307,7.449,1.85,9.005,5.373
c1.556,3.521,0.717,7.642-2.092,10.275c-5.328,4.995-12.202,9.427-19.879,12.818c-7.677,3.392-15.583,5.489-22.861,6.065
C48.308,84.511,48.069,84.521,47.829,84.521z"/>
<path style="fill:#3C9684;" d="M257.017,435.151c-2.574,11.359-6.429,23.492-12.012,36.193v0.012c-0.012,0-0.036-0.012-0.048-0.012
c-12.52-4.23-23.625-9.269-33.402-14.707c0,0,3.517-7.311,10.646-13.752C229.73,436.117,241.295,430.317,257.017,435.151z"/>
</g>
<path style="fill:#2B7C67;" d="M245.005,471.344h-0.048c-12.52-4.23-23.625-9.269-33.402-14.707c0,0,3.517-7.311,10.646-13.752
C229.573,455.235,237.682,466.449,245.005,471.344z"/>
</svg>

19
Asset_Remarks_GIS/manifest.json

@ -0,0 +1,19 @@
{
"name": "Asset_Remarks_GIS",
"label": "Asset_Remarks_GIS",
"type": "widget",
"version": "1.16.0",
"exbVersion": "1.16.0",
"author": "DIVYA LAWRANCE HMS",
"description": "",
"copyright": "",
"license": "http://www.apache.org/licenses/LICENSE-2.0",
"properties": {},
"translatedLocales": [
"en"
],
"defaultSize": {
"width": 800,
"height": 500
}
}

9
Asset_Remarks_GIS/src/config.ts

@ -0,0 +1,9 @@
import { type ImmutableObject } from 'seamless-immutable'
export interface Config {
exampleConfigProperty: string
customizeLayers?: boolean
selectedLayerIds?: string[]
}
export type IMConfig = ImmutableObject<Config>

204
Asset_Remarks_GIS/src/runtime/components/BufferQueryComponent.tsx

@ -0,0 +1,204 @@
// // BufferStreetLookup.ts
// import geometryEngine from "@arcgis/core/geometry/geometryEngine";
// import Query from "@arcgis/core/rest/support/Query";
// import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
// const carraigeWayLayer = new FeatureLayer({
// url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Transporations/FeatureServer/1", // replace with your actual layer URL
// outFields: ["road_name"]
// });
// // the below code fetches the road name under buffer
// // export async function getStreetNamesFromBuffer(geometry): Promise<string[]> {
// // const distances = [50, 100, 500, 1000]; // meters
// // for (let dist of distances) {
// // const buffered = geometryEngine.buffer(geometry, dist, "meters");
// // const query = new Query({
// // geometry: buffered,
// // spatialRelationship: "intersects",
// // returnGeometry: false,
// // outFields: ["road_name"]
// // });
// // try {
// // const result = await carraigeWayLayer.queryFeatures(query);
// // if (result.features?.length > 0) {
// // const names = result.features.map(f => f.attributes.road_name).filter(Boolean);
// // return Array.from(new Set(names)); // unique street names
// // }
// // } catch (err) {
// // console.error(`Query failed for ${dist}m buffer`, err);
// // }
// // }
// // return []; // fallback if no results
// // }
// export async function getStreetNamesFromBuffer(geometry): Promise<any[]> {
// const distances = [50, 100, 500, 1000]; // meters
// for (let dist of distances) {
// const buffered = geometryEngine.buffer(geometry, dist, "meters");
// const query = new Query({
// geometry: buffered,
// spatialRelationship: "intersects",
// returnGeometry: false,
// outFields: ["road_name", "zone", "ward", "road_id"] // Add all required fields
// });
// try {
// const result = await carraigeWayLayer.queryFeatures(query);
// if (result.features?.length > 0) {
// // Return unique records by road_name
// const seen = new Set();
// const unique = [];
// for (const f of result.features) {
// const name = f.attributes.road_name;
// if (name && !seen.has(name)) {
// seen.add(name);
// unique.push(f.attributes); // return all attributes
// }
// }
// return unique;
// }
// } catch (err) {
// console.error(`Query failed for ${dist}m buffer`, err);
// }
// }
// return []; // fallback if no results
// }
// import projection from '@arcgis/core/geometry/projection';
// import SpatialReference from '@arcgis/core/geometry/SpatialReference';
// import geometryEngine from "@arcgis/core/geometry/geometryEngine";
// import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
// import Query from "@arcgis/core/rest/support/Query";
// const carraigeWayLayer = new FeatureLayer({
// url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Transporations/FeatureServer/1", // replace with your actual layer URL
// outFields: ["road_name"]
// });
// export async function getStreetNamesFromBuffer(geometry): Promise<any[]> {
// // Always load the projection module before using it
// await projection.load();
// // Project input geometry to Web Mercator (meters)
// const projected = projection.project(
// geometry,
// new SpatialReference({ wkid: 3857 }) // Web Mercator uses meters
// );
// const distances = [50, 100, 500, 1000];
// for (let dist of distances) {
// const buffered = geometryEngine.buffer(projected, dist, 'meters');
// const query = new Query({
// geometry: buffered,
// spatialRelationship: 'intersects',
// returnGeometry: false,
// outFields: ['road_name', 'zone', 'ward', 'road_id']
// });
// try {
// const result = await carraigeWayLayer.queryFeatures(query);
// if (result.features?.length > 0) {
// const seen = new Set();
// const unique = [];
// for (const f of result.features) {
// const name = f.attributes.road_name;
// if (name && !seen.has(name)) {
// seen.add(name);
// unique.push(f.attributes);
// }
// }
// return unique;
// }
// } catch (err) {
// console.error(`Query failed for ${dist}m buffer`, err);
// }
// }
// return [];
// }
// import buffer from "@arcgis/core/geometry/support/buffer";
import * as projection from "@arcgis/core/geometry/projection.js";
import SpatialReference from "@arcgis/core/geometry/SpatialReference.js";
import * as geometryEngineAsync from "@arcgis/core/geometry/geometryEngineAsync.js";
import Query from "@arcgis/core/rest/support/Query.js";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer.js";
// Load your carriageway layer
const carriageWayLayer = new FeatureLayer({
url: "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Transporations/FeatureServer/1",
outFields: ["road_name", "zone", "ward", "road_id"]
});
export async function getStreetNamesFromBuffer(geometry): Promise<any[]> {
const distances = [10,25,50, 100, 500, 1000]; // in meters
const targetSR = new SpatialReference({ wkid: 32644 }); // UTM zone 44N — use your local SR
let projectedGeom = geometry;
try {
if (!projection.isLoaded()) {
await projection.load();
}
// ✅ Only project if geometry is not already in a linear spatial reference
if (geometry.spatialReference?.isGeographic) {
projectedGeom = projection.project(geometry, targetSR);
if (!projectedGeom) {
console.warn("Projection failed.");
return [];
}
}
} catch (err) {
console.error("Projection error:", err);
return [];
}
for (const dist of distances) {
try {
const buffered = await geometryEngineAsync.buffer(projectedGeom, dist, "meters");
const query = new Query({
geometry: buffered,
spatialRelationship: "intersects",
returnGeometry: false,
outFields: ["road_name", "zone", "ward", "road_id"]
});
const result = await carriageWayLayer.queryFeatures(query);
if (result.features?.length > 0) {
const seen = new Set();
const unique = [];
for (const f of result.features) {
const name = f.attributes.road_name;
if (name && !seen.has(name)) {
seen.add(name);
unique.push(f.attributes);
}
}
return unique;
}
} catch (err) {
console.error(`Buffer/query failed at ${dist}m:`, err);
}
}
return [];
}

150
Asset_Remarks_GIS/src/runtime/components/FieldExpressions.tsx

@ -0,0 +1,150 @@
import ExpressionInfo from 'esri/form/ExpressionInfo'
interface Config {
zoneLayerName?: string
wardLayerName?: string
roadLayerName?: string
buildingsLayerName?: string
buildingLayerName?: string
}
export function getExpressions(config: Config) {
const zoneLayer = config.zoneLayerName || 'ZoneBoundary'
const wardLayer = config.wardLayerName || 'WardBoundary'
const roadLayer = config.roadLayerName || 'CarriageWay'
const buildingsLayer = config.buildingsLayerName || 'Buildings'
const updationLayer = config.buildingLayerName || 'building_updation_test'
const zoneExpression = new ExpressionInfo({
name: 'zone-expression',
title: 'Zone',
expression: `var intersectingFeatures = Intersects($feature, FeatureSetByName($map, "${zoneLayer}")); for (var f in intersectingFeatures) { return f.zone; }`,
returnType: 'string'
})
const remarkexpresssion=new ExpressionInfo({
name: "always-true",
expression: "true", // Always evaluates to true
returnType: "boolean"
})
const wardExpression = new ExpressionInfo({
name: 'ward-expression',
title: 'Ward',
expression: `var intersectingFeatures = Intersects($feature, FeatureSetByName($map, "${wardLayer}")); for (var f in intersectingFeatures) { return f.ward_no; }`,
returnType: 'string'
})
const roadIdExpression = new ExpressionInfo({
name: 'road-id-expression',
title: 'Road ID',
returnType: 'string',
expression: `var bufferGeom = Buffer($feature, 0.0009, "degree"); var roads = Intersects(FeatureSetByName($map, "${roadLayer}"), bufferGeom); var minDist = Infinity; var nearest = null; for (var r in roads) { var dist = Distance($feature, r, "meters"); if (dist < minDist) { minDist = dist; nearest = r.road_id; } } return nearest;`
})
const gisIdExpression = new ExpressionInfo({
name: 'gis-id-expression',
title: 'GIS ID',
returnType: 'string',
expression: `
var categoryValue = $feature["building_status"];
var existingGISID = "";
if (categoryValue == "Existing Building") {
var buildings = FeatureSetByName($map, "${buildingsLayer}");
var intersecting = Intersects($feature, buildings);
for (var b in intersecting) {
return b["gcc_gis_id"];
}
return null;
}
var updationLayer = FeatureSetByName($map, "${updationLayer}");
var intersectingPoints = Intersects($feature, updationLayer);
var maxSerial = 0;
for (var pt in intersectingPoints) {
var id = pt["gis_id"];
if (!IsEmpty(id)) {
var sn = Number(Mid(id, 9, 5));
if (sn > maxSerial) {
maxSerial = sn;
}
}
}
var inputWard = $feature["ward"];
if (IsEmpty(inputWard)) {
var maxWard = 0;
for (var f in updationLayer) {
var w = Number(f["ward"]);
if (w > maxWard) {
maxWard = w;
}
}
inputWard = maxWard + 1;
}
var wardText = Text(inputWard, "000");
var yearText = Text(Year(Now()) % 100, "00");
var serialNum = maxSerial + 1;
var serialText = Text(serialNum, "00000");
var newGISID = "G-" + wardText + "-" + yearText + "-" + serialText;
var exists = Count(Filter(updationLayer, "gis_id = @newGISID")) > 0;
while (exists) {
serialNum++;
serialText = Text(serialNum, "00000");
newGISID = "G-" + wardText + "-" + yearText + "-" + serialText;
exists = Count(Filter(updationLayer, "gis_id = @newGISID")) > 0;
}
return newGISID;
`
})
const parentgisidExpression = new ExpressionInfo({
name: 'parentgisid-expression',
title: 'Parent GIS ID',
returnType: 'string',
expression: `
var buildings = FeatureSetByName($map, "${buildingsLayer}");
var intersecting = Intersects($feature, buildings);
var idCounts = {};
var maxCount = 0;
var mostCommonId = null;
for (var b in intersecting) {
var id = b["gcc_gis_id"];
if (IsEmpty(id)) continue;
if (HasKey(idCounts, id)) {
idCounts[id] += 1;
} else {
idCounts[id] = 1;
}
if (idCounts[id] > maxCount) {
maxCount = idCounts[id];
mostCommonId = id;
}
}
return mostCommonId;
`
})
const formTypeRequiredExpression = {
name: 'require-form-type',
expression: '$feature.form_type != null && $feature.form_type != ""'
}
return {
zoneExpression,
remarkexpresssion,
wardExpression,
roadIdExpression,
gisIdExpression,
parentgisidExpression,
formTypeRequiredExpression
}
}

9
Asset_Remarks_GIS/src/runtime/style.css

@ -0,0 +1,9 @@
#editorContainer {
position: absolute;
/* height: 48%; */
/* bottom: 2%; */
width: 100%;
}
.export-button {
display: none !important;
}

811
Asset_Remarks_GIS/src/runtime/widget.tsx

@ -0,0 +1,811 @@
// src/runtime/widget.tsx
import React, { useEffect, useMemo, useRef, useState } from "react";
import { AllWidgetProps } from "jimu-core";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import Graphic from "@arcgis/core/Graphic";
type AnyAttrs = Record<string, any>;
type StatusState = {
type: "idle" | "loading" | "success" | "error";
message?: string;
};
type CategoryOption = {
code: string;
label: string;
};
type SummaryRow = {
zone: string;
categoryCode: string;
categoryLabel: string;
count: number;
remarks: string;
};
const ASSET_POINT_URL =
"https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Asset_2026_final/FeatureServer/0";
const SUMMARY_LAYER_URL =
"https://gisgcc.chennaicorporation.gov.in/server/rest/services/gccdeptfield/Summary_Report/FeatureServer/1";
const ZONE_FIELD = "zone";
const CATEGORY_FIELD = "category";
const COUNT_FIELD = "total";
const REMARKS_FIELD = "remarks_by_gis_expert";
function escSql(value: string | number | null | undefined) {
return String(value ?? "").replace(/'/g, "''").trim();
}
function normalizeText(value: any) {
return String(value ?? "").trim();
}
function isBlank(value: any) {
return value === null || value === undefined || String(value).trim() === "";
}
function uniqSorted(values: string[]) {
return Array.from(new Set(values.map((v) => normalizeText(v)).filter((v) => !isBlank(v)))).sort((a, b) =>
a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" })
);
}
function htmlEscape(value: any) {
return String(value ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function parseApplyEditsResult(res: any) {
if (!res) return { added: 0, updated: 0, deleted: 0, errors: [] as any[] };
const addResults = res.addFeatureResults || res.addResults || [];
const updateResults = res.updateFeatureResults || res.updateResults || [];
const deleteResults = res.deleteFeatureResults || res.deleteResults || [];
const okAdded = addResults.filter((r: any) => r && (r.success === true || Number.isFinite(r.objectId))).length;
const okUpdated = updateResults.filter((r: any) => r && (r.success === true || Number.isFinite(r.objectId))).length;
const okDeleted = deleteResults.filter((r: any) => r && (r.success === true || Number.isFinite(r.objectId))).length;
const errors: any[] = [];
for (const arr of [addResults, updateResults, deleteResults]) {
for (const r of arr) {
if (r && r.error) errors.push(r.error);
}
}
return { added: okAdded, updated: okUpdated, deleted: okDeleted, errors };
}
function getOidField(layer: FeatureLayer) {
return layer.objectIdField || "OBJECTID";
}
function isNumericFieldType(fieldType?: string) {
if (!fieldType) return false;
const t = fieldType.toLowerCase();
return t.includes("integer") || t.includes("double") || t.includes("single") || t.includes("small-integer");
}
function sqlLiteral(value: string, fieldType?: string) {
const trimmed = String(value ?? "").trim();
if (isNumericFieldType(fieldType)) {
const n = Number(trimmed);
if (!Number.isNaN(n)) return String(n);
}
return `'${escSql(trimmed)}'`;
}
function makeWhereEquals(fieldName: string, value: string, fieldType?: string) {
return `${fieldName} = ${sqlLiteral(value, fieldType)}`;
}
export default function AssetSummaryWidget(props: AllWidgetProps<any>) {
const assetLayerRef = useRef<FeatureLayer | null>(null);
const summaryLayerRef = useRef<FeatureLayer | null>(null);
const categoryDomainMapRef = useRef<Map<string, string>>(new Map());
const categoryFieldTypeRef = useRef<string | undefined>(undefined);
const zoneFieldTypeRef = useRef<string | undefined>(undefined);
const [selectedZone, setSelectedZone] = useState<string>("");
const [zoneOptions, setZoneOptions] = useState<string[]>([]);
const [categoryOptions, setCategoryOptions] = useState<CategoryOption[]>([]);
const [selectedCategoryCode, setSelectedCategoryCode] = useState<string>("");
const [remarks, setRemarks] = useState<string>("");
const [totalCount, setTotalCount] = useState<number>(0);
const [pendingRows, setPendingRows] = useState<SummaryRow[]>([]);
const [status, setStatus] = useState<StatusState>({ type: "idle" });
const selectedCategoryLabel = useMemo(() => {
return categoryOptions.find((c) => c.code === selectedCategoryCode)?.label || selectedCategoryCode;
}, [categoryOptions, selectedCategoryCode]);
const currentRow = useMemo<SummaryRow | null>(() => {
if (!selectedZone || selectedCategoryCode === "") return null;
return {
zone: selectedZone,
categoryCode: selectedCategoryCode,
categoryLabel: selectedCategoryLabel || selectedCategoryCode,
count: totalCount,
remarks: remarks || "",
};
}, [selectedZone, selectedCategoryCode, selectedCategoryLabel, totalCount, remarks]);
useEffect(() => {
assetLayerRef.current = new FeatureLayer({ url: ASSET_POINT_URL });
summaryLayerRef.current = new FeatureLayer({ url: SUMMARY_LAYER_URL });
}, []);
useEffect(() => {
const init = async () => {
try {
setStatus({ type: "loading", message: "Loading layer metadata..." });
await Promise.all([loadZoneOptions(), loadCategoryOptionsFromDomain()]);
setStatus({ type: "idle" });
} catch (err) {
console.error("Widget init error:", err);
setStatus({ type: "error", message: "Failed to initialize widget" });
}
};
init();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [assetLayerRef.current]);
async function safeQueryFeatures(layer: FeatureLayer | null, where: string, outFields: string[] = ["*"]) {
if (!layer) return null;
if (!where || !where.trim()) return null;
try {
const q = layer.createQuery();
q.where = where;
q.outFields = outFields;
q.returnGeometry = false;
q.num = 2000;
return await layer.queryFeatures(q);
} catch (err) {
console.error("safeQueryFeatures error:", where, err);
return null;
}
}
async function loadZoneOptions() {
const layer = assetLayerRef.current;
if (!layer) {
setZoneOptions([]);
return [];
}
try {
await layer.load();
const zoneField = layer.fields?.find((f) => f.name.toLowerCase() === ZONE_FIELD.toLowerCase());
zoneFieldTypeRef.current = zoneField?.type;
const q = layer.createQuery();
q.where = "1=1";
q.outFields = [ZONE_FIELD];
q.returnGeometry = false;
q.returnDistinctValues = true;
const res = await layer.queryFeatures(q);
const zones: string[] = [];
for (const f of res?.features || []) {
const z = normalizeText(f.attributes?.[ZONE_FIELD]);
if (!isBlank(z)) zones.push(z);
}
const sorted = uniqSorted(zones);
setZoneOptions(sorted);
return sorted;
} catch (err) {
console.error("loadZoneOptions error:", err);
setZoneOptions([]);
return [];
}
}
async function loadCategoryOptionsFromDomain() {
const layer = assetLayerRef.current;
if (!layer) {
setCategoryOptions([]);
return [];
}
try {
await layer.load();
const categoryField = layer.fields?.find((f) => f.name.toLowerCase() === CATEGORY_FIELD.toLowerCase());
categoryFieldTypeRef.current = categoryField?.type;
const map = new Map<string, string>();
const codedValues = (categoryField as any)?.domain?.codedValues || [];
for (const cv of codedValues) {
const rawCode = cv?.code;
const code = normalizeText(rawCode);
const label = normalizeText(cv?.name || cv?.label || rawCode);
if (!isBlank(code) || code === "0") {
map.set(code, label || code);
}
}
categoryDomainMapRef.current = map;
const fromDomain = Array.from(map.entries())
.map(([code, label]) => ({ code, label }))
.sort((a, b) => a.label.localeCompare(b.label, undefined, { numeric: true, sensitivity: "base" }));
if (fromDomain.length) {
setCategoryOptions(fromDomain);
return fromDomain;
}
// Fallback: if no domain exists, load distinct values from asset layer.
const q = layer.createQuery();
q.where = "1=1";
q.outFields = [CATEGORY_FIELD];
q.returnGeometry = false;
q.returnDistinctValues = true;
const res = await layer.queryFeatures(q);
const options: CategoryOption[] = [];
for (const f of res?.features || []) {
const code = normalizeText(f.attributes?.[CATEGORY_FIELD]);
if (isBlank(code) && code !== "0") continue;
options.push({
code,
label: code,
});
}
options.sort((a, b) => a.label.localeCompare(b.label, undefined, { numeric: true, sensitivity: "base" }));
setCategoryOptions(options);
return options;
} catch (err) {
console.error("loadCategoryOptionsFromDomain error:", err);
setCategoryOptions([]);
return [];
}
}
async function loadCountForZoneCategory(zone: string, categoryCode: string) {
const layer = assetLayerRef.current;
if (!layer || isBlank(zone) || categoryCode === "") {
setTotalCount(0);
return 0;
}
try {
const q = layer.createQuery();
q.where = `${makeWhereEquals(ZONE_FIELD, zone, zoneFieldTypeRef.current)} AND ${makeWhereEquals(
CATEGORY_FIELD,
categoryCode,
categoryFieldTypeRef.current
)}`;
q.returnGeometry = false;
const count = await layer.queryFeatureCount(q);
setTotalCount(count || 0);
return count || 0;
} catch (err) {
console.error("loadCountForZoneCategory error:", err);
setTotalCount(0);
return 0;
}
}
async function loadExistingSummary(zone: string, categoryCode: string) {
const layer = summaryLayerRef.current;
if (!layer || isBlank(zone) || categoryCode === "") {
setRemarks("");
return;
}
const where =
`${makeWhereEquals(ZONE_FIELD, zone, zoneFieldTypeRef.current)} AND ` +
`${makeWhereEquals(CATEGORY_FIELD, categoryCode, categoryFieldTypeRef.current)}`;
const res = await safeQueryFeatures(layer, where, ["*"]);
if (res?.features?.length) {
const attrs = res.features[0].attributes || {};
setRemarks(normalizeText(attrs[REMARKS_FIELD]));
} else {
setRemarks("");
}
}
async function handleZoneChange(zone: string) {
setSelectedZone(zone);
setSelectedCategoryCode("");
setRemarks("");
setTotalCount(0);
if (!zone) return;
try {
setStatus({ type: "loading", message: "Zone selected" });
setStatus({ type: "success", message: "Zone loaded" });
} catch (err) {
console.error("handleZoneChange error:", err);
setStatus({ type: "error", message: "Failed to load zone" });
} finally {
setTimeout(() => setStatus({ type: "idle" }), 900);
}
}
async function handleCategoryChange(categoryCode: string) {
setSelectedCategoryCode(categoryCode);
setRemarks("");
setTotalCount(0);
if (!selectedZone || categoryCode === "") return;
try {
setStatus({ type: "loading", message: "Calculating count..." });
await Promise.all([loadCountForZoneCategory(selectedZone, categoryCode), loadExistingSummary(selectedZone, categoryCode)]);
setStatus({ type: "success", message: "Category loaded" });
} catch (err) {
console.error("handleCategoryChange error:", err);
setStatus({ type: "error", message: "Failed to load category" });
} finally {
setTimeout(() => setStatus({ type: "idle" }), 900);
}
}
function resetCurrentForm() {
setSelectedZone("");
setSelectedCategoryCode("");
setRemarks("");
setTotalCount(0);
setStatus({ type: "idle" });
}
function resetAll() {
resetCurrentForm();
setPendingRows([]);
}
function addCurrentRowToList() {
if (!currentRow) {
alert("Please select zone and category.");
return;
}
setPendingRows((prev) => {
const exists = prev.find((r) => r.zone === currentRow.zone && r.categoryCode === currentRow.categoryCode);
if (exists) {
return prev.map((r) =>
r.zone === currentRow.zone && r.categoryCode === currentRow.categoryCode ? currentRow : r
);
}
return [...prev, currentRow];
});
setStatus({ type: "success", message: "Row added to list" });
setTimeout(() => setStatus({ type: "idle" }), 900);
}
function removePendingRow(index: number) {
setPendingRows((prev) => prev.filter((_, i) => i !== index));
}
async function upsertSummaryRow(row: SummaryRow) {
const layer = summaryLayerRef.current;
if (!layer) throw new Error("Summary layer is not ready.");
const where =
`${makeWhereEquals(ZONE_FIELD, row.zone, zoneFieldTypeRef.current)} AND ` +
`${makeWhereEquals(CATEGORY_FIELD, row.categoryCode, categoryFieldTypeRef.current)}`;
const existing = await safeQueryFeatures(layer, where, ["*"]);
const attrs: AnyAttrs = {
[ZONE_FIELD]: row.zone,
[CATEGORY_FIELD]: row.categoryCode,
[COUNT_FIELD]: String(row.count ?? 0),
[REMARKS_FIELD]: row.remarks || "",
};
let result: any;
if (existing?.features?.length) {
const found = existing.features[0];
const oidField = getOidField(layer);
attrs[oidField] = found.attributes?.[oidField];
result = await layer.applyEdits({
updateFeatures: [new Graphic({ attributes: attrs })],
});
} else {
result = await layer.applyEdits({
addFeatures: [new Graphic({ attributes: attrs })],
});
}
return parseApplyEditsResult(result);
}
async function submitCurrent() {
if (!currentRow) {
alert("Please select zone and category.");
return;
}
try {
setStatus({ type: "loading", message: "Submitting current row..." });
const result = await upsertSummaryRow(currentRow);
if (result.added > 0 || result.updated > 0) {
setStatus({ type: "success", message: "Current row submitted" });
} else {
setStatus({ type: "error", message: "Submit failed" });
}
} catch (err) {
console.error("submitCurrent error:", err);
setStatus({ type: "error", message: "Submit error" });
} finally {
setTimeout(() => setStatus({ type: "idle" }), 1000);
}
}
async function submitAll() {
if (!pendingRows.length) {
alert("No rows in the list. Add one or more rows first.");
return;
}
try {
setStatus({ type: "loading", message: "Submitting all rows..." });
let success = 0;
let failed = 0;
for (const row of pendingRows) {
try {
const result = await upsertSummaryRow(row);
if (result.added > 0 || result.updated > 0) success += 1;
else failed += 1;
} catch (rowErr) {
console.error("submitAll row error:", rowErr);
failed += 1;
}
}
if (failed === 0) {
setPendingRows([]);
setStatus({ type: "success", message: `Submitted ${success} row(s)` });
} else {
setStatus({ type: "error", message: `Submitted ${success}, failed ${failed}` });
}
} catch (err) {
console.error("submitAll error:", err);
setStatus({ type: "error", message: "Submit all error" });
} finally {
setTimeout(() => setStatus({ type: "idle" }), 1200);
}
}
function rowsForExport() {
if (pendingRows.length) return pendingRows;
if (currentRow) return [currentRow];
return [];
}
function downloadBlob(content: BlobPart, filename: string, mimeType: string) {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
function exportExcel() {
const rows = rowsForExport();
if (!rows.length) {
alert("Nothing to export.");
return;
}
const header = ["Zone", "Category", "Category Name", "Count", "Remarks"];
const body = rows
.map(
(r) =>
`<tr>
<td>${htmlEscape(r.zone)}</td>
<td>${htmlEscape(r.categoryCode)}</td>
<td>${htmlEscape(r.categoryLabel)}</td>
<td>${htmlEscape(r.count)}</td>
<td>${htmlEscape(r.remarks)}</td>
</tr>`
)
.join("");
const html = `
<html>
<head>
<meta charset="utf-8" />
<style>
body { font-family: Arial, sans-serif; padding: 16px; }
h2 { margin: 0 0 12px 0; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #999; padding: 6px 8px; font-size: 12px; vertical-align: top; }
th { background: #f2f2f2; }
</style>
</head>
<body>
<h2>Asset Summary</h2>
<table>
<thead>
<tr>
${header.map((h) => `<th>${h}</th>`).join("")}
</tr>
</thead>
<tbody>
${body}
</tbody>
</table>
</body>
</html>
`.trim();
downloadBlob(
`\ufeff${html}`,
`asset_summary_${new Date().toISOString().slice(0, 10)}.xls`,
"application/vnd.ms-excel"
);
}
function exportPdf() {
const rows = rowsForExport();
if (!rows.length) {
alert("Nothing to export.");
return;
}
const printWindow = window.open("", "_blank", "width=1000,height=720");
if (!printWindow) {
alert("Popup blocked. Please allow popups for PDF export.");
return;
}
const htmlRows = rows
.map(
(r) => `
<tr>
<td>${htmlEscape(r.zone)}</td>
<td>${htmlEscape(r.categoryCode)}</td>
<td>${htmlEscape(r.categoryLabel)}</td>
<td>${htmlEscape(r.count)}</td>
<td>${htmlEscape(r.remarks)}</td>
</tr>`
)
.join("");
const html = `
<html>
<head>
<meta charset="utf-8" />
<title>Asset Summary</title>
<style>
body { font-family: Arial, sans-serif; padding: 18px; }
h2 { margin: 0 0 12px 0; }
.meta { margin-bottom: 12px; font-size: 13px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #000; padding: 6px 8px; font-size: 12px; vertical-align: top; }
th { background: #efefef; }
@media print {
body { padding: 0; }
}
</style>
</head>
<body>
<h2>Asset Summary</h2>
<div class="meta">Generated on ${new Date().toLocaleString()}</div>
<table>
<thead>
<tr>
<th>Zone</th>
<th>Category</th>
<th>Category Name</th>
<th>Count</th>
<th>Remarks</th>
</tr>
</thead>
<tbody>
${htmlRows}
</tbody>
</table>
<script>
window.onload = function () {
window.focus();
window.print();
setTimeout(function () { window.close(); }, 250);
};
</script>
</body>
</html>
`.trim();
printWindow.document.open();
printWindow.document.write(html);
printWindow.document.close();
}
async function reloadCountAndRemarks() {
if (!selectedZone || selectedCategoryCode === "") return;
await Promise.all([
loadCountForZoneCategory(selectedZone, selectedCategoryCode),
loadExistingSummary(selectedZone, selectedCategoryCode),
]);
}
useEffect(() => {
if (!selectedZone || selectedCategoryCode === "") {
setTotalCount(0);
return;
}
(async () => {
try {
setStatus({ type: "loading", message: "Refreshing count..." });
await reloadCountAndRemarks();
setStatus({ type: "idle" });
} catch (err) {
console.error("reload effect error:", err);
setStatus({ type: "error", message: "Failed to refresh count" });
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedZone, selectedCategoryCode]);
return (
<div style={{ padding: 12, maxWidth: 1100, fontFamily: "Segoe UI, Roboto, Arial" }}>
<h3 style={{ margin: "0 0 10px 0" }}>Zone - Category Asset Summary</h3>
<div style={{ background: "#fff", padding: 12, borderRadius: 8, border: "1px solid #e8e8e8" }}>
<div style={{ marginBottom: 10 }}>
<strong>Status:</strong>{" "}
{status.type === "idle"
? "idle"
: status.type === "loading"
? `working — ${status.message || ""}`
: status.message}
</div>
<div style={{ display: "flex", gap: 8, marginBottom: 12, flexWrap: "wrap" }}>
<button onClick={addCurrentRowToList} disabled={!selectedZone || selectedCategoryCode === ""}>
Add Single / Add to List
</button>
<button onClick={submitCurrent} disabled={!selectedZone || selectedCategoryCode === ""}>
Submit Single
</button>
<button onClick={submitAll} disabled={!pendingRows.length}>
Submit Multiple
</button>
<button onClick={resetCurrentForm}>Reset Form</button>
<button onClick={resetAll}>Reset All</button>
<button onClick={exportExcel}>Export Excel</button>
<button onClick={exportPdf}>Export PDF</button>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10 }}>
<div>
<label style={{ fontSize: 12, color: "#444" }}>Zone</label>
<select
value={selectedZone}
onChange={(e) => handleZoneChange(e.target.value)}
style={{ width: "100%", padding: 8, borderRadius: 6, border: "1px solid #ddd" }}
>
<option value="">-- select zone --</option>
{zoneOptions.map((z) => (
<option key={z} value={z}>
{z}
</option>
))}
</select>
</div>
<div>
<label style={{ fontSize: 12, color: "#444" }}>Category (Domain Name)</label>
<select
value={selectedCategoryCode}
onChange={(e) => handleCategoryChange(e.target.value)}
disabled={!selectedZone}
style={{ width: "100%", padding: 8, borderRadius: 6, border: "1px solid #ddd" }}
>
<option value="">-- select category --</option>
{categoryOptions.map((c) => (
<option key={c.code} value={c.code}>
{c.label}
</option>
))}
</select>
</div>
<div>
<label style={{ fontSize: 12, color: "#444" }}>Total Count</label>
<input
value={String(totalCount)}
readOnly
style={{ width: "100%", padding: 8, borderRadius: 6, border: "1px solid #ddd" }}
/>
</div>
</div>
<div style={{ marginTop: 10 }}>
<label style={{ display: "block", fontSize: 12, color: "#444", marginBottom: 6 }}>
Remarks by GIS Expert
</label>
<textarea
value={remarks}
onChange={(e) => setRemarks(e.target.value)}
disabled={!selectedZone || selectedCategoryCode === ""}
rows={4}
style={{ width: "100%", padding: 8, borderRadius: 6, border: "1px solid #ddd", resize: "vertical" }}
placeholder="Enter remarks here"
/>
</div>
<div style={{ marginTop: 12 }}>
<div style={{ fontSize: 13, marginBottom: 6 }}>
<strong>
{selectedZone && selectedCategoryCode !== ""
? `Zone ${selectedZone} | ${selectedCategoryLabel} | Count ${totalCount}`
: "Select zone and category"}
</strong>
</div>
<div style={{ maxHeight: 280, overflow: "auto", border: "1px solid #eee", borderRadius: 6 }}>
<table style={{ width: "100%", borderCollapse: "collapse" }}>
<thead>
<tr style={{ background: "#f7f7f7" }}>
<th style={{ textAlign: "left", padding: 8, borderBottom: "1px solid #eee" }}>Zone</th>
<th style={{ textAlign: "left", padding: 8, borderBottom: "1px solid #eee" }}>Category</th>
<th style={{ textAlign: "left", padding: 8, borderBottom: "1px solid #eee" }}>Category Name</th>
<th style={{ textAlign: "left", padding: 8, borderBottom: "1px solid #eee" }}>Count</th>
<th style={{ textAlign: "left", padding: 8, borderBottom: "1px solid #eee" }}>Remarks</th>
<th style={{ textAlign: "left", padding: 8, borderBottom: "1px solid #eee" }}>Action</th>
</tr>
</thead>
<tbody>
{pendingRows.length === 0 ? (
<tr>
<td colSpan={6} style={{ padding: 8, color: "#666" }}>
Add single or multiple rows to build the submission list.
</td>
</tr>
) : (
pendingRows.map((row, idx) => (
<tr key={`${row.zone}-${row.categoryCode}-${idx}`}>
<td style={{ padding: 8, borderBottom: "1px solid #f2f2f2" }}>{row.zone}</td>
<td style={{ padding: 8, borderBottom: "1px solid #f2f2f2" }}>{row.categoryCode}</td>
<td style={{ padding: 8, borderBottom: "1px solid #f2f2f2" }}>{row.categoryLabel}</td>
<td style={{ padding: 8, borderBottom: "1px solid #f2f2f2" }}>{row.count}</td>
<td style={{ padding: 8, borderBottom: "1px solid #f2f2f2" }}>{row.remarks}</td>
<td style={{ padding: 8, borderBottom: "1px solid #f2f2f2" }}>
<button onClick={() => removePendingRow(idx)}>Remove</button>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
<div style={{ marginTop: 12, fontSize: 12, color: "#666" }}>
Category domain values are loaded from the asset layer, including code 0. Zero counts are allowed and can be
submitted, exported, and kept in the list.
</div>
</div>
</div>
);
}

72
Asset_Remarks_GIS/src/setting/setting.tsx

@ -0,0 +1,72 @@
import { React } from 'jimu-core'
import { type AllWidgetSettingProps } from 'jimu-for-builder'
import { MapWidgetSelector } from 'jimu-ui/advanced/setting-components'
const Setting = (props: AllWidgetSettingProps<any>) => {
const onMapWidgetSelected = (useMapWidgetIds: string[]) => {
props.onSettingChange({
id: props.id,
useMapWidgetIds: useMapWidgetIds
})
}
return <div className="widget-setting-demo">
<MapWidgetSelector useMapWidgetIds={props.useMapWidgetIds} onSelect={onMapWidgetSelected} />
</div>
}
export default Setting
// /** @jsx jsx */
// import { React, jsx } from 'jimu-core'
// import { AllWidgetSettingProps } from 'jimu-for-builder'
// import { MapWidgetSelector, DataSourceSelector } from 'jimu-ui/advanced/setting-components'
// import { IMUseDataSource } from 'jimu-core'
// const Setting = (props: AllWidgetSettingProps<any>) => {
// const { useMapWidgetIds, useDataSources, config, onSettingChange, id } = props
// const onMapWidgetSelected = (ids: string[]) => {
// onSettingChange({
// id,
// useMapWidgetIds: ids,
// useDataSources: [],
// config: {
// ...config,
// layerDataSource: null
// }
// })
// }
// const onLayerSelected = (useDataSources: IMUseDataSource[]) => {
// onSettingChange({
// id,
// useDataSources,
// config: {
// ...config,
// layerDataSource: useDataSources?.[0] || null
// }
// })
// }
// return (
// <div className="widget-setting-custom">
// <MapWidgetSelector
// useMapWidgetIds={useMapWidgetIds}
// onSelect={onMapWidgetSelected}
// />
// {useMapWidgetIds?.length > 0 && (
// <DataSourceSelector
// useMapWidgetIds={useMapWidgetIds}
// mustUseMap={true}
// types={['FeatureLayer']}
// useDataSources={useDataSources}
// onChange={onLayerSelected}
// multiple={false}
// />
// )}
// </div>
// )
// }
// export default Setting

3
Asset_Road_Summary/config.json

@ -0,0 +1,3 @@
{
"exampleConfigProperty": "test"
}

9
Asset_Road_Summary/icon.svg

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg"
width="800px" height="800px" viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve">
<path d="M44,4H8C5.8,4,4,5.8,4,8v36c0,2.2,1.8,4,4,4h36c2.2,0,4-1.8,4-4V8C48,5.8,46.2,4,44,4z M12,14
c0-0.6,0.4-1,1-1h10c0.6,0,1,0.4,1,1v10c0,0.6-0.4,1-1,1H13c-0.6,0-1-0.4-1-1V14z M36,40c0,0.6-0.4,1-1,1H13c-0.6,0-1-0.4-1-1v-2
c0-0.6,0.4-1,1-1h22c0.6,0,1,0.4,1,1V40z M40,32c0,0.6-0.4,1-1,1H13c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1h26c0.6,0,1,0.4,1,1V32z
M40,24c0,0.6-0.4,1-1,1H29c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1h10c0.6,0,1,0.4,1,1V24z M40,16c0,0.6-0.4,1-1,1H29
c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1h10c0.6,0,1,0.4,1,1V16z"/>
</svg>

29
Asset_Road_Summary/manifest.json

@ -0,0 +1,29 @@
{
"name": "Asset_Road_Summary",
"label": "Asset_Road_Summary",
"type": "widget",
"version": "1.16.0",
"exbVersion": "1.16.0",
"author": "Esri R&D Center Beijing",
"description": "This is a barebones, simple widget. It includes an example of how to use the config file, and it includes a placeholder SVG icon. It intentionally does not include the following so you can use this to start from scratch: i18n translations, a Settings panel, unit tests. See the documentation for more information on how to implement these features.",
"copyright": "",
"license": "http://www.apache.org/licenses/LICENSE-2.0",
"properties": {
"hasSettingPage": false
},
"widgets": [
{
"uri": "Asset_Road_Summary",
"label": "Asset_Road_Summary",
"thumbnail": "thumbnail.png",
"description": "Summarizes Asset and Road."
}
],
"framework": "react",
"translatedLocales": ["en"],
"dataSources": [],
"elements": []
}

7
Asset_Road_Summary/src/config.ts

@ -0,0 +1,7 @@
import { type ImmutableObject } from 'seamless-immutable'
export interface Config {
exampleConfigProperty: string
}
export type IMConfig = ImmutableObject<Config>

317
Asset_Road_Summary/src/index.tsx

@ -0,0 +1,317 @@
import React, { useEffect, useState } from 'react';
import FeatureLayer from '@arcgis/core/layers/FeatureLayer';
import Query from "@arcgis/core/rest/support/Query";
import {
Table, TableBody, TableCell, TableContainer,
TableHead, TableRow, Paper, Select, MenuItem, Button, Grid
} from '@mui/material';
// NOTE: install these two packages in your project for Excel/PDF export
// npm i xlsx jspdf jspdf-autotable
import * as XLSX from 'xlsx';
import jsPDF from "jspdf";
import autoTable from "jspdf-autotable";
// Combined Zone-Ward Asset Summary component
// Updated: removed hard-coded allowedCategories. The component now
// uses the asset layer's category domain (codedValues) to resolve
// category descriptions and includes whatever descriptions exist in the domain.
const CombinedZoneWardSummary = () => {
const [zone, setZone] = useState('');
const [ward, setWard] = useState('');
const [zoneList, setZoneList] = useState([]);
const [wardList, setWardList] = useState([]);
const [detailedData, setDetailedData] = useState([]); // { zone, ward, category, count }
const [zoneSummary, setZoneSummary] = useState([]); // { zone, category, count }
const [categoryDomain, setCategoryDomain] = useState({});
const assetLayerUrl = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Final_Asset_Location/FeatureServer/0";
useEffect(() => {
fetchZoneList();
fetchCategoryDomain();
}, []);
useEffect(() => {
if (zone) fetchWardList(zone);
}, [zone]);
// Fetch distinct zones
const fetchZoneList = async () => {
try {
const layer = new FeatureLayer({ url: assetLayerUrl });
const query = layer.createQuery();
query.outFields = ["zone_no"];
query.returnDistinctValues = true;
query.returnGeometry = false;
const result = await layer.queryFeatures(query);
const zones = [...new Set(result.features.map(f => f.attributes.zone_no))].sort();
setZoneList(zones);
} catch (err) {
console.error('fetchZoneList error', err);
}
};
const fetchWardList = async (zoneNo) => {
try {
const layer = new FeatureLayer({ url: assetLayerUrl });
const query = layer.createQuery();
query.where = `zone_no = '${zoneNo}'`;
query.outFields = ["ward_no"];
query.returnDistinctValues = true;
query.returnGeometry = false;
const result = await layer.queryFeatures(query);
const wards = [...new Set(result.features.map(f => f.attributes.ward_no))].sort();
setWardList(wards);
} catch (err) {
console.error('fetchWardList error', err);
}
};
// Load category domain and set mapping { code: description }
// returns the mapping so callers can await it
const fetchCategoryDomain = async () => {
try {
const layer = new FeatureLayer({ url: assetLayerUrl });
await layer.load();
const field = layer.fields.find(f => f.name === "category");
if (field?.domain?.codedValues) {
const map = Object.fromEntries(field.domain.codedValues.map(cv => [cv.code, cv.name]));
setCategoryDomain(map);
return map;
}
setCategoryDomain({});
return {};
} catch (err) {
console.error('fetchCategoryDomain error', err);
setCategoryDomain({});
return {};
}
};
// Ensure category domain is loaded before summarizing so we use descriptions
const handleFetch = async () => {
if (!zone) return;
await fetchCategoryDomain();
await fetchAssetSummaries(zone, ward);
};
const handleClear = () => {
setZone('');
setWard('');
setWardList([]);
setDetailedData([]);
setZoneSummary([]);
};
// Build detailed and zone-aggregated summaries using domain descriptions
const fetchAssetSummaries = async (zoneNo, wardNo) => {
try {
const layer = new FeatureLayer({ url: assetLayerUrl });
const query = layer.createQuery();
query.where = `zone_no='${zoneNo}'` + (wardNo ? ` AND ward_no='${wardNo}'` : "");
query.outFields = ['zone_no', 'ward_no', 'category'];
query.returnGeometry = false;
const result = await layer.queryFeatures(query);
// counts keyed by "zone|ward|category" and "zone|category"
const dCounts = {};
const zCounts = {};
result.features.forEach(({ attributes }) => {
const zoneVal = attributes.zone_no;
const wardVal = attributes.ward_no || '';
const catCode = attributes.category;
// Resolve to domain description when available; fall back to raw code/string
const name = (categoryDomain && categoryDomain.hasOwnProperty(catCode)) ? categoryDomain[catCode] : String(catCode || '').trim();
// Use whatever description is available in the domain — no hard-coded filter
const dKey = `${zoneVal}|${wardVal}|${name}`;
dCounts[dKey] = (dCounts[dKey] || 0) + 1;
const zKey = `${zoneVal}|${name}`;
zCounts[zKey] = (zCounts[zKey] || 0) + 1;
});
const detailed = Object.entries(dCounts).map(([k, v]) => {
const [z, w, c] = k.split('|');
return { zone: z, ward: w, category: c, count: v };
}).sort((a, b) => (a.zone + a.ward + a.category).localeCompare(b.zone + b.ward + b.category));
const zoneAgg = Object.entries(zCounts).map(([k, v]) => {
const [z, c] = k.split('|');
return { zone: z, category: c, count: v };
}).sort((a, b) => (a.zone + a.category).localeCompare(b.zone + b.category));
setDetailedData(detailed);
setZoneSummary(zoneAgg);
} catch (err) {
console.error('fetchAssetSummaries error', err);
setDetailedData([]);
setZoneSummary([]);
}
};
// Export helpers
const exportExcel = () => {
const wb = XLSX.utils.book_new();
// Detailed sheet
const detailedSheetData = [ ["Sl.No", "Zone", "Ward", "Category", "Count"] ];
detailedData.forEach((r, i) => detailedSheetData.push([i + 1, r.zone, r.ward, r.category, r.count]));
const ws1 = XLSX.utils.aoa_to_sheet(detailedSheetData);
XLSX.utils.book_append_sheet(wb, ws1, 'Detailed');
// Zone summary sheet
const zoneSheetData = [ ["Sl.No", "Zone", "Category", "Count"] ];
zoneSummary.forEach((r, i) => zoneSheetData.push([i + 1, r.zone, r.category, r.count]));
const ws2 = XLSX.utils.aoa_to_sheet(zoneSheetData);
XLSX.utils.book_append_sheet(wb, ws2, 'ZoneSummary');
XLSX.writeFile(wb, `Asset_Summary_Zone_${zone || 'All'}.xlsx`);
};
const exportPDF = () => {
const doc = new jsPDF({
orientation: "portrait",
unit: "pt",
format: "a4"
});
// ===== Detailed Table =====
const detailedColumns = ["Sl.No", "Zone", "Ward", "Category", "Count"];
const detailedRows = detailedData.map((r, i) => [
i + 1,
r.zone,
r.ward,
r.category,
r.count
]);
doc.setFontSize(14);
doc.text(`Asset Summary - Detailed (Zone ${zone || "All"})`, 40, 40);
autoTable(doc, {
head: [detailedColumns],
body: detailedRows,
startY: 60,
styles: { fontSize: 8 },
});
// ===== Zone Summary Table =====
doc.addPage();
const zoneColumns = ["Sl.No", "Zone", "Category", "Count"];
const zoneRows = zoneSummary.map((r, i) => [
i + 1,
r.zone,
r.category,
r.count
]);
doc.text(`Asset Summary - Zone Aggregated (Zone ${zone || "All"})`, 40, 40);
autoTable(doc, {
head: [zoneColumns],
body: zoneRows,
startY: 60,
styles: { fontSize: 8 },
});
doc.save(`Asset_Summary_Zone_${zone || "All"}.pdf`);
};
return (
<div style={{ padding: '1rem', height: '100%', overflow: 'hidden' }}>
<h2>Zone-wise Ward Asset Summary (Exports: Excel / PDF)</h2>
<div style={{ marginBottom: '10px', fontWeight: 600 }}>
Total Assets (Detailed): {detailedData.reduce((s, r) => s + r.count, 0)} &nbsp; | &nbsp;
Total Assets (Zone): {zoneSummary.reduce((s, r) => s + r.count, 0)}
</div>
<Grid container spacing={2} alignItems="center">
<Grid item>
<Select value={zone} onChange={e => setZone(e.target.value)} displayEmpty style={{ width: 150 }}>
<MenuItem value="">Select Zone</MenuItem>
{zoneList.map(z => <MenuItem key={z} value={z}>{z}</MenuItem>)}
</Select>
</Grid>
<Grid item>
<Select value={ward} onChange={e => setWard(e.target.value)} displayEmpty style={{ width: 150 }} disabled={!zone}>
<MenuItem value="">All Wards</MenuItem>
{wardList.map(w => <MenuItem key={w} value={w}>{w}</MenuItem>)}
</Select>
</Grid>
<Grid item>
<Button variant="contained" color="primary" onClick={handleFetch}>FETCH DATA</Button>
</Grid>
<Grid item>
<Button variant="outlined" color="secondary" onClick={handleClear}>CLEAR</Button>
</Grid>
<Grid item>
<Button variant="contained" onClick={exportExcel} disabled={detailedData.length === 0 && zoneSummary.length === 0}>Export Excel</Button>
</Grid>
<Grid item>
<Button variant="contained" onClick={exportPDF} disabled={detailedData.length === 0 && zoneSummary.length === 0}>Export PDF</Button>
</Grid>
</Grid>
{detailedData.length > 0 && (
<TableContainer component={Paper} style={{ marginTop: '20px', maxHeight: 300, maxWidth: '100%', overflow: 'auto' }}>
<Table size="small" stickyHeader>
<TableHead>
<TableRow>
<TableCell><strong>Sl.No</strong></TableCell>
<TableCell><strong>Zone</strong></TableCell>
<TableCell><strong>Ward</strong></TableCell>
<TableCell><strong>Category</strong></TableCell>
<TableCell><strong>Count</strong></TableCell>
</TableRow>
</TableHead>
<TableBody>
{detailedData.map((r, i) => (
<TableRow key={i}>
<TableCell>{i + 1}</TableCell>
<TableCell>{r.zone}</TableCell>
<TableCell>{r.ward}</TableCell>
<TableCell>{r.category}</TableCell>
<TableCell>{r.count}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{zoneSummary.length > 0 && (
<TableContainer component={Paper} style={{ marginTop: '20px', maxHeight: 300, maxWidth: '100%', overflow: 'auto' }}>
<Table size="small" stickyHeader>
<TableHead>
<TableRow>
<TableCell><strong>Sl.No</strong></TableCell>
<TableCell><strong>Zone</strong></TableCell>
<TableCell><strong>Category</strong></TableCell>
<TableCell><strong>Count</strong></TableCell>
</TableRow>
</TableHead>
<TableBody>
{zoneSummary.map((r, i) => (
<TableRow key={i}>
<TableCell>{i + 1}</TableCell>
<TableCell>{r.zone}</TableCell>
<TableCell>{r.category}</TableCell>
<TableCell>{r.count}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
);
};
export default CombinedZoneWardSummary;

8
Asset_Road_Summary/src/runtime/widget.tsx

@ -0,0 +1,8 @@
import { React, AllWidgetProps } from 'jimu-core';
import ZoneAbstractWidget from '../index';
const Widget = (props: AllWidgetProps<any>) => {
return <ZoneAbstractWidget {...props} />;
};
export default Widget;

24
Asset_Road_Summary/src/styles.css

@ -0,0 +1,24 @@
.widget-container {
padding: 15px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
h3 {
font-size: 18px;
margin-bottom: 10px;
}
.export-button {
margin-top: 10px;
background-color: #0078d4;
color: white;
}
.table-container {
max-height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
}

14
Asset_Road_Summary/tests/simple-widget.test.tsx

@ -0,0 +1,14 @@
import { React } from 'jimu-core'
import _Widget from '../src/runtime/widget'
import { widgetRender, wrapWidget } from 'jimu-for-test'
const render = widgetRender()
describe('test simple widget', () => {
it('simple test', () => {
const Widget = wrapWidget(_Widget, {
config: { exampleConfigProperty: 'a' }
})
const { queryByText } = render(<Widget widgetId="Widget_1" />)
expect(queryByText('exampleConfigProperty: a').tagName).toBe('P')
})
})

3
Asset_Road_Summary_view/config.json

@ -0,0 +1,3 @@
{
"exampleConfigProperty": "test"
}

9
Asset_Road_Summary_view/icon.svg

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg"
width="800px" height="800px" viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve">
<path d="M44,4H8C5.8,4,4,5.8,4,8v36c0,2.2,1.8,4,4,4h36c2.2,0,4-1.8,4-4V8C48,5.8,46.2,4,44,4z M12,14
c0-0.6,0.4-1,1-1h10c0.6,0,1,0.4,1,1v10c0,0.6-0.4,1-1,1H13c-0.6,0-1-0.4-1-1V14z M36,40c0,0.6-0.4,1-1,1H13c-0.6,0-1-0.4-1-1v-2
c0-0.6,0.4-1,1-1h22c0.6,0,1,0.4,1,1V40z M40,32c0,0.6-0.4,1-1,1H13c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1h26c0.6,0,1,0.4,1,1V32z
M40,24c0,0.6-0.4,1-1,1H29c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1h10c0.6,0,1,0.4,1,1V24z M40,16c0,0.6-0.4,1-1,1H29
c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1h10c0.6,0,1,0.4,1,1V16z"/>
</svg>

29
Asset_Road_Summary_view/manifest.json

@ -0,0 +1,29 @@
{
"name": "Asset_Road_Summary_view",
"label": "Asset_Road_Summary_view",
"type": "widget",
"version": "1.16.0",
"exbVersion": "1.16.0",
"author": "Esri R&D Center Beijing",
"description": "This is a barebones, simple widget. It includes an example of how to use the config file, and it includes a placeholder SVG icon. It intentionally does not include the following so you can use this to start from scratch: i18n translations, a Settings panel, unit tests. See the documentation for more information on how to implement these features.",
"copyright": "",
"license": "http://www.apache.org/licenses/LICENSE-2.0",
"properties": {
"hasSettingPage": false
},
"widgets": [
{
"uri": "Asset_Road_Summary_view",
"label": "Asset_Road_Summary_view",
"thumbnail": "thumbnail.png",
"description": "Summarizes Asset and Road."
}
],
"framework": "react",
"translatedLocales": ["en"],
"dataSources": [],
"elements": []
}

7
Asset_Road_Summary_view/src/config.ts

@ -0,0 +1,7 @@
import { type ImmutableObject } from 'seamless-immutable'
export interface Config {
exampleConfigProperty: string
}
export type IMConfig = ImmutableObject<Config>

242
Asset_Road_Summary_view/src/index.tsx

@ -0,0 +1,242 @@
import React, { useEffect, useState } from 'react';
import FeatureLayer from '@arcgis/core/layers/FeatureLayer';
import Query from "@arcgis/core/rest/support/Query";
import {
Table, TableBody, TableCell, TableContainer,
TableHead, TableRow, Paper, Select, MenuItem, Button, Grid
} from '@mui/material';
// NOTE: install these two packages in your project for Excel/PDF export
// npm i xlsx jspdf jspdf-autotable
// Combined Zone-Ward Asset Summary component
// Updated: removed hard-coded allowedCategories. The component now
// uses the asset layer's category domain (codedValues) to resolve
// category descriptions and includes whatever descriptions exist in the domain.
const CombinedZoneWardSummary = () => {
const [zone, setZone] = useState('');
const [ward, setWard] = useState('');
const [zoneList, setZoneList] = useState([]);
const [wardList, setWardList] = useState([]);
const [detailedData, setDetailedData] = useState([]); // { zone, ward, category, count }
const [zoneSummary, setZoneSummary] = useState([]); // { zone, category, count }
const [categoryDomain, setCategoryDomain] = useState({});
const assetLayerUrl = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Final_Asset_Location/FeatureServer/0";
useEffect(() => {
fetchZoneList();
fetchCategoryDomain();
}, []);
useEffect(() => {
if (zone) fetchWardList(zone);
}, [zone]);
// Fetch distinct zones
const fetchZoneList = async () => {
try {
const layer = new FeatureLayer({ url: assetLayerUrl });
const query = layer.createQuery();
query.outFields = ["zone_no"];
query.returnDistinctValues = true;
query.returnGeometry = false;
const result = await layer.queryFeatures(query);
const zones = [...new Set(result.features.map(f => f.attributes.zone_no))].sort();
setZoneList(zones);
} catch (err) {
console.error('fetchZoneList error', err);
}
};
const fetchWardList = async (zoneNo) => {
try {
const layer = new FeatureLayer({ url: assetLayerUrl });
const query = layer.createQuery();
query.where = `zone_no = '${zoneNo}'`;
query.outFields = ["ward_no"];
query.returnDistinctValues = true;
query.returnGeometry = false;
const result = await layer.queryFeatures(query);
const wards = [...new Set(result.features.map(f => f.attributes.ward_no))].sort();
setWardList(wards);
} catch (err) {
console.error('fetchWardList error', err);
}
};
// Load category domain and set mapping { code: description }
// returns the mapping so callers can await it
const fetchCategoryDomain = async () => {
try {
const layer = new FeatureLayer({ url: assetLayerUrl });
await layer.load();
const field = layer.fields.find(f => f.name === "category");
if (field?.domain?.codedValues) {
const map = Object.fromEntries(field.domain.codedValues.map(cv => [cv.code, cv.name]));
setCategoryDomain(map);
return map;
}
setCategoryDomain({});
return {};
} catch (err) {
console.error('fetchCategoryDomain error', err);
setCategoryDomain({});
return {};
}
};
// Ensure category domain is loaded before summarizing so we use descriptions
const handleFetch = async () => {
if (!zone) return;
await fetchCategoryDomain();
await fetchAssetSummaries(zone, ward);
};
const handleClear = () => {
setZone('');
setWard('');
setWardList([]);
setDetailedData([]);
setZoneSummary([]);
};
// Build detailed and zone-aggregated summaries using domain descriptions
const fetchAssetSummaries = async (zoneNo, wardNo) => {
try {
const layer = new FeatureLayer({ url: assetLayerUrl });
const query = layer.createQuery();
query.where = `zone_no='${zoneNo}'` + (wardNo ? ` AND ward_no='${wardNo}'` : "");
query.outFields = ['zone_no', 'ward_no', 'category'];
query.returnGeometry = false;
const result = await layer.queryFeatures(query);
// counts keyed by "zone|ward|category" and "zone|category"
const dCounts = {};
const zCounts = {};
result.features.forEach(({ attributes }) => {
const zoneVal = attributes.zone_no;
const wardVal = attributes.ward_no || '';
const catCode = attributes.category;
// Resolve to domain description when available; fall back to raw code/string
const name = (categoryDomain && categoryDomain.hasOwnProperty(catCode)) ? categoryDomain[catCode] : String(catCode || '').trim();
// Use whatever description is available in the domain — no hard-coded filter
const dKey = `${zoneVal}|${wardVal}|${name}`;
dCounts[dKey] = (dCounts[dKey] || 0) + 1;
const zKey = `${zoneVal}|${name}`;
zCounts[zKey] = (zCounts[zKey] || 0) + 1;
});
const detailed = Object.entries(dCounts).map(([k, v]) => {
const [z, w, c] = k.split('|');
return { zone: z, ward: w, category: c, count: v };
}).sort((a, b) => (a.zone + a.ward + a.category).localeCompare(b.zone + b.ward + b.category));
const zoneAgg = Object.entries(zCounts).map(([k, v]) => {
const [z, c] = k.split('|');
return { zone: z, category: c, count: v };
}).sort((a, b) => (a.zone + a.category).localeCompare(b.zone + b.category));
setDetailedData(detailed);
setZoneSummary(zoneAgg);
} catch (err) {
console.error('fetchAssetSummaries error', err);
setDetailedData([]);
setZoneSummary([]);
}
};
return (
<div style={{ padding: '1rem', height: '100%', overflow: 'hidden' }}>
<h2>Zone-wise Ward Asset Summary </h2>
<div style={{ marginBottom: '10px', fontWeight: 600 }}>
Total Assets (Detailed): {detailedData.reduce((s, r) => s + r.count, 0)} &nbsp; | &nbsp;
Total Assets (Zone): {zoneSummary.reduce((s, r) => s + r.count, 0)}
</div>
<Grid container spacing={2} alignItems="center">
<Grid item>
<Select value={zone} onChange={e => setZone(e.target.value)} displayEmpty style={{ width: 150 }}>
<MenuItem value="">Select Zone</MenuItem>
{zoneList.map(z => <MenuItem key={z} value={z}>{z}</MenuItem>)}
</Select>
</Grid>
<Grid item>
<Select value={ward} onChange={e => setWard(e.target.value)} displayEmpty style={{ width: 150 }} disabled={!zone}>
<MenuItem value="">All Wards</MenuItem>
{wardList.map(w => <MenuItem key={w} value={w}>{w}</MenuItem>)}
</Select>
</Grid>
<Grid item>
<Button variant="contained" color="primary" onClick={handleFetch}>FETCH DATA</Button>
</Grid>
<Grid item>
<Button variant="outlined" color="secondary" onClick={handleClear}>CLEAR</Button>
</Grid>
{detailedData.length > 0 && (
<TableContainer component={Paper} style={{ marginTop: '20px', maxHeight: 300, maxWidth: '100%', overflow: 'auto' }}>
<Table size="small" stickyHeader>
<TableHead>
<TableRow>
<TableCell><strong>Sl.No</strong></TableCell>
<TableCell><strong>Zone</strong></TableCell>
<TableCell><strong>Ward</strong></TableCell>
<TableCell><strong>Category</strong></TableCell>
<TableCell><strong>Count</strong></TableCell>
</TableRow>
</TableHead>
<TableBody>
{detailedData.map((r, i) => (
<TableRow key={i}>
<TableCell>{i + 1}</TableCell>
<TableCell>{r.zone}</TableCell>
<TableCell>{r.ward}</TableCell>
<TableCell>{r.category}</TableCell>
<TableCell>{r.count}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{zoneSummary.length > 0 && (
<TableContainer component={Paper} style={{ marginTop: '20px', maxHeight: 300, maxWidth: '100%', overflow: 'auto' }}>
<Table size="small" stickyHeader>
<TableHead>
<TableRow>
<TableCell><strong>Sl.No</strong></TableCell>
<TableCell><strong>Zone</strong></TableCell>
<TableCell><strong>Category</strong></TableCell>
<TableCell><strong>Count</strong></TableCell>
</TableRow>
</TableHead>
<TableBody>
{zoneSummary.map((r, i) => (
<TableRow key={i}>
<TableCell>{i + 1}</TableCell>
<TableCell>{r.zone}</TableCell>
<TableCell>{r.category}</TableCell>
<TableCell>{r.count}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
);
};
export default CombinedZoneWardSummary;

8
Asset_Road_Summary_view/src/runtime/widget.tsx

@ -0,0 +1,8 @@
import { React, AllWidgetProps } from 'jimu-core';
import ZoneAbstractWidget from '../index';
const Widget = (props: AllWidgetProps<any>) => {
return <ZoneAbstractWidget {...props} />;
};
export default Widget;

24
Asset_Road_Summary_view/src/styles.css

@ -0,0 +1,24 @@
.widget-container {
padding: 15px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
h3 {
font-size: 18px;
margin-bottom: 10px;
}
.export-button {
margin-top: 10px;
background-color: #0078d4;
color: white;
}
.table-container {
max-height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
}

14
Asset_Road_Summary_view/tests/simple-widget.test.tsx

@ -0,0 +1,14 @@
import { React } from 'jimu-core'
import _Widget from '../src/runtime/widget'
import { widgetRender, wrapWidget } from 'jimu-for-test'
const render = widgetRender()
describe('test simple widget', () => {
it('simple test', () => {
const Widget = wrapWidget(_Widget, {
config: { exampleConfigProperty: 'a' }
})
const { queryByText } = render(<Widget widgetId="Widget_1" />)
expect(queryByText('exampleConfigProperty: a').tagName).toBe('P')
})
})

5
Asset_SWD_Filter/config.json

@ -0,0 +1,5 @@
{
"exampleConfigProperty": "test",
"useMapWidget": true
}

25
Asset_SWD_Filter/dist/runtime/assets/zoom-in.svg

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 488.409 488.409" xml:space="preserve">
<circle style="fill:#91C7C9;" cx="160.296" cy="328.409" r="136.8"/>
<circle style="fill:#B3DEE0;" cx="177.896" cy="346.009" r="119.2"/>
<path style="fill:#13BCCE;" d="M478.696,10.009c16,16.8,16,43.2-35.2,94.4s-168.8,142.4-184.8,125.6s74.4-133.6,125.6-185.6
C435.496-6.791,462.696-6.791,478.696,10.009z"/>
<path style="fill:#0E94AA;" d="M478.696,10.009c16,16.8,16,43.2-35.2,94.4s-168.8,142.4-184.8,125.6s80.8-124.8,132.8-176.8
C442.696,2.009,462.696-6.791,478.696,10.009z"/>
<path style="fill:#096E89;" d="M478.696,10.009c16,16.8,16,43.2-35.2,94.4s-168.8,142.4-184.8,125.6"/>
<path style="fill:#F98AC1;" d="M164.296,488.409c-91.2,0-164-72.8-164-164c0-90.4,73.6-164,164-164s164,73.6,164,164
C328.296,415.609,254.696,488.409,164.296,488.409z M164.296,200.409c-68,0-124,56-124,124s56,124,124,124s124-56,124-124
S232.296,200.409,164.296,200.409z"/>
<path style="fill:#EF3388;" d="M164.296,488.409c-90.4,0-156-69.6-156-160s72.8-163.2,163.2-163.2s156.8,68.8,156.8,159.2
C328.296,415.609,254.696,488.409,164.296,488.409z M164.296,200.409c-68,0-124,56-124,124s56,124,124,124s124-56,124-124
S232.296,200.409,164.296,200.409z"/>
<path style="fill:#BC086B;" d="M306.696,240.409c-3.2,21.6-11.2,43.2-20.8,62.4c1.6,7.2,1.6,14.4,1.6,21.6c0,68-56,124-124,124
c-20.8,0-40-4.8-57.6-14.4c-11.2,1.6-22.4,2.4-33.6,2.4c-10.4,0-20-0.8-30.4-1.6c30.4,32.8,72.8,53.6,120.8,53.6
c90.4,0,165.6-73.6,165.6-164C329.896,294.009,321.096,265.209,306.696,240.409z"/>
<path style="fill:#EF3388;" d="M229.096,308.409h-48.8v-48.8c0-8-8-13.6-16-13.6s-16,6.4-16,13.6v48.8h-49.6c-8,0-14.4,8-14.4,16
s6.4,16,14.4,16h49.6v49.6c0,8,8,14.4,16,14.4s16-6.4,16-14.4v-49.6h48.8c8,0,14.4-8,14.4-16S237.096,308.409,229.096,308.409z"/>
<path style="fill:#BC086B;" d="M229.096,308.409h-48.8l0,0l-32,32l0,0v49.6c0,8,8,14.4,16,14.4s16-6.4,16-14.4v-49.6h48.8
c8,0,14.4-8,14.4-16S237.096,308.409,229.096,308.409z"/>
</svg>

2
Asset_SWD_Filter/dist/runtime/widget.js
File diff suppressed because it is too large
View File

311
Asset_SWD_Filter/dist/runtime/widget.js.LICENSE.txt

@ -0,0 +1,311 @@
/**
* @license
Copyright (c) 2008, Adobe Systems Incorporated
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Adobe Systems Incorporated nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @license
*
* Copyright (c) 2014 James Robb, https://github.com/jamesbrobb
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/
/**
* @license
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* (c) Dean McNamee <dean@gmail.com>, 2013.
*
* https://github.com/deanm/omggif
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* omggif is a JavaScript implementation of a GIF 89a encoder and decoder,
* including animation and compression. It does not rely on any specific
* underlying system, so should run in the browser, Node, or Plask.
*/
/**
* @license
* Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Copyright (c) 2017 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Copyright (c) 2018 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Copyright (c) 2019 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* FPDF is released under a permissive license: there is no usage restriction.
* You may embed it freely in your application (commercial or not), with or
* without modifications.
*
* Reference: http://www.fpdf.org/en/script/script37.php
*/
/**
* @license
* Joseph Myers does not specify a particular license for his work.
*
* Author: Joseph Myers
* Accessed from: http://www.myersdaily.org/joseph/javascript/md5.js
*
* Modified by: Owen Leong
*/
/**
* @license
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
* Author: Owen Leong (@owenl131)
* Date: 15 Oct 2020
* References:
* https://www.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt
* https://github.com/foliojs/pdfkit/blob/master/lib/security.js
* http://www.fpdf.org/en/script/script37.php
*/
/**
* @license
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* Unicode Bidi Engine based on the work of Alex Shensis (@asthensis)
* MIT License
*/
/**
* @license
* jsPDF fileloading PlugIn
* Copyright (c) 2018 Aras Abbasi (aras.abbasi@gmail.com)
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* jsPDF filters PlugIn
* Copyright (c) 2014 Aras Abbasi
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* @license
* jsPDF virtual FileSystem functionality
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
/**
* A class to parse color values
* @author Stoyan Stefanov <sstoo@gmail.com>
* {@link http://www.phpied.com/rgb-color-parser-in-javascript/}
* @license Use it if you like it
*/
/** ====================================================================
* @license
* jsPDF XMP metadata plugin
* Copyright (c) 2016 Jussi Utunen, u-jussi@suomi24.fi
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/
/** @license
* Copyright (c) 2017 Dominik Homberger
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
https://webpjs.appspot.com
WebPRiffParser dominikhlbg@gmail.com
*/
/** @license
*
* jsPDF - PDF Document creation from JavaScript
* Version 3.0.1 Built on 2025-03-17T14:19:36.873Z
* CommitID 00000000
*
* Copyright (c) 2010-2021 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF
* 2015-2021 yWorks GmbH, http://www.yworks.com
* 2015-2021 Lukas Holländer <lukas.hollaender@yworks.com>, https://github.com/HackbrettXXX
* 2016-2018 Aras Abbasi <aras.abbasi@gmail.com>
* 2010 Aaron Spike, https://github.com/acspike
* 2012 Willow Systems Corporation, https://github.com/willowsystems
* 2012 Pablo Hess, https://github.com/pablohess
* 2012 Florian Jenett, https://github.com/fjenett
* 2013 Warren Weckesser, https://github.com/warrenweckesser
* 2013 Youssef Beddad, https://github.com/lifof
* 2013 Lee Driscoll, https://github.com/lsdriscoll
* 2013 Stefan Slonevskiy, https://github.com/stefslon
* 2013 Jeremy Morel, https://github.com/jmorel
* 2013 Christoph Hartmann, https://github.com/chris-rock
* 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria
* 2014 James Makes, https://github.com/dollaruw
* 2014 Diego Casorran, https://github.com/diegocr
* 2014 Steven Spungin, https://github.com/Flamenco
* 2014 Kenneth Glassey, https://github.com/Gavvers
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Contributor(s):
* siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango,
* kim3er, mfo, alnorth, Flamenco
*/
/** @license
* Copyright (c) 2012 Willow Systems Corporation, https://github.com/willowsystems
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ====================================================================
*/

1
Asset_SWD_Filter/dist/setting/setting.js

@ -0,0 +1 @@
System.register(["jimu-core","jimu-ui/advanced/setting-components"],(function(e,t){var r={},o={};return{setters:[function(e){r.React=e.React},function(e){o.MapWidgetSelector=e.MapWidgetSelector}],execute:function(){e((()=>{var e={79244:e=>{"use strict";e.exports=r},79298:e=>{"use strict";e.exports=o}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var i=t[r]={exports:{}};return e[r](i,i.exports,n),i.exports}n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.p="";var i={};return n.p=window.jimuConfig.baseUrl,(()=>{"use strict";n.r(i),n.d(i,{__set_webpack_public_path__:()=>o,default:()=>r});var e=n(79244),t=n(79298);const r=r=>e.React.createElement("div",{className:"widget-setting-demo"},e.React.createElement(t.MapWidgetSelector,{useMapWidgetIds:r.useMapWidgetIds,onSelect:e=>{console.log("Selected Map Widget IDs:",e),r.onSettingChange({id:r.id,useMapWidgetIds:e})}}));function o(e){n.p=e}})(),i})())}}}));

3
Asset_SWD_Filter/icon.svg

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M860.48 259.52a22.72 22.72 0 0 0 22.656-22.656V152c0-17.6-14.4-32-32-32h-640c-17.6 0-32 14.4-32 32v768c0 17.6 14.4 32 32 32h640c17.6 0 32-14.4 32-32v-250.432h-0.064l0.064-0.448a22.656 22.656 0 0 0-45.312 0l0.064 0.448h-0.064V501.12a22.656 22.656 0 0 0 45.312 0V323.52h-0.064a22.592 22.592 0 0 0-45.248 0h-0.064V236.864a22.784 22.784 0 0 0 22.72 22.656z" fill="" /><path d="M837.824 903.616a6.4 6.4 0 0 1-6.4 6.4H230.848a6.4 6.4 0 0 1-6.4-6.4V170.432a6.4 6.4 0 0 1 6.4-6.4h600.576a6.4 6.4 0 0 1 6.4 6.4v733.184z" fill="#FFFF00" /><path d="M285.248 520.576a16 16 0 0 1-32 0V249.152a16 16 0 0 1 32 0v271.424zM285.248 655.552a16 16 0 0 1-32 0v-62.976a16 16 0 0 1 32 0v62.976z" fill="#FFFFFF" /><path d="M328.704 357.44c0 8.832 7.168 16 16 16h283.968a16 16 0 0 0 0-32H344.704a16.064 16.064 0 0 0-16 16zM690.688 341.44a16 16 0 0 0 0 32h36.032a16 16 0 0 0 0-32h-36.032z" fill="#BABA00" /><path d="M328.704 485.12c0 8.832 7.168 16 16 16h381.952a16 16 0 0 0 0-32H344.704a16 16 0 0 0-16 16zM328.704 612.736c0 8.768 7.168 16 16 16h381.952a16 16 0 1 0 0-32H344.704a16 16 0 0 0-16 16z" fill="#BABA00" /><path d="M392.448 141.568c0 12.288-10.112 22.4-22.4 22.4h-0.512a22.528 22.528 0 0 1-22.4-22.4V74.752c0-12.288 10.112-22.4 22.4-22.4h0.512c12.288 0 22.4 10.112 22.4 22.4v66.816zM715.136 141.568c0 12.288-10.112 22.4-22.4 22.4h-0.512a22.528 22.528 0 0 1-22.464-22.4V74.752c0-12.288 10.048-22.4 22.464-22.4h0.512c12.224 0 22.4 10.112 22.4 22.4v66.816z" fill="" /></svg>

19
Asset_SWD_Filter/manifest.json

@ -0,0 +1,19 @@
{
"name": "Asset_SWD_Filter",
"label": "Asset_SWD_Filter",
"type": "widget",
"version": "1.16.0",
"exbVersion": "1.16.0",
"author": "Esri R&D Center Beijing",
"description": "This is a barebones, simple widget. It includes an example of how to use the config file, and it includes a placeholder SVG icon. It intentionally does not include the following so you can use this to start from scratch: i18n translations, a Settings panel, unit tests. See the documentation for more information on how to implement these features.",
"copyright": "",
"license": "http://www.apache.org/licenses/LICENSE-2.0",
"properties": {},
"translatedLocales": [
"en"
],
"defaultSize": {
"width": 800,
"height": 500
}
}

7
Asset_SWD_Filter/src/config.ts

@ -0,0 +1,7 @@
import { type ImmutableObject } from 'seamless-immutable'
export interface Config {
exampleConfigProperty: string
}
export type IMConfig = ImmutableObject<Config>

25
Asset_SWD_Filter/src/runtime/assets/zoom-in.svg

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 488.409 488.409" xml:space="preserve">
<circle style="fill:#91C7C9;" cx="160.296" cy="328.409" r="136.8"/>
<circle style="fill:#B3DEE0;" cx="177.896" cy="346.009" r="119.2"/>
<path style="fill:#13BCCE;" d="M478.696,10.009c16,16.8,16,43.2-35.2,94.4s-168.8,142.4-184.8,125.6s74.4-133.6,125.6-185.6
C435.496-6.791,462.696-6.791,478.696,10.009z"/>
<path style="fill:#0E94AA;" d="M478.696,10.009c16,16.8,16,43.2-35.2,94.4s-168.8,142.4-184.8,125.6s80.8-124.8,132.8-176.8
C442.696,2.009,462.696-6.791,478.696,10.009z"/>
<path style="fill:#096E89;" d="M478.696,10.009c16,16.8,16,43.2-35.2,94.4s-168.8,142.4-184.8,125.6"/>
<path style="fill:#F98AC1;" d="M164.296,488.409c-91.2,0-164-72.8-164-164c0-90.4,73.6-164,164-164s164,73.6,164,164
C328.296,415.609,254.696,488.409,164.296,488.409z M164.296,200.409c-68,0-124,56-124,124s56,124,124,124s124-56,124-124
S232.296,200.409,164.296,200.409z"/>
<path style="fill:#EF3388;" d="M164.296,488.409c-90.4,0-156-69.6-156-160s72.8-163.2,163.2-163.2s156.8,68.8,156.8,159.2
C328.296,415.609,254.696,488.409,164.296,488.409z M164.296,200.409c-68,0-124,56-124,124s56,124,124,124s124-56,124-124
S232.296,200.409,164.296,200.409z"/>
<path style="fill:#BC086B;" d="M306.696,240.409c-3.2,21.6-11.2,43.2-20.8,62.4c1.6,7.2,1.6,14.4,1.6,21.6c0,68-56,124-124,124
c-20.8,0-40-4.8-57.6-14.4c-11.2,1.6-22.4,2.4-33.6,2.4c-10.4,0-20-0.8-30.4-1.6c30.4,32.8,72.8,53.6,120.8,53.6
c90.4,0,165.6-73.6,165.6-164C329.896,294.009,321.096,265.209,306.696,240.409z"/>
<path style="fill:#EF3388;" d="M229.096,308.409h-48.8v-48.8c0-8-8-13.6-16-13.6s-16,6.4-16,13.6v48.8h-49.6c-8,0-14.4,8-14.4,16
s6.4,16,14.4,16h49.6v49.6c0,8,8,14.4,16,14.4s16-6.4,16-14.4v-49.6h48.8c8,0,14.4-8,14.4-16S237.096,308.409,229.096,308.409z"/>
<path style="fill:#BC086B;" d="M229.096,308.409h-48.8l0,0l-32,32l0,0v49.6c0,8,8,14.4,16,14.4s16-6.4,16-14.4v-49.6h48.8
c8,0,14.4-8,14.4-16S237.096,308.409,229.096,308.409z"/>
</svg>

45
Asset_SWD_Filter/src/runtime/components/LoadingPopup.css

@ -0,0 +1,45 @@
.loading-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
}
.loading-box {
background: #fff;
padding: 24px;
border-radius: 8px;
width: 280px;
text-align: center;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
}
.loading-title {
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 16px;
}
.spinner {
border: 4px solid #e0e0e0;
border-top: 4px solid #009688;
border-radius: 50%;
width: 32px;
height: 32px;
animation: spin 1s linear infinite;
margin: 0 auto 12px auto;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading-message {
color: #555;
}

23
Asset_SWD_Filter/src/runtime/components/LoadingPopup.tsx

@ -0,0 +1,23 @@
import React from 'react';
import './LoadingPopup.css';
interface LoadingPopupProps {
show: boolean;
message?: string;
}
const LoadingPopup: React.FC<LoadingPopupProps> = ({ show, message = "Preparing to download ..." }) => {
if (!show) return null;
return (
<div className="loading-overlay">
<div className="loading-box">
<h2 className="loading-title">Please Wait..</h2>
<div className="spinner" />
<p className="loading-message">{message}</p>
</div>
</div>
);
};
export default LoadingPopup;

23
Asset_SWD_Filter/src/runtime/components/LoadingSpinner.jsx

@ -0,0 +1,23 @@
import React from 'react';
import './LoadingPopup.css';
interface LoadingPopupProps {
show: boolean;
message?: string;
}
const LoadingPopup: React.FC<LoadingPopupProps> = ({ show, message = "Preparing to download ..." }) => {
if (!show) return null;
return (
<div className="loading-overlay">
<div className="loading-box">
<h2 className="loading-title">Please Wait..</h2>
<div className="spinner" />
<p className="loading-message">{message}</p>
</div>
</div>
);
};
export default LoadingPopup;

123
Asset_SWD_Filter/src/runtime/simple_widget.css

@ -0,0 +1,123 @@
.property-widget-parent {
padding: 0.7rem;
font-family: "Arial", sans-serif;
height: 100%;
overflow: hidden;
}
.property-widget {
background: #fff;
border-radius: 6px;
padding: 0.7rem;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.08);
height: 100%;
max-height: 100%;
overflow-y: auto;
box-sizing: border-box;
}
.widget-title {
font-size: 1rem;
font-weight: bold;
margin-bottom: 0.7rem;
}
#countDiv {
background: #f7f7f7;
border-radius: 4px;
padding: 4px 8px; /* less padding */
margin-bottom: 0.3rem;
font-size: 0.75rem; /* smaller text */
color: #333;
line-height: 1.2;
display: inline-block; /* shrink width to content */
}
.property_form {
display: flex;
flex-direction: column;
margin-bottom: 0.7rem;
}
.property_form label {
font-weight: 600;
margin-bottom: 2px;
font-size: 0.7rem;
}
.property_form select {
padding: 4px 6px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 0.7rem;
}
button {
width: 100%;
padding: 6px 8px;
margin-top: 0.3rem;
border-radius: 4px;
font-weight: bold;
font-size: 0.7rem;
border: none;
cursor: pointer;
}
button.property_form {
background-color: #0078d4;
color: white;
}
button[property_form]:hover {
background-color: #005ea0;
}
button[variant="outlined"] {
background-color: white;
color: #0078d4;
border: 1px solid #0078d4;
}
button[variant="outlined"]:hover {
background-color: #f0f0f0;
}
.attribute-table {
margin-top: 0.7rem;
overflow-x: auto;
}
.attribute-table table {
table-layout: fixed;
width: 100%;
border-collapse: collapse;
}
.attribute-table th,
.attribute-table td {
max-width: 150px;
padding: 6px;
border: 1px solid #ddd;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
font-size: 0.7rem;
}
.attribute-table th {
background-color: #f2f2f2;
font-weight: bold;
position: sticky;
top: 0;
z-index: 10;
text-align: left;
border-bottom: 1px solid #ccc;
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1);
}
.attribute-table tr:hover {
background-color: #f9f9f9;
cursor: pointer;
}

1274
Asset_SWD_Filter/src/runtime/widget.tsx
File diff suppressed because it is too large
View File

20
Asset_SWD_Filter/src/setting/setting.tsx

@ -0,0 +1,20 @@
import { React } from 'jimu-core'
import { type AllWidgetSettingProps } from 'jimu-for-builder'
import { MapWidgetSelector } from 'jimu-ui/advanced/setting-components'
const Setting = (props: AllWidgetSettingProps<any>) => {
const onMapWidgetSelected = (useMapWidgetIds: string[]) => {
console.log("Selected Map Widget IDs:", useMapWidgetIds);
props.onSettingChange({
id: props.id,
useMapWidgetIds: useMapWidgetIds
})
}
return <div className="widget-setting-demo">
<MapWidgetSelector useMapWidgetIds={props.useMapWidgetIds} onSelect={onMapWidgetSelected} />
</div>
}
export default Setting

BIN
Asset_SWD_Filter/src/zoom-in.png

After

Width: 512  |  Height: 512  |  Size: 23 KiB

14
Asset_SWD_Filter/tests/simple-widget.test.tsx

@ -0,0 +1,14 @@
import { React } from 'jimu-core'
import _Widget from '../src/runtime/widget'
import { widgetRender, wrapWidget } from 'jimu-for-test'
const render = widgetRender()
describe('test simple widget', () => {
it('simple test', () => {
const Widget = wrapWidget(_Widget, {
config: { exampleConfigProperty: 'a' }
})
const { queryByText } = render(<Widget widgetId="Widget_1" />)
expect(queryByText('exampleConfigProperty: a').tagName).toBe('P')
})
})

3
Asset_Summary/config.json

@ -0,0 +1,3 @@
{
"exampleConfigProperty": "test"
}

4
Asset_Summary/icon.svg

@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.1323 10.469L15.1344 8.67087L15.4652 8.40057C15.677 8.22748 15.8568 8.01844 15.9961 7.78298L16.0015 7.77362C16.5302 6.85747 16.3594 5.73361 15.6221 4.9964C14.8849 4.25926 13.761 4.08832 12.8448 4.61713L12.8356 4.62263C12.6001 4.76193 12.3911 4.94163 12.218 5.15344L11.9477 5.48422L10.1496 3.48633L7.33395 6.30197L8.72375 7.43773L8.72456 7.4384C8.83298 7.52762 8.92489 7.63516 8.99614 7.75614C9.26009 8.21606 9.19062 8.77633 8.79417 9.17277C8.3977 9.56925 7.83739 9.63872 7.37747 9.37476C7.2562 9.30333 7.14847 9.2111 7.05912 9.10233L7.05369 9.09587L5.92343 7.7125L2.98242 10.6532L8.32993 16.0008C8.54466 16.2155 8.79959 16.3859 9.08016 16.5021C9.36073 16.6183 9.66144 16.6781 9.96513 16.6781C10.2688 16.6781 10.5695 16.6183 10.8501 16.5021C11.1307 16.3859 11.3856 16.2155 11.6003 16.0008L17.1323 10.469ZM10.4674 15.5782C10.6266 15.5122 10.7713 15.4156 10.8932 15.2937L15.6799 10.5072L13.9779 8.97543L13.9938 8.31147L14.8324 7.62621C14.9533 7.52744 15.0559 7.40814 15.1354 7.27378C15.4309 6.76174 15.3423 6.13078 14.915 5.70353C14.4878 5.27629 13.8567 5.18775 13.3448 5.48325C13.2105 5.56275 13.0912 5.66529 12.9924 5.78617L12.3071 6.6248L11.6431 6.64063L10.1114 4.93873L8.82285 6.22728L9.35855 6.66505C9.4391 6.73123 9.51469 6.80292 9.58482 6.87952C9.68951 6.99389 9.78202 7.11921 9.86066 7.25351C10.3569 8.1132 10.2125 9.16864 9.50128 9.87988C8.79004 10.5911 7.73457 10.7355 6.87486 10.2393C6.74055 10.1606 6.61522 10.0681 6.50085 9.96343C6.42425 9.89331 6.35257 9.81772 6.28639 9.73717L6.28366 9.73392L5.84862 9.20146L4.39667 10.6533L9.03704 15.2937C9.15891 15.4156 9.30361 15.5122 9.46285 15.5782C9.62209 15.6442 9.79276 15.6781 9.96513 15.6781C10.1375 15.6781 10.3082 15.6442 10.4674 15.5782Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 2C0 0.895431 0.895431 0 2 0H18C19.1046 0 20 0.895431 20 2V18C20 19.1046 19.1046 20 18 20H2C0.895431 20 0 19.1046 0 18V2ZM2 1H18C18.5523 1 19 1.44772 19 2V18C19 18.5523 18.5523 19 18 19H2C1.44772 19 1 18.5523 1 18V2C1 1.44772 1.44772 1 2 1Z" fill="black"/>
</svg>

29
Asset_Summary/manifest.json

@ -0,0 +1,29 @@
{
"name": "Asset_Summary",
"label": "Asset_Summary",
"type": "widget",
"version": "1.16.0",
"exbVersion": "1.16.0",
"author": "Esri R&D Center Beijing",
"description": "This is a barebones, simple widget. It includes an example of how to use the config file, and it includes a placeholder SVG icon. It intentionally does not include the following so you can use this to start from scratch: i18n translations, a Settings panel, unit tests. See the documentation for more information on how to implement these features.",
"copyright": "",
"license": "http://www.apache.org/licenses/LICENSE-2.0",
"properties": {
"hasSettingPage": false
},
"widgets": [
{
"uri": "Asset_Summary",
"label": "Asset_Summary",
"thumbnail": "thumbnail.png",
"description": "Summarizes Street Light, Highmast, and Pillar Box counts by zone."
}
],
"framework": "react",
"translatedLocales": ["en"],
"dataSources": [],
"elements": []
}

7
Asset_Summary/src/config.ts

@ -0,0 +1,7 @@
import { type ImmutableObject } from 'seamless-immutable'
export interface Config {
exampleConfigProperty: string
}
export type IMConfig = ImmutableObject<Config>

41
Asset_Summary/src/index.tsx

@ -0,0 +1,41 @@
import React, { useEffect, useState } from "react";
const featureLayerUrl = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/streetlight_Enumeration/FeatureServer/0/query";
const ZoneWiseStreetLightCount: React.FC = () => {
const [count, setCount] = useState<number>(0);
const [zone, setZone] = useState<string>("08"); // Example Zone 08, can be changed
const fetchCountData = async () => {
const query = `where=zone%3D%27${zone}%27&returnCountOnly=true&f=geojson`;
try {
const response = await fetch(`${featureLayerUrl}?${query}`);
const data = await response.json();
if (data.error) {
console.error('API Error:', data.error.message);
return;
}
if (data.count) {
setCount(data.count); // Extract the count from the response
}
} catch (error) {
console.error("Request Error:", error);
}
};
useEffect(() => {
fetchCountData();
}, [zone]); // Refetch data whenever the zone changes
return (
<div>
<h3>Zone {zone}</h3>
<p>Total Count: {count}</p>
</div>
);
};
export default ZoneWiseStreetLightCount;

151
Asset_Summary/src/runtime/widget.tsx

@ -0,0 +1,151 @@
import React, { useEffect, useState } from "react";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import Query from "@arcgis/core/rest/support/Query";
import "../styles.css";
// Feature layer URL
const featureLayerUrl =
"https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/GCCAsset_Public/FeatureServer/0";
const ZoneCategoryWiseAssetCount: React.FC = () => {
const [counts, setCounts] = useState<{ zone_no: string; assetDetails: string }[]>([]);
const [zone_no, setZone_no] = useState<string>("");
const [categoryDomain, setCategoryDomain] = useState<Record<number, string>>({});
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string>("");
// Fetch domain values for 'category'
const fetchDomainValues = async () => {
try {
const featureLayer = new FeatureLayer({ url: featureLayerUrl });
await featureLayer.load();
const categoryField = featureLayer.fields.find((field) => field.name === "category");
if (categoryField && categoryField.domain && categoryField.domain.codedValues) {
const domainMapping: Record<number, string> = {};
categoryField.domain.codedValues.forEach((codedValue: { code: number; name: string }) => {
domainMapping[codedValue.code] = codedValue.name;
});
setCategoryDomain(domainMapping);
fetchZoneCategorySummary(domainMapping);
}
} catch (error) {
console.error("Error fetching domain values:", error);
}
};
// Fetch and aggregate zone-category data
const fetchZoneCategorySummary = async (domainMapping: Record<number, string>) => {
try {
setLoading(true);
setError("");
const featureLayer = new FeatureLayer({ url: featureLayerUrl });
let whereClause = "1=1";
if (zone_no) whereClause = `zone_no='${zone_no}'`;
const query = new Query();
query.where = whereClause;
query.outFields = ["zone_no", "category"];
query.returnGeometry = false;
const result = await featureLayer.queryFeatures(query);
console.log("Total records returned:", result.features.length);
const zoneData: Record<string, Record<string, number>> = {};
result.features.forEach((feature) => {
const zone_no = feature.attributes.zone_no;
const categoryCode = feature.attributes.category;
const categoryName = domainMapping[categoryCode] || `Unknown (${categoryCode})`;
if (!zoneData[zone_no]) {
zoneData[zone_no] = {};
}
if (!zoneData[zone_no][categoryName]) {
zoneData[zone_no][categoryName] = 0;
}
zoneData[zone_no][categoryName] += 1;
});
const countsArray = Object.keys(zoneData)
.sort((a, b) => parseInt(a) - parseInt(b))
.map((zone_noKey) => {
const assetDetails = Object.entries(zoneData[zone_noKey])
.map(([category, count]) => `${category} (${count})`)
.join(", ");
return { zone_no: zone_noKey, assetDetails };
});
setCounts(countsArray);
} catch (error) {
console.error("Error fetching asset category count:", error);
setError("Failed to fetch data. Please try again later.");
setCounts([]);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchDomainValues();
}, []);
useEffect(() => {
if (Object.keys(categoryDomain).length > 0) {
fetchZoneCategorySummary(categoryDomain);
}
}, [zone_no]);
return (
<div className="zone-category-wise-asset-container">
<h3>Zone and Category Wise - Asset Count</h3>
<label>
Select Zone No:
<select
value={zone_no}
onChange={(e) => setZone_no(e.target.value)}
className="zone-no-select"
>
<option value="">-- Select Zone No --</option>
{[...Array(15)].map((_, i) => (
<option key={i} value={String(i + 1).padStart(2, "0")}>
{String(i + 1).padStart(2, "0")}
</option>
))}
</select>
</label>
{error && <p className="error-message">{error}</p>}
{loading ? (
<p>Loading...</p>
) : (
<table className="zone-category-wise-table">
<thead>
<tr>
<th>Zone No</th>
<th>Asset Details</th>
</tr>
</thead>
<tbody>
{counts.map(({ zone_no, assetDetails }, index) => (
<tr key={index}>
<td>{zone_no}</td>
<td>{assetDetails}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
};
export default ZoneCategoryWiseAssetCount;

61
Asset_Summary/src/styles.css

@ -0,0 +1,61 @@
.widget-container {
padding: 15px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
h3 {
font-size: 18px;
margin-bottom: 10px;
}
.export-button {
margin-top: 10px;
background-color: #0078d4;
color: white;
}
.table-container {
max-height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
}
/* Zone Wise Streetlight Count */
.zone-wise-streetlight-container {
font-family: Arial, sans-serif;
margin: 20px;
}
.zone-select {
margin-left: 10px;
padding: 5px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
}
.zone-wise-table {
border-collapse: collapse;
width: 100%;
margin-top: 20px;
}
.zone-wise-table th,
.zone-wise-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
.zone-wise-table th {
background-color: #f2f2f2;
}
.zone-wise-table tr:nth-child(even) {
background-color: #f9f9f9;
}
.zone-wise-table tr:hover {
background-color: #f1f1f1;
}

14
Asset_Summary/tests/simple-widget.test.tsx

@ -0,0 +1,14 @@
import { React } from 'jimu-core'
import _Widget from '../src/runtime/widget'
import { widgetRender, wrapWidget } from 'jimu-for-test'
const render = widgetRender()
describe('test simple widget', () => {
it('simple test', () => {
const Widget = wrapWidget(_Widget, {
config: { exampleConfigProperty: 'a' }
})
const { queryByText } = render(<Widget widgetId="Widget_1" />)
expect(queryByText('exampleConfigProperty: a').tagName).toBe('P')
})
})

3
Asset_Updation_Attachment_status/config.json

@ -0,0 +1,3 @@
{
"exampleConfigProperty": "test"
}

2
Asset_Updation_Attachment_status/icon.svg

@ -0,0 +1,2 @@
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 32 32" data-name="Layer 1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style>.cls-1{fill:url(#linear-gradient);}.cls-2{fill:url(#linear-gradient-2);}.cls-3{fill:#f8edeb;}.cls-4{fill:#577590;}</style><linearGradient gradientUnits="userSpaceOnUse" id="linear-gradient" x1="4" x2="25" y1="16" y2="16"><stop offset="0" stop-color="#f9dcc4"/><stop offset="0.01" stop-color="#f8dbc2"/><stop offset="0.12" stop-color="#f3ceb1"/><stop offset="0.26" stop-color="#f0c5a6"/><stop offset="0.46" stop-color="#eebf9f"/><stop offset="1" stop-color="#edbe9d"/></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="linear-gradient-2" x1="7.48" x2="27.52" y1="8.98" y2="29.02"><stop offset="0" stop-color="#f9dcc4"/><stop offset="0.32" stop-color="#f8d9c0"/><stop offset="0.64" stop-color="#f4cfb3"/><stop offset="0.98" stop-color="#eebf9f"/><stop offset="1" stop-color="#edbe9d"/></linearGradient></defs><rect class="cls-1" height="22" rx="2.5" width="21" x="4" y="5"/><rect class="cls-2" height="22" rx="2.5" width="21" x="7" y="8"/><path class="cls-3" d="M20,2a5,5,0,0,0-5,5v5a2,2,0,0,0,3,1.73V15.5a3.5,3.5,0,0,0,7,0V7A5,5,0,0,0,20,2Z"/><path class="cls-4" d="M20,2a5,5,0,0,0-5,5v4.5a.5.5,0,0,0,1,0V7a4,4,0,0,1,8,0v8.5a2.5,2.5,0,0,1-5,0V7a1,1,0,0,1,2,0v8.5a.5.5,0,0,0,1,0V7a2,2,0,0,0-4,0v8.5a3.5,3.5,0,0,0,7,0V7A5,5,0,0,0,20,2Z"/></svg>

29
Asset_Updation_Attachment_status/manifest.json

@ -0,0 +1,29 @@
{
"name": "Asset_Updation_Attachment_status",
"label": "Asset_Updation_Attachment_status",
"type": "widget",
"version": "1.16.0",
"exbVersion": "1.16.0",
"author": "Tanii Team Developer",
"description": "This is a barebones, simple widget. It includes an example of how to use the config file, and it includes a placeholder SVG icon. It intentionally does not include the following so you can use this to start from scratch: i18n translations, a Settings panel, unit tests. See the documentation for more information on how to implement these features.",
"copyright": "",
"license": "http://www.apache.org/licenses/LICENSE-2.0",
"properties": {
"hasSettingPage": false
},
"widgets": [
{
"uri": "Asset_Updation_Attachment_status",
"label": "Asset_Updation_Attachment_status",
"thumbnail": "thumbnail.png",
"description": "Attachment Available Status."
}
],
"framework": "react",
"translatedLocales": ["en"],
"dataSources": [],
"elements": []
}

7
Asset_Updation_Attachment_status/src/config.ts

@ -0,0 +1,7 @@
import { type ImmutableObject } from 'seamless-immutable'
export interface Config {
exampleConfigProperty: string
}
export type IMConfig = ImmutableObject<Config>

898
Asset_Updation_Attachment_status/src/index.tsx

@ -0,0 +1,898 @@
import React, { useEffect, useMemo, useState } from "react";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import Graphic from "@arcgis/core/Graphic";
import { JimuMapViewComponent, JimuMapView } from "jimu-arcgis";
import { type AllWidgetProps } from "jimu-core";
import * as XLSX from "xlsx";
import jsPDF from "jspdf";
import "jspdf-autotable";
interface SummaryInfo {
zone: string;
ward: string;
category: string;
totalCount: number;
withAttachment: number;
withoutAttachment: number;
}
interface AssetFeatureInfo {
objectid: number;
zone: string;
ward: string;
road_name: string;
category: string;
asset_code: string;
asset_type: string;
name_of_the_asset: string;
address: string;
building_status: string;
status: string;
ownership: string;
remarks: string;
created_date: string;
geometry?: __esri.Geometry;
}
const ASSET_URL =
"https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Asset_2026_final/FeatureServer/1";
const AssetAttachmentStatus = (props: AllWidgetProps<any>) => {
const [zones, setZones] = useState<string[]>([]);
const [wards, setWards] = useState<string[]>([]);
const [categories, setCategories] = useState<string[]>([]);
const [selectedZone, setSelectedZone] = useState("");
const [selectedWard, setSelectedWard] = useState("");
const [selectedCategory, setSelectedCategory] = useState("");
const [summary, setSummary] = useState<SummaryInfo | null>(null);
const [features, setFeatures] = useState<AssetFeatureInfo[]>([]);
const [loading, setLoading] = useState(false);
const [fetchingDetails, setFetchingDetails] = useState(false);
const [fetchProgress, setFetchProgress] = useState({ fetched: 0, total: 0, phase: "" });
const [jimuMapView, setJimuMapView] = useState<JimuMapView | null>(null);
const [highlightHandle, setHighlightHandle] = useState<any>(null);
const assetLayer = useMemo(() => {
return new FeatureLayer({
url: ASSET_URL
});
}, []);
const escapeSql = (s: string) => (s ? s.replace(/'/g, "''") : s);
const safeFileName = (s: string) => (s ? s.replace(/[^a-z0-9_\-]/gi, "_") : "All");
const numericSortMaybe = (arr: string[]) =>
arr
.filter(Boolean)
.sort((a, b) => {
const na = Number(a);
const nb = Number(b);
if (!isNaN(na) && !isNaN(nb)) return na - nb;
return String(a).localeCompare(String(b));
});
const alphaSort = (arr: string[]) =>
arr
.filter(Boolean)
.sort((a, b) => String(a).trim().localeCompare(String(b).trim(), undefined, { sensitivity: "base" }));
const findAssetLayerInMap = (map: any): FeatureLayer | null => {
try {
const targetUrl = ASSET_URL;
const searchLayers = (layers: any): FeatureLayer | null => {
if (!layers) return null;
const items = layers.items || [];
for (const layer of items) {
if (!layer) continue;
if (layer.url && typeof layer.url === "string" && layer.url.indexOf(targetUrl) !== -1) {
return layer as FeatureLayer;
}
if (layer.layers) {
const found = searchLayers(layer.layers);
if (found) return found;
}
}
return null;
};
return searchLayers(map.layers);
} catch (e) {
console.warn("findAssetLayerInMap failed:", e);
return null;
}
};
useEffect(() => {
const loadZones = async () => {
try {
const q = assetLayer.createQuery();
q.outFields = ["zone"];
q.where = "zone IS NOT NULL AND zone <> ''";
q.returnGeometry = false;
const result = await assetLayer.queryFeatures(q);
const distinctZones = [...new Set(result.features.map((f) => String(f.attributes.zone || "").trim()).filter(Boolean))];
setZones(numericSortMaybe(distinctZones));
} catch (err) {
console.error("loadZones error:", err);
}
};
loadZones();
}, [assetLayer]);
useEffect(() => {
if (!selectedZone) {
setWards([]);
return;
}
const loadWards = async () => {
try {
let where = "ward IS NOT NULL AND ward <> ''";
if (selectedZone !== "All") where += ` AND zone='${escapeSql(selectedZone)}'`;
const q = assetLayer.createQuery();
q.outFields = ["ward"];
q.where = where;
q.returnGeometry = false;
const result = await assetLayer.queryFeatures(q);
const distinct = [...new Set(result.features.map((f) => String(f.attributes.ward || "").trim()).filter(Boolean))];
setWards(numericSortMaybe(distinct));
} catch (err) {
console.error("loadWards error:", err);
}
};
loadWards();
}, [selectedZone, assetLayer]);
useEffect(() => {
if (!selectedZone || !selectedWard) {
setCategories([]);
return;
}
const loadCategories = async () => {
try {
let where = "category IS NOT NULL AND category <> ''";
if (selectedZone !== "All") where += ` AND zone='${escapeSql(selectedZone)}'`;
if (selectedWard !== "All") where += ` AND ward='${escapeSql(selectedWard)}'`;
const q = assetLayer.createQuery();
q.outFields = ["category"];
q.where = where;
q.returnGeometry = false;
const result = await assetLayer.queryFeatures(q);
const distinct = [...new Set(result.features.map((f) => String(f.attributes.category || "").trim()).filter(Boolean))];
setCategories(alphaSort(distinct));
} catch (err) {
console.error("loadCategories error:", err);
}
};
loadCategories();
}, [selectedZone, selectedWard, assetLayer]);
const activeViewChangeHandler = (jmv: JimuMapView) => {
if (jmv && jmv.view) setJimuMapView(jmv);
};
const zoomToFeature = async (feature: AssetFeatureInfo) => {
try {
if (!jimuMapView?.view) return;
let geom = feature.geometry as __esri.Geometry | undefined;
if (!geom) {
const q = assetLayer.createQuery();
q.objectIds = [feature.objectid];
q.outFields = [];
q.returnGeometry = true;
const res = await assetLayer.queryFeatures(q);
if (res.features && res.features.length > 0) {
geom = res.features[0].geometry;
setFeatures((prev) => prev.map((pf) => (pf.objectid === feature.objectid ? { ...pf, geometry: geom } : pf)));
}
}
if (!geom) return;
if (highlightHandle) {
try {
highlightHandle.remove();
} catch {
/* ignore */
}
setHighlightHandle(null);
}
const assetLayerInMap = findAssetLayerInMap(jimuMapView.view.map);
if (assetLayerInMap) {
const layerView = await jimuMapView.view.whenLayerView(assetLayerInMap);
if (layerView) {
try {
const hl = layerView.highlight(feature.objectid);
setHighlightHandle(hl);
setTimeout(() => {
try {
hl.remove();
setHighlightHandle(null);
} catch {
/* ignore */
}
}, 5000);
} catch {
try {
const graphic = new Graphic({ geometry: geom, attributes: feature as any });
const hl = layerView.highlight(graphic);
setHighlightHandle(hl);
setTimeout(() => {
try {
hl.remove();
setHighlightHandle(null);
} catch {
/* ignore */
}
}, 5000);
} catch (gErr) {
console.warn("Highlight failed:", gErr);
}
}
}
}
await jimuMapView.view.goTo({ target: geom, zoom: 18 });
} catch (err) {
console.error("Error in zoomToFeature:", err);
throw err;
}
};
const handleRowZoom = async (feature: AssetFeatureInfo) => {
try {
await zoomToFeature(feature);
} catch (err) {
console.error("Row zoom failed:", err);
alert("Zoom failed — check console.");
}
};
const zoomToLayer = async () => {
if (!jimuMapView?.view) return;
try {
const map = jimuMapView.view.map;
const layerInMap = findAssetLayerInMap(map);
if (!layerInMap) return;
if ((layerInMap as any).fullExtent) {
const fullExt = (layerInMap as any).fullExtent;
const padded = fullExt.expand && typeof fullExt.expand === "function" ? fullExt.expand(1.2) : fullExt;
await jimuMapView.view.goTo({ target: padded });
return;
}
try {
const q = layerInMap.createQuery();
q.where = "1=1";
const extentResult: any = await layerInMap.queryExtent(q);
if (extentResult && extentResult.extent) {
const ext = extentResult.extent;
const padded = ext.expand && typeof ext.expand === "function" ? ext.expand(1.2) : ext;
await jimuMapView.view.goTo({ target: padded });
return;
}
} catch (qe) {
console.warn("queryExtent failed:", qe);
}
} catch (err) {
console.error("Error in zoomToLayer:", err);
}
};
const getOidsWithAttachments = async (allObjectIds: number[], initialChunk = 500) => {
const hasAtt = new Set<number>();
const tryChunk = async (ids: number[], chunkSize: number) => {
for (let i = 0; i < ids.length; i += chunkSize) {
const chunk = ids.slice(i, i + chunkSize);
try {
const attachResult = await assetLayer.queryAttachments({ objectIds: chunk });
for (const oid of chunk) {
const arr = (attachResult as any)[String(oid)] || (attachResult as any)[oid];
if (arr && arr.length > 0) hasAtt.add(oid);
}
setFetchProgress((p) => ({ ...p, fetched: Math.min(p.total, p.fetched + chunk.length) }));
} catch (err) {
console.warn(`queryAttachments failed for chunkSize ${chunkSize}`, err);
if (chunkSize <= 50) throw err;
await tryChunk(chunk, Math.max(50, Math.floor(chunkSize / 5)));
}
}
};
setFetchProgress({ fetched: 0, total: allObjectIds.length, phase: "attachments" });
await tryChunk(allObjectIds, initialChunk);
return hasAtt;
};
const fetchAttributesForOids = async (oids: number[], attrChunk = 500) => {
const results: AssetFeatureInfo[] = [];
const objectIdField = assetLayer.objectIdField || "objectid";
setFetchProgress({ fetched: 0, total: oids.length, phase: "attributes" });
for (let i = 0; i < oids.length; i += attrChunk) {
const slice = oids.slice(i, i + attrChunk);
try {
const fq = assetLayer.createQuery();
fq.objectIds = slice;
fq.outFields = [
objectIdField,
"zone",
"ward",
"road_name",
"category",
"asset_code",
"asset_type",
"name_of_the_asset",
"address",
"building_status",
"status",
"ownership",
"remarks",
"created_date"
];
fq.returnGeometry = true;
const res = await assetLayer.queryFeatures(fq);
for (const feat of res.features) {
const cdVal = feat.attributes.created_date;
const createdDateStr = cdVal ? new Date(cdVal).toLocaleString() : "";
results.push({
objectid: feat.attributes[objectIdField],
zone: feat.attributes.zone || "",
ward: feat.attributes.ward || "",
road_name: feat.attributes.road_name || "",
category: feat.attributes.category || "",
asset_code: feat.attributes.asset_code || "",
asset_type: feat.attributes.asset_type || "",
name_of_the_asset: feat.attributes.name_of_the_asset || "",
address: feat.attributes.address || "",
building_status: feat.attributes.building_status || "",
status: feat.attributes.status || "",
ownership: feat.attributes.ownership || "",
remarks: feat.attributes.remarks || "",
created_date: createdDateStr,
geometry: feat.geometry
});
}
setFetchProgress((p) => ({ ...p, fetched: Math.min(p.total, p.fetched + slice.length) }));
} catch (err) {
console.error("Failed to fetch attributes for slice:", err);
if (attrChunk <= 50) throw err;
const smaller = Math.max(50, Math.floor(attrChunk / 5));
const partial = await fetchAttributesForOids(slice, smaller);
results.push(...partial);
}
}
return results;
};
const checkFeatureDetails = async () => {
setLoading(true);
setFeatures([]);
setSummary(null);
try {
let where = "1=1";
if (selectedZone && selectedZone !== "All") where += ` AND zone='${escapeSql(selectedZone)}'`;
if (selectedWard && selectedWard !== "All") where += ` AND ward='${escapeSql(selectedWard)}'`;
if (selectedCategory && selectedCategory !== "All") where += ` AND category='${escapeSql(selectedCategory)}'`;
const countQuery = assetLayer.createQuery();
countQuery.where = where;
const totalCount = await assetLayer.queryFeatureCount(countQuery);
const objectIdQuery = assetLayer.createQuery();
objectIdQuery.where = where;
const allObjectIds = await assetLayer.queryObjectIds(objectIdQuery);
if (!allObjectIds || allObjectIds.length === 0) {
setSummary({
zone: selectedZone === "All" ? "All Zones" : selectedZone || "Not selected",
ward: selectedWard === "All" ? "All Wards" : selectedWard || "Not selected",
category: selectedCategory === "All" ? "All Categories" : selectedCategory || "Not selected",
totalCount: 0,
withAttachment: 0,
withoutAttachment: 0
});
setFeatures([]);
setLoading(false);
return;
}
setFetchingDetails(true);
setFetchProgress({ fetched: 0, total: 0, phase: "" });
const hasAttachmentSet = await getOidsWithAttachments(allObjectIds, 500);
const withAttachment = hasAttachmentSet.size;
const withoutAttachment = allObjectIds.length - withAttachment;
setSummary({
zone: selectedZone === "All" ? "All Zones" : selectedZone || "Not selected",
ward: selectedWard === "All" ? "All Wards" : selectedWard || "Not selected",
category: selectedCategory === "All" ? "All Categories" : selectedCategory || "Not selected",
totalCount,
withAttachment,
withoutAttachment
});
const oidsWithout = allObjectIds.filter((oid) => !hasAttachmentSet.has(oid));
if (oidsWithout.length === 0) {
setFeatures([]);
setFetchingDetails(false);
setFetchProgress({ fetched: 0, total: 0, phase: "" });
setLoading(false);
return;
}
const featureRows = await fetchAttributesForOids(oidsWithout, 500);
setFeatures(featureRows);
setFetchingDetails(false);
setFetchProgress({ fetched: 0, total: 0, phase: "" });
} catch (err) {
console.error("Error in checkFeatureDetails:", err);
alert("Failed to compute counts / fetch details — see console for details.");
setFetchingDetails(false);
setFetchProgress({ fetched: 0, total: 0, phase: "" });
} finally {
setLoading(false);
}
};
const resetAll = async () => {
setSelectedZone("");
setSelectedWard("");
setSelectedCategory("");
setSummary(null);
setFeatures([]);
setLoading(false);
setFetchingDetails(false);
setFetchProgress({ fetched: 0, total: 0, phase: "" });
if (highlightHandle) {
try {
highlightHandle.remove();
} catch {
/* ignore */
}
setHighlightHandle(null);
}
if (jimuMapView?.view) {
try {
jimuMapView.view.graphics.removeAll();
} catch {
/* ignore */
}
}
};
const exportToExcel = () => {
if (!summary) {
alert("No report available. Run 'Show Summary & Fetch Details' first.");
return;
}
if (!features || features.length === 0) {
alert("No feature list available to export.");
return;
}
const summarySheetData = [
["Zone", summary.zone],
["Ward", summary.ward],
["Category", summary.category],
["Total Features", summary.totalCount],
["With Attachment", summary.withAttachment],
["Without Attachment", summary.withoutAttachment]
];
const featureData = sortedFeatures.map((f, i) => ({
"Sl.No": i + 1,
ObjectID: f.objectid,
Zone: f.zone,
Ward: f.ward,
Category: f.category,
"Road Name": f.road_name,
"Asset Code": f.asset_code,
"Asset Type": f.asset_type,
"Asset Name": f.name_of_the_asset,
Address: f.address,
Status: f.status,
Ownership: f.ownership,
"Building Status": f.building_status,
Remarks: f.remarks,
"Created Date": f.created_date
}));
const wb = XLSX.utils.book_new();
const wsSummary = XLSX.utils.aoa_to_sheet(summarySheetData);
const wsData = XLSX.utils.json_to_sheet(featureData);
if (featureData.length > 0) {
const headers = Object.keys(featureData[0]);
const colWidths = headers.map((h) => {
const maxLen = Math.max(h.length, ...featureData.map((r) => (r[h] ? String(r[h]).length : 0)));
return { wch: Math.min(Math.max(maxLen + 2, 10), 50) };
});
(wsData as any)["!cols"] = colWidths;
}
XLSX.utils.book_append_sheet(wb, wsSummary, "Summary");
XLSX.utils.book_append_sheet(wb, wsData, "Without_Attachments");
const zonePart = summary.zone ? safeFileName(summary.zone) : "AllZones";
const wardPart = summary.ward ? safeFileName(summary.ward) : "AllWards";
const categoryPart = summary.category ? safeFileName(summary.category) : "AllCategories";
const ts = new Date().toISOString().replace(/[:.]/g, "-");
XLSX.writeFile(wb, `Asset_WithoutAttachments_${zonePart}_${wardPart}_${categoryPart}_${ts}.xlsx`);
};
const exportToPDF = () => {
if (!summary) {
alert("No report available. Run 'Show Summary & Fetch Details' first.");
return;
}
if (!features || features.length === 0) {
alert("No feature list available to export.");
return;
}
const doc = new jsPDF({ orientation: "landscape", unit: "pt", format: "a4" });
doc.setFontSize(14);
doc.text("Asset - Features Without Attachments", 40, 40);
doc.setFontSize(10);
doc.text(`Zone: ${summary.zone}`, 40, 65);
doc.text(`Ward: ${summary.ward}`, 220, 65);
doc.text(`Category: ${summary.category}`, 400, 65);
doc.text(`Total: ${summary.totalCount}`, 590, 65);
doc.text(`Without Attachments: ${summary.withoutAttachment}`, 700, 65);
const head = [[
"Sl.No",
"ObjectID",
"Zone",
"Ward",
"Category",
"Road Name",
"Asset Code",
"Asset Type",
"Asset Name",
"Address",
"Status",
"Ownership",
"Building Status",
"Remarks",
"Created Date"
]];
const body = sortedFeatures.map((f, i) => [
i + 1,
f.objectid,
f.zone || "",
f.ward || "",
f.category || "",
f.road_name || "",
f.asset_code || "",
f.asset_type || "",
f.name_of_the_asset || "",
f.address || "",
f.status || "",
f.ownership || "",
f.building_status || "",
f.remarks || "",
f.created_date || ""
]);
(doc as any).autoTable({
startY: 85,
head,
body,
styles: { fontSize: 8 },
headStyles: { fillColor: [200, 200, 200] },
margin: { left: 20, right: 20 }
});
const zonePart = summary.zone ? safeFileName(summary.zone) : "AllZones";
const wardPart = summary.ward ? safeFileName(summary.ward) : "AllWards";
const categoryPart = summary.category ? safeFileName(summary.category) : "AllCategories";
const ts = new Date().toISOString().replace(/[:.]/g, "-");
doc.save(`Asset_WithoutAttachments_${zonePart}_${wardPart}_${categoryPart}_${ts}.pdf`);
};
const sortedFeatures = useMemo(() => {
return [...features].sort((a, b) => {
const zoneCmp = String(a.zone || "").trim().localeCompare(String(b.zone || "").trim(), undefined, {
numeric: true,
sensitivity: "base"
});
if (zoneCmp !== 0) return zoneCmp;
const wardCmp = String(a.ward || "").trim().localeCompare(String(b.ward || "").trim(), undefined, {
numeric: true,
sensitivity: "base"
});
if (wardCmp !== 0) return wardCmp;
return String(a.category || "").trim().localeCompare(String(b.category || "").trim(), undefined, {
numeric: true,
sensitivity: "base"
});
});
}, [features]);
return (
<div className="property-widget-parent">
<div className="property-widget">
<JimuMapViewComponent
useMapWidgetId={props.useMapWidgetIds?.[0]}
onActiveViewChange={activeViewChangeHandler}
/>
<div>
<label htmlFor="zoneSelect">Zone: </label>
<select
id="zoneSelect"
value={selectedZone}
onChange={(e) => {
setSelectedZone(e.target.value);
setSelectedWard("");
setSelectedCategory("");
setSummary(null);
setFeatures([]);
}}
>
<option value="">-- Select Zone --</option>
<option value="All">All Zones</option>
{zones.map((z) => (
<option key={z} value={z}>
{z}
</option>
))}
</select>
</div>
<div>
<label htmlFor="wardSelect">Ward: </label>
<select
id="wardSelect"
value={selectedWard}
onChange={(e) => {
setSelectedWard(e.target.value);
setSelectedCategory("");
setSummary(null);
setFeatures([]);
}}
disabled={!selectedZone}
>
<option value="">-- Select Ward --</option>
{selectedZone && <option value="All">All Wards</option>}
{wards.map((w) => (
<option key={w} value={w}>
{w}
</option>
))}
</select>
</div>
<div>
<label htmlFor="categorySelect">Category: </label>
<select
id="categorySelect"
value={selectedCategory}
onChange={(e) => {
setSelectedCategory(e.target.value);
setSummary(null);
setFeatures([]);
}}
disabled={!selectedWard}
>
<option value="">-- Select Category --</option>
{selectedWard && <option value="All">All Categories</option>}
{categories.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
</div>
<div style={{ marginTop: "10px", display: "flex", gap: 8, flexWrap: "wrap" }}>
<button onClick={checkFeatureDetails} disabled={loading || fetchingDetails || !selectedZone || !selectedWard || !selectedCategory}>
{loading
? "Calculating full counts..."
: fetchingDetails
? `Fetching... (${fetchProgress.phase} ${fetchProgress.fetched}/${fetchProgress.total})`
: "Show Summary & Fetch Details"}
</button>
<button onClick={resetAll} disabled={loading || fetchingDetails} type="button">
♻️ Reset
</button>
<button onClick={zoomToLayer} disabled={!jimuMapView?.view} type="button">
🔎 Zoom to Layer
</button>
<button onClick={exportToExcel} disabled={!features || features.length === 0} type="button">
⬇️ Export Excel
</button>
<button onClick={exportToPDF} disabled={!features || features.length === 0} type="button">
📄 Export PDF
</button>
</div>
{summary && (
<div style={{ marginTop: "15px", border: "1px solid #ccc", padding: "10px" }}>
<p><strong>Zone:</strong> {summary.zone}</p>
<p><strong>Ward:</strong> {summary.ward}</p>
<p><strong>Category:</strong> {summary.category}</p>
<p><strong>Total Features:</strong> {summary.totalCount}</p>
<p><strong>With Attachment:</strong> ✅ {summary.withAttachment}</p>
<p><strong>Without Attachment:</strong> ❌ {summary.withoutAttachment}</p>
{fetchingDetails && (
<p style={{ color: "#0074D9" }}>
Fetching details... Phase: {fetchProgress.phase} — {fetchProgress.fetched}/{fetchProgress.total}
</p>
)}
{!fetchingDetails && summary.withoutAttachment > 0 && (
<p style={{ color: "#666", marginTop: 8 }}>
Details fetched for {features.length} asset(s) without attachments. You can export the list using Excel or PDF.
</p>
)}
{summary.withoutAttachment === 0 && <p style={{ color: "#0A0" }}>All assets have attachments.</p>}
</div>
)}
{features.length > 0 && (
<div style={{ marginTop: "15px" }}>
<h4>Assets Without Attachments: ({features.length})</h4>
<div
style={{
maxHeight: "500px",
overflow: "auto",
position: "relative",
border: "1px solid #ccc",
borderRadius: "4px"
}}
>
<div style={{ overflowX: "auto", position: "relative" }}>
<table
style={{
borderCollapse: "collapse",
width: "100%",
minWidth: "1400px"
}}
border={1}
>
<thead
style={{
position: "sticky",
top: 0,
zIndex: 10,
backgroundColor: "#f5f5f5",
boxShadow: "0 2px 2px -1px rgba(0,0,0,0.1)"
}}
>
<tr>
<th style={{ padding: "8px", textAlign: "left", backgroundColor: "#f5f5f5", whiteSpace: "nowrap" }}>Sl.no</th>
<th style={{ padding: "8px", textAlign: "left", backgroundColor: "#f5f5f5", whiteSpace: "nowrap" }}>ObjectID</th>
<th style={{ padding: "8px", textAlign: "left", backgroundColor: "#f5f5f5", whiteSpace: "nowrap" }}>Zone</th>
<th style={{ padding: "8px", textAlign: "left", backgroundColor: "#f5f5f5", whiteSpace: "nowrap" }}>Ward</th>
<th style={{ padding: "8px", textAlign: "left", backgroundColor: "#f5f5f5", whiteSpace: "nowrap" }}>Category</th>
<th style={{ padding: "8px", textAlign: "left", backgroundColor: "#f5f5f5", whiteSpace: "nowrap" }}>Road Name</th>
<th style={{ padding: "8px", textAlign: "left", backgroundColor: "#f5f5f5", whiteSpace: "nowrap" }}>Asset Code</th>
<th style={{ padding: "8px", textAlign: "left", backgroundColor: "#f5f5f5", whiteSpace: "nowrap" }}>Action</th>
</tr>
</thead>
<tbody>
{sortedFeatures.map((f, index) => (
<tr
key={f.objectid}
style={{
backgroundColor: index % 2 === 0 ? "#fff" : "#f9f9f9",
cursor: "pointer"
}}
onClick={() => {
void handleRowZoom(f);
}}
onDoubleClick={() => {
void handleRowZoom(f);
}}
title="Click row to zoom"
>
<td style={{ padding: "8px", whiteSpace: "nowrap" }}>{index + 1}</td>
<td style={{ padding: "8px", whiteSpace: "nowrap" }}>{f.objectid}</td>
<td style={{ padding: "8px", whiteSpace: "nowrap" }}>{f.zone}</td>
<td style={{ padding: "8px", whiteSpace: "nowrap" }}>{f.ward}</td>
<td style={{ padding: "8px", whiteSpace: "nowrap" }}>{f.category}</td>
<td style={{ padding: "8px", whiteSpace: "nowrap" }}>{f.road_name}</td>
<td style={{ padding: "8px", whiteSpace: "nowrap" }}>{f.asset_code}</td>
<td style={{ padding: "8px", whiteSpace: "nowrap" }}>
<button
title="Zoom to feature"
onClick={async (e) => {
e.stopPropagation();
await handleRowZoom(f);
}}
disabled={!jimuMapView?.view}
style={{
padding: "4px 8px",
background: "#007acc",
color: "white",
border: "none",
borderRadius: "3px",
cursor: "pointer"
}}
>
📍
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div
style={{
marginTop: "10px",
fontSize: "0.9em",
color: "#666",
display: "flex",
justifyContent: "space-between",
alignItems: "center"
}}
>
<span>Scroll vertically and horizontally to view all data</span>
<span>Total rows: {features.length}</span>
</div>
</div>
)}
{summary && features.length === 0 && summary.totalCount > 0 && (
<p style={{ marginTop: "10px" }}>All assets have attachments or the detailed list could not be fetched. ✅</p>
)}
{summary && summary.totalCount === 0 && <p style={{ marginTop: "10px" }}>No assets found for the selected criteria.</p>}
</div>
</div>
);
};
export default AssetAttachmentStatus;

8
Asset_Updation_Attachment_status/src/runtime/widget.tsx

@ -0,0 +1,8 @@
import { React, AllWidgetProps } from 'jimu-core';
import ZoneAbstractWidget from '../index';
const Widget = (props: AllWidgetProps<any>) => {
return <ZoneAbstractWidget {...props} />;
};
export default Widget;

24
Asset_Updation_Attachment_status/src/styles.css

@ -0,0 +1,24 @@
.widget-container {
padding: 15px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
h3 {
font-size: 18px;
margin-bottom: 10px;
}
.export-button {
margin-top: 10px;
background-color: #0078d4;
color: white;
}
.table-container {
max-height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
}

14
Asset_Updation_Attachment_status/tests/simple-widget.test.tsx

@ -0,0 +1,14 @@
import { React } from 'jimu-core'
import _Widget from '../src/runtime/widget'
import { widgetRender, wrapWidget } from 'jimu-for-test'
const render = widgetRender()
describe('test simple widget', () => {
it('simple test', () => {
const Widget = wrapWidget(_Widget, {
config: { exampleConfigProperty: 'a' }
})
const { queryByText } = render(<Widget widgetId="Widget_1" />)
expect(queryByText('exampleConfigProperty: a').tagName).toBe('P')
})
})

3
Asset_Updation_Summary/config.json

@ -0,0 +1,3 @@
{
"exampleConfigProperty": "test"
}

26
Asset_Updation_Summary/icon.svg

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 179.004 179.004" xml:space="preserve">
<g>
<path style="fill:#010002;" d="M127.124,127.773c0,0.77,0,1.504,0,2.22c4.075,1.671,7.769,2.685,10.472,1.939
c5.955-1.635,10.705-8.169,15.293-14.488c3.574-4.905,6.951-9.517,11.116-12.22c0-30.419,0-70.212,0-83.286
c-0.018,0.012-0.036,0.024-0.054,0.036c-0.519-4.344-15.365-4.571-18.396-4.571c-3.025,0-17.871,0.227-18.402,4.571
c-0.012-0.012-0.03-0.024-0.042-0.036c0,12.059,0,46.858,0,76.048c6.128,0.37,15.943,2.387,17.531,13.3
c0.537,3.61-0.316,6.922-2.446,9.553C139.064,124.73,133.556,126.675,127.124,127.773z M130.221,22.213
c1.575-0.835,6.808-2.047,15.335-2.047c8.539,0,13.778,1.211,15.335,2.047c-1.569,0.829-6.802,2.041-15.335,2.041
C137.029,24.248,131.796,23.036,130.221,22.213z M114.242,98.326c1.456-0.095,2.948-0.185,4.439-0.269c0-20.544,0-42.979,0-54.304
c-0.018,0.012-0.036,0.012-0.054,0.024c-0.519-4.332-15.365-4.559-18.39-4.559c-3.025,0-17.877,0.227-18.396,4.559
c-0.012-0.012-0.036-0.012-0.048-0.024c0,10.663,0,31.195,0,50.706c0.853,0.197,1.701,0.4,2.566,0.615
c5.967,1.438,12.131,2.924,18.121,3.485C105.948,98.893,110.071,98.589,114.242,98.326z M84.903,44.004
c1.575-0.841,6.82-2.035,15.341-2.035c8.533,0,13.783,1.193,15.335,2.035c-1.563,0.841-6.808,2.047-15.335,2.047
C91.723,46.051,86.478,44.845,84.903,44.004z M73.375,92.646c0-8.926,0-12.703,0-27.096c-0.018,0.018-0.036,0.024-0.054,0.036
c-0.519-4.338-15.365-4.571-18.396-4.571c-3.019,0-17.877,0.233-18.396,4.571c-0.006-0.012-0.03-0.018-0.042-0.036
c0,17.93,0,19.392,0,34.876c6.981-4.266,14.404-7.584,22.316-8.592C63.398,91.25,68.285,91.727,73.375,92.646z M54.931,67.848
c-8.533,0-13.778-1.205-15.335-2.053c1.575-0.829,6.814-2.029,15.335-2.029c8.539,0,13.778,1.199,15.341,2.029
C68.703,66.642,63.464,67.848,54.931,67.848z M0,131.639c15.812-6.689,35.962-32.436,59.37-35.443
c12.16-1.569,27.489,5.334,42.711,6.749c13.39,1.247,36.058-5.854,38.218,8.992c1.88,12.859-20.806,12.16-35.968,13.497
c11.856,2.363,24.661,13.449,34.435,10.758c12.59-3.479,19.13-22.083,27.907-27.477c9.929-6.104,13.777-2.166,11.85,5.49
c-4.791,19.082-13.008,36.708-29.22,42.705c-42.299,15.645-87.886-14.452-119.809,4.242L0,131.639z"/>
</g>
</svg>

29
Asset_Updation_Summary/manifest.json

@ -0,0 +1,29 @@
{
"name": "Asset_Updation_Summary",
"label": "Asset_Updation_Summary",
"type": "widget",
"version": "1.16.0",
"exbVersion": "1.16.0",
"author": "Esri R&D Center Beijing",
"description": "This is a barebones, simple widget. It includes an example of how to use the config file, and it includes a placeholder SVG icon. It intentionally does not include the following so you can use this to start from scratch: i18n translations, a Settings panel, unit tests. See the documentation for more information on how to implement these features.",
"copyright": "",
"license": "http://www.apache.org/licenses/LICENSE-2.0",
"properties": {
"hasSettingPage": false
},
"widgets": [
{
"uri": "Asset_Updation_Summary",
"label": "Asset_Updation_Summary",
"thumbnail": "thumbnail.png",
"description": "Summarizes Asset and Road."
}
],
"framework": "react",
"translatedLocales": ["en"],
"dataSources": [],
"elements": []
}

7
Asset_Updation_Summary/src/config.ts

@ -0,0 +1,7 @@
import { type ImmutableObject } from 'seamless-immutable'
export interface Config {
exampleConfigProperty: string
}
export type IMConfig = ImmutableObject<Config>

381
Asset_Updation_Summary/src/index.tsx

@ -0,0 +1,381 @@
import React, { useEffect, useState } from 'react';
import FeatureLayer from '@arcgis/core/layers/FeatureLayer';
import {
Table, TableBody, TableCell, TableContainer,
TableHead, TableRow, Paper, Select, MenuItem, Button, Grid
} from '@mui/material';
import * as XLSX from 'xlsx';
import jsPDF from "jspdf";
import autoTable from "jspdf-autotable";
const CombinedZoneWardSummary = () => {
const [zone, setZone] = useState('');
const [ward, setWard] = useState('');
const [zoneList, setZoneList] = useState<string[]>([]);
const [wardList, setWardList] = useState<string[]>([]);
// 1) Zone + Ward + Category wise
const [detailedData, setDetailedData] = useState<any[]>([]);
// 2) Zone total count
const [zoneTotalSummary, setZoneTotalSummary] = useState<any[]>([]);
// 3) Ward total count
const [wardTotalSummary, setWardTotalSummary] = useState<any[]>([]);
// 4) Zone + Category wise count
const [zoneCategorySummary, setZoneCategorySummary] = useState<any[]>([]);
const assetLayerUrl =
"https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/Asset_2026_final/FeatureServer/0";
useEffect(() => {
fetchZoneList();
}, []);
useEffect(() => {
if (zone) fetchWardList(zone);
else setWardList([]);
}, [zone]);
const fetchZoneList = async () => {
try {
const layer = new FeatureLayer({ url: assetLayerUrl });
const query = layer.createQuery();
query.outFields = ["zone"];
query.returnDistinctValues = true;
query.returnGeometry = false;
const result = await layer.queryFeatures(query);
const zones = [...new Set(result.features.map(f => f.attributes.zone).filter(Boolean))].sort();
setZoneList(zones as string[]);
} catch (err) {
console.error('fetchZoneList error', err);
}
};
const fetchWardList = async (zoneNo: string) => {
try {
const layer = new FeatureLayer({ url: assetLayerUrl });
const query = layer.createQuery();
query.where = `zone = '${zoneNo}'`;
query.outFields = ["ward"];
query.returnDistinctValues = true;
query.returnGeometry = false;
const result = await layer.queryFeatures(query);
const wards = [...new Set(result.features.map(f => f.attributes.ward).filter(Boolean))].sort();
setWardList(wards as string[]);
} catch (err) {
console.error('fetchWardList error', err);
}
};
const handleFetch = async () => {
if (!zone) return;
await fetchAssetSummaries(zone, ward);
};
const handleClear = () => {
setZone('');
setWard('');
setWardList([]);
setDetailedData([]);
setZoneTotalSummary([]);
setWardTotalSummary([]);
setZoneCategorySummary([]);
};
const fetchAssetSummaries = async (zoneNo: string, wardNo: string) => {
try {
const layer = new FeatureLayer({ url: assetLayerUrl });
const query = layer.createQuery();
query.where = `zone='${zoneNo}'` + (wardNo ? ` AND ward='${wardNo}'` : "");
query.outFields = ['zone', 'ward', 'category'];
query.returnGeometry = false;
const result = await layer.queryFeatures(query);
const detailedMap: Record<string, number> = {};
const zoneMap: Record<string, number> = {};
const wardMap: Record<string, number> = {};
const zoneCategoryMap: Record<string, number> = {};
result.features.forEach(({ attributes }) => {
const z = String(attributes.zone || '').trim();
const w = String(attributes.ward || '').trim();
const c = String(attributes.category || '').trim();
const detailedKey = `${z}|${w}|${c}`;
const zoneKey = `${z}`;
const wardKey = `${z}|${w}`;
const zoneCategoryKey = `${z}|${c}`;
detailedMap[detailedKey] = (detailedMap[detailedKey] || 0) + 1;
zoneMap[zoneKey] = (zoneMap[zoneKey] || 0) + 1;
wardMap[wardKey] = (wardMap[wardKey] || 0) + 1;
zoneCategoryMap[zoneCategoryKey] = (zoneCategoryMap[zoneCategoryKey] || 0) + 1;
});
const detailed = Object.entries(detailedMap)
.map(([key, count]) => {
const [z, w, c] = key.split('|');
return { zone: z, ward: w, category: c, count };
})
.sort((a, b) => (a.zone + a.ward + a.category).localeCompare(b.zone + b.ward + b.category));
const zoneSummary = Object.entries(zoneMap)
.map(([zone, count]) => ({ zone, count }))
.sort((a, b) => a.zone.localeCompare(b.zone));
const wardSummary = Object.entries(wardMap)
.map(([key, count]) => {
const [z, w] = key.split('|');
return { zone: z, ward: w, count };
})
.sort((a, b) => (a.zone + a.ward).localeCompare(b.zone + b.ward));
const zoneCategory = Object.entries(zoneCategoryMap)
.map(([key, count]) => {
const [z, c] = key.split('|');
return { zone: z, category: c, count };
})
.sort((a, b) => (a.zone + a.category).localeCompare(b.zone + b.category));
setDetailedData(detailed);
setZoneTotalSummary(zoneSummary);
setWardTotalSummary(wardSummary);
setZoneCategorySummary(zoneCategory);
} catch (err) {
console.error('fetchAssetSummaries error', err);
setDetailedData([]);
setZoneTotalSummary([]);
setWardTotalSummary([]);
setZoneCategorySummary([]);
}
};
const exportExcel = () => {
const wb = XLSX.utils.book_new();
const detailedSheetData = [["Sl.No", "Zone", "Ward", "Category", "Count"]];
detailedData.forEach((r, i) => detailedSheetData.push([i + 1, r.zone, r.ward, r.category, r.count]));
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(detailedSheetData), 'ZoneWardCategory');
const zoneCategorySheetData = [["Sl.No", "Zone", "Category", "Total Count"]];
zoneCategorySummary.forEach((r, i) => zoneCategorySheetData.push([i + 1, r.zone, r.category, r.count]));
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(zoneCategorySheetData), 'ZoneCategory');
const zoneSheetData = [["Sl.No", "Zone", "Total Count"]];
zoneTotalSummary.forEach((r, i) => zoneSheetData.push([i + 1, r.zone, r.count]));
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(zoneSheetData), 'ZoneTotal');
const wardSheetData = [["Sl.No", "Zone", "Ward", "Total Count"]];
wardTotalSummary.forEach((r, i) => wardSheetData.push([i + 1, r.zone, r.ward, r.count]));
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(wardSheetData), 'WardTotal');
XLSX.writeFile(wb, `Asset_Summary_${zone || 'All'}.xlsx`);
};
const exportPDF = () => {
const doc = new jsPDF({ orientation: "portrait", unit: "pt", format: "a4" });
doc.setFontSize(14);
doc.text(`Zone + Ward + Category Wise Count`, 40, 40);
autoTable(doc, {
head: [["Sl.No", "Zone", "Ward", "Category", "Count"]],
body: detailedData.map((r, i) => [i + 1, r.zone, r.ward, r.category, r.count]),
startY: 60,
styles: { fontSize: 8 },
});
doc.addPage();
doc.text(`Zone + Category Wise Count`, 40, 40);
autoTable(doc, {
head: [["Sl.No", "Zone", "Category", "Total Count"]],
body: zoneCategorySummary.map((r, i) => [i + 1, r.zone, r.category, r.count]),
startY: 60,
styles: { fontSize: 8 },
});
doc.addPage();
doc.text(`Zone Total Count`, 40, 40);
autoTable(doc, {
head: [["Sl.No", "Zone", "Total Count"]],
body: zoneTotalSummary.map((r, i) => [i + 1, r.zone, r.count]),
startY: 60,
styles: { fontSize: 8 },
});
doc.addPage();
doc.text(`Ward Total Count`, 40, 40);
autoTable(doc, {
head: [["Sl.No", "Zone", "Ward", "Total Count"]],
body: wardTotalSummary.map((r, i) => [i + 1, r.zone, r.ward, r.count]),
startY: 60,
styles: { fontSize: 8 },
});
doc.save(`Asset_Summary_${zone || 'All'}.pdf`);
};
return (
<div style={{ padding: '1rem', height: '100%', overflow: 'hidden' }}>
<h2>Zone / Ward / Category Summary</h2>
<div style={{ marginBottom: '10px', fontWeight: 600 }}>
Total Assets: {detailedData.reduce((s, r) => s + r.count, 0)}
</div>
<Grid container spacing={2} alignItems="center">
<Grid item>
<Select
value={zone}
onChange={e => setZone(e.target.value)}
displayEmpty
style={{ width: 150 }}
>
<MenuItem value="">Select Zone</MenuItem>
{zoneList.map(z => <MenuItem key={z} value={z}>{z}</MenuItem>)}
</Select>
</Grid>
<Grid item>
<Select
value={ward}
onChange={e => setWard(e.target.value)}
displayEmpty
style={{ width: 150 }}
disabled={!zone}
>
<MenuItem value="">All Wards</MenuItem>
{wardList.map(w => <MenuItem key={w} value={w}>{w}</MenuItem>)}
</Select>
</Grid>
<Grid item>
<Button variant="contained" onClick={handleFetch}>FETCH DATA</Button>
</Grid>
<Grid item>
<Button variant="outlined" onClick={handleClear}>CLEAR</Button>
</Grid>
<Grid item>
<Button variant="contained" onClick={exportExcel} disabled={detailedData.length === 0}>
Export Excel
</Button>
</Grid>
<Grid item>
<Button variant="contained" onClick={exportPDF} disabled={detailedData.length === 0}>
Export PDF
</Button>
</Grid>
</Grid>
{detailedData.length > 0 && (
<TableContainer component={Paper} style={{ marginTop: '20px', maxHeight: 300, overflow: 'auto' }}>
<Table size="small" stickyHeader>
<TableHead>
<TableRow>
<TableCell><strong>Sl.No</strong></TableCell>
<TableCell><strong>Zone</strong></TableCell>
<TableCell><strong>Ward</strong></TableCell>
<TableCell><strong>Category</strong></TableCell>
<TableCell><strong>Count</strong></TableCell>
</TableRow>
</TableHead>
<TableBody>
{detailedData.map((r, i) => (
<TableRow key={i}>
<TableCell>{i + 1}</TableCell>
<TableCell>{r.zone}</TableCell>
<TableCell>{r.ward}</TableCell>
<TableCell>{r.category}</TableCell>
<TableCell>{r.count}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{zoneCategorySummary.length > 0 && (
<TableContainer component={Paper} style={{ marginTop: '20px', maxHeight: 300, overflow: 'auto' }}>
<Table size="small" stickyHeader>
<TableHead>
<TableRow>
<TableCell><strong>Sl.No</strong></TableCell>
<TableCell><strong>Zone</strong></TableCell>
<TableCell><strong>Category</strong></TableCell>
<TableCell><strong>Total Count</strong></TableCell>
</TableRow>
</TableHead>
<TableBody>
{zoneCategorySummary.map((r, i) => (
<TableRow key={i}>
<TableCell>{i + 1}</TableCell>
<TableCell>{r.zone}</TableCell>
<TableCell>{r.category}</TableCell>
<TableCell>{r.count}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{zoneTotalSummary.length > 0 && (
<TableContainer component={Paper} style={{ marginTop: '20px', maxHeight: 300, overflow: 'auto' }}>
<Table size="small" stickyHeader>
<TableHead>
<TableRow>
<TableCell><strong>Sl.No</strong></TableCell>
<TableCell><strong>Zone</strong></TableCell>
<TableCell><strong>Total Count</strong></TableCell>
</TableRow>
</TableHead>
<TableBody>
{zoneTotalSummary.map((r, i) => (
<TableRow key={i}>
<TableCell>{i + 1}</TableCell>
<TableCell>{r.zone}</TableCell>
<TableCell>{r.count}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{wardTotalSummary.length > 0 && (
<TableContainer component={Paper} style={{ marginTop: '20px', maxHeight: 300, overflow: 'auto' }}>
<Table size="small" stickyHeader>
<TableHead>
<TableRow>
<TableCell><strong>Sl.No</strong></TableCell>
<TableCell><strong>Zone</strong></TableCell>
<TableCell><strong>Ward</strong></TableCell>
<TableCell><strong>Total Count</strong></TableCell>
</TableRow>
</TableHead>
<TableBody>
{wardTotalSummary.map((r, i) => (
<TableRow key={i}>
<TableCell>{i + 1}</TableCell>
<TableCell>{r.zone}</TableCell>
<TableCell>{r.ward}</TableCell>
<TableCell>{r.count}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
);
};
export default CombinedZoneWardSummary;

8
Asset_Updation_Summary/src/runtime/widget.tsx

@ -0,0 +1,8 @@
import { React, AllWidgetProps } from 'jimu-core';
import ZoneAbstractWidget from '../index';
const Widget = (props: AllWidgetProps<any>) => {
return <ZoneAbstractWidget {...props} />;
};
export default Widget;

24
Asset_Updation_Summary/src/styles.css

@ -0,0 +1,24 @@
.widget-container {
padding: 15px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
h3 {
font-size: 18px;
margin-bottom: 10px;
}
.export-button {
margin-top: 10px;
background-color: #0078d4;
color: white;
}
.table-container {
max-height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
}

14
Asset_Updation_Summary/tests/simple-widget.test.tsx

@ -0,0 +1,14 @@
import { React } from 'jimu-core'
import _Widget from '../src/runtime/widget'
import { widgetRender, wrapWidget } from 'jimu-for-test'
const render = widgetRender()
describe('test simple widget', () => {
it('simple test', () => {
const Widget = wrapWidget(_Widget, {
config: { exampleConfigProperty: 'a' }
})
const { queryByText } = render(<Widget widgetId="Widget_1" />)
expect(queryByText('exampleConfigProperty: a').tagName).toBe('P')
})
})

3
Attachment_ShopComplex/config.json

@ -0,0 +1,3 @@
{
"exampleConfigProperty": "test"
}

2
Attachment_ShopComplex/icon.svg

@ -0,0 +1,2 @@
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 32 32" data-name="Layer 1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style>.cls-1{fill:url(#linear-gradient);}.cls-2{fill:url(#linear-gradient-2);}.cls-3{fill:#f8edeb;}.cls-4{fill:#577590;}</style><linearGradient gradientUnits="userSpaceOnUse" id="linear-gradient" x1="4" x2="25" y1="16" y2="16"><stop offset="0" stop-color="#f9dcc4"/><stop offset="0.01" stop-color="#f8dbc2"/><stop offset="0.12" stop-color="#f3ceb1"/><stop offset="0.26" stop-color="#f0c5a6"/><stop offset="0.46" stop-color="#eebf9f"/><stop offset="1" stop-color="#edbe9d"/></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="linear-gradient-2" x1="7.48" x2="27.52" y1="8.98" y2="29.02"><stop offset="0" stop-color="#f9dcc4"/><stop offset="0.32" stop-color="#f8d9c0"/><stop offset="0.64" stop-color="#f4cfb3"/><stop offset="0.98" stop-color="#eebf9f"/><stop offset="1" stop-color="#edbe9d"/></linearGradient></defs><rect class="cls-1" height="22" rx="2.5" width="21" x="4" y="5"/><rect class="cls-2" height="22" rx="2.5" width="21" x="7" y="8"/><path class="cls-3" d="M20,2a5,5,0,0,0-5,5v5a2,2,0,0,0,3,1.73V15.5a3.5,3.5,0,0,0,7,0V7A5,5,0,0,0,20,2Z"/><path class="cls-4" d="M20,2a5,5,0,0,0-5,5v4.5a.5.5,0,0,0,1,0V7a4,4,0,0,1,8,0v8.5a2.5,2.5,0,0,1-5,0V7a1,1,0,0,1,2,0v8.5a.5.5,0,0,0,1,0V7a2,2,0,0,0-4,0v8.5a3.5,3.5,0,0,0,7,0V7A5,5,0,0,0,20,2Z"/></svg>

29
Attachment_ShopComplex/manifest.json

@ -0,0 +1,29 @@
{
"name": "Attachment_ShopComplex",
"label": "Attachment_ShopComplex",
"type": "widget",
"version": "1.16.0",
"exbVersion": "1.16.0",
"author": "Tanii Team Developer",
"description": "This is a barebones, simple widget. It includes an example of how to use the config file, and it includes a placeholder SVG icon. It intentionally does not include the following so you can use this to start from scratch: i18n translations, a Settings panel, unit tests. See the documentation for more information on how to implement these features.",
"copyright": "",
"license": "http://www.apache.org/licenses/LICENSE-2.0",
"properties": {
"hasSettingPage": false
},
"widgets": [
{
"uri": "Attachment_ShopComplex",
"label": "Attachment_ShopComplex",
"thumbnail": "thumbnail.png",
"description": "Attachment Available Status."
}
],
"framework": "react",
"translatedLocales": ["en"],
"dataSources": [],
"elements": []
}

7
Attachment_ShopComplex/src/config.ts

@ -0,0 +1,7 @@
import { type ImmutableObject } from 'seamless-immutable'
export interface Config {
exampleConfigProperty: string
}
export type IMConfig = ImmutableObject<Config>

482
Attachment_ShopComplex/src/index.tsx

@ -0,0 +1,482 @@
import React, { useState, useEffect, useMemo } from "react";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import { JimuMapViewComponent } from "jimu-arcgis";
import { type AllWidgetProps } from "jimu-core";
import * as XLSX from "xlsx";
import { saveAs } from "file-saver";
import jsPDF from "jspdf";
import autoTable from "jspdf-autotable";
interface SummaryInfo {
zone: string;
ward: string;
totalCount: number;
attachedCount: number;
withoutAttachmentCount: number;
}
interface FeatureInfo {
objectid: number;
zone_no: string;
ward_no: string;
shopping_complex_name?: string;
shopping_complex_no?: string;
road_name?: string;
address?: string;
category?: string;
attachmentCount: number;
attachmentStatus: "Attached" | "No Attachment";
}
const SHOPPING_COMPLEX_URL =
"https://gisgcc.chennaicorporation.gov.in/server/rest/services/gccdeptfield/Shopping_Complex_Shop/FeatureServer/1";
const chunkArray = <T,>(arr: T[], size: number): T[][] =>
arr.reduce((acc: T[][], val, i) => {
const idx = Math.floor(i / size);
acc[idx] = acc[idx] || [];
acc[idx].push(val);
return acc;
}, []);
const ShoppingComplexAttachmentStatus = (props: AllWidgetProps<any>) => {
const zonesList = useMemo(() => {
const zones = ["All"];
for (let i = 1; i <= 15; i++) zones.push(String(i).padStart(2, "0"));
return zones;
}, []);
const [zones] = useState<string[]>(zonesList);
const [wards, setWards] = useState<string[]>([]);
const [selectedZone, setSelectedZone] = useState<string>("All");
const [selectedWard, setSelectedWard] = useState<string>("All");
const [summary, setSummary] = useState<SummaryInfo | null>(null);
const [features, setFeatures] = useState<FeatureInfo[]>([]);
const [loading, setLoading] = useState(false);
const shoppingLayer = useMemo(
() =>
new FeatureLayer({
url: SHOPPING_COMPLEX_URL
}),
[]
);
useEffect(() => {
const loadWards = async () => {
try {
let where = "ward_no IS NOT NULL";
if (selectedZone && selectedZone !== "All") {
where += ` AND zone_no='${selectedZone}'`;
}
const q = shoppingLayer.createQuery();
q.outFields = ["ward_no"];
q.where = where;
q.returnGeometry = false;
const result = await shoppingLayer.queryFeatures(q);
const distinctWards = [
...new Set(
result.features
.map(f => String(f.attributes.ward_no ?? "").trim())
.filter(Boolean)
)
];
const sorted = distinctWards.slice().sort((a, b) => {
const na = Number(a);
const nb = Number(b);
if (!isNaN(na) && !isNaN(nb)) return na - nb;
return a.localeCompare(b);
});
setWards(sorted);
} catch (err) {
console.error("loadWards error:", err);
setWards([]);
}
};
loadWards();
}, [selectedZone, shoppingLayer]);
const checkFeatureDetails = async () => {
setLoading(true);
try {
let where = "1=1";
if (selectedZone && selectedZone !== "All") {
where += ` AND zone_no='${selectedZone}'`;
}
if (selectedWard && selectedWard !== "All") {
where += ` AND ward_no='${selectedWard}'`;
}
const qObj = shoppingLayer.createQuery();
qObj.where = where;
qObj.returnGeometry = false;
const allObjectIds = await shoppingLayer.queryObjectIds(qObj);
if (!allObjectIds || allObjectIds.length === 0) {
setSummary({
zone: selectedZone || "All Zones",
ward: selectedWard || "All Wards",
totalCount: 0,
attachedCount: 0,
withoutAttachmentCount: 0
});
setFeatures([]);
return;
}
const batchSize = 1000;
const idChunks = chunkArray(allObjectIds, batchSize);
const fetchedFeatures: __esri.Graphic[] = [];
for (const chunk of idChunks) {
const q = shoppingLayer.createQuery();
q.objectIds = chunk;
q.outFields = [
"objectid",
"zone_no",
"ward_no",
"shopping_complex_name",
"shopping_complex_no",
"road_name",
"address",
"category"
];
q.returnGeometry = false;
const res = await shoppingLayer.queryFeatures(q);
fetchedFeatures.push(...res.features);
}
const featureRows: FeatureInfo[] = fetchedFeatures.map(f => {
const objectid =
Number(f.attributes.objectid ?? f.attributes.OBJECTID ?? f.attributes.ObjectId) || 0;
return {
objectid,
zone_no: String(f.attributes.zone_no ?? ""),
ward_no: String(f.attributes.ward_no ?? ""),
shopping_complex_name: f.attributes.shopping_complex_name,
shopping_complex_no: f.attributes.shopping_complex_no,
road_name: f.attributes.road_name,
address: f.attributes.address,
category: f.attributes.category,
attachmentCount: 0,
attachmentStatus: "No Attachment"
};
});
const attachMap: Record<string, any[]> = {};
const attachChunks = chunkArray(
featureRows.map(f => f.objectid),
500
);
for (const chunk of attachChunks) {
const res = await shoppingLayer.queryAttachments({ objectIds: chunk });
Object.assign(attachMap, res);
}
let attachedCount = 0;
let withoutAttachmentCount = 0;
const mergedRows = featureRows.map(f => {
const attachments = attachMap[String(f.objectid)] ?? attachMap[f.objectid] ?? [];
const attachmentCount = Array.isArray(attachments) ? attachments.length : 0;
const attachmentStatus = attachmentCount > 0 ? "Attached" : "No Attachment";
if (attachmentStatus === "Attached") attachedCount++;
else withoutAttachmentCount++;
return {
...f,
attachmentCount,
attachmentStatus
};
});
setSummary({
zone: selectedZone || "All Zones",
ward: selectedWard || "All Wards",
totalCount: allObjectIds.length,
attachedCount,
withoutAttachmentCount
});
setFeatures(mergedRows);
} catch (err) {
console.error("Error in checkFeatureDetails:", err);
} finally {
setLoading(false);
}
};
const exportToExcel = () => {
try {
const summaryRows = [
["Zone", summary ? summary.zone : selectedZone || "All Zones"],
["Ward", summary ? summary.ward : selectedWard || "All Wards"],
["Total Shops", summary ? summary.totalCount : 0],
["With Attachment", summary ? summary.attachedCount : 0],
["Without Attachment", summary ? summary.withoutAttachmentCount : 0]
];
const detailHeaders = [
"Sl.no",
"ObjectId",
"Zone",
"Ward",
"Complex Name",
"Complex No",
"Road Name",
"Address",
"Category",
"Attachment Count",
"Attachment Status"
];
const detailRows = features.map((f, idx) => [
idx + 1,
f.objectid,
f.zone_no,
f.ward_no,
f.shopping_complex_name || "",
f.shopping_complex_no || "",
f.road_name || "",
f.address || "",
f.category || "",
f.attachmentCount,
f.attachmentStatus
]);
const wb = XLSX.utils.book_new();
const wsSummary = XLSX.utils.aoa_to_sheet([
["Shopping Complex Attachment Status"],
[],
...summaryRows
]);
XLSX.utils.book_append_sheet(wb, wsSummary, "Summary");
const wsDetails = XLSX.utils.aoa_to_sheet([detailHeaders, ...detailRows]);
XLSX.utils.book_append_sheet(wb, wsDetails, "Details");
const wbout = XLSX.write(wb, { bookType: "xlsx", type: "array" });
saveAs(
new Blob([wbout], { type: "application/octet-stream" }),
`Shopping_Complex_Attachment_Status_${selectedZone || "All"}.xlsx`
);
} catch (err) {
console.error("exportToExcel error:", err);
}
};
const exportToPDF = () => {
try {
const doc = new jsPDF("l", "pt", "a4");
doc.setFontSize(14);
doc.text("Shopping Complex Attachment Status", 40, 40);
const summaryLines = [
`Zone: ${summary ? summary.zone : selectedZone || "All Zones"}`,
`Ward: ${summary ? summary.ward : selectedWard || "All Wards"}`,
`Total Shops: ${summary ? summary.totalCount : 0}`,
`With Attachment: ${summary ? summary.attachedCount : 0}`,
`Without Attachment: ${summary ? summary.withoutAttachmentCount : 0}`
];
doc.setFontSize(10);
let y = 60;
summaryLines.forEach(line => {
doc.text(line, 40, y);
y += 14;
});
const headers = [
"Sl.no",
"ObjectId",
"Zone",
"Ward",
"Complex Name",
"Complex No",
"Road Name",
"Address",
"Category",
"Attachment Count",
"Attachment Status"
];
const body = features.map((f, idx) => [
idx + 1,
f.objectid,
f.zone_no,
f.ward_no,
f.shopping_complex_name || "",
f.shopping_complex_no || "",
f.road_name || "",
f.address || "",
f.category || "",
f.attachmentCount,
f.attachmentStatus
]);
if (body.length === 0) {
doc.text("No records found.", 40, y + 10);
} else {
autoTable(doc, {
startY: y + 6,
head: [headers],
body,
styles: { fontSize: 8 },
headStyles: { fillColor: [200, 200, 200] }
});
}
doc.save(`Shopping_Complex_Attachment_Status_${selectedZone || "All"}.pdf`);
} catch (err) {
console.error("exportToPDF error:", err);
}
};
return (
<div className="property-widget-parent">
<div className="property-widget" style={{ padding: 8 }}>
<JimuMapViewComponent useMapWidgetId={props.useMapWidgetIds?.[0]} />
<div style={{ marginTop: 8 }}>
<label>Zone: </label>
<select
value={selectedZone}
onChange={e => {
setSelectedZone(e.target.value);
setSelectedWard("All");
setSummary(null);
setFeatures([]);
}}
>
{zones.map(z => (
<option key={z} value={z}>
{z === "All" ? "All Zones" : z}
</option>
))}
</select>
</div>
<div style={{ marginTop: 8 }}>
<label>Ward: </label>
<select
value={selectedWard}
onChange={e => {
setSelectedWard(e.target.value);
setSummary(null);
setFeatures([]);
}}
>
<option value="All">All Wards</option>
{wards.map(w => (
<option key={w} value={w}>
{w}
</option>
))}
</select>
</div>
<div style={{ marginTop: 10 }}>
<button onClick={checkFeatureDetails} disabled={loading}>
{loading ? "Loading..." : "Show Summary"}
</button>
<button
onClick={exportToExcel}
disabled={loading || features.length === 0}
style={{ marginLeft: 8 }}
title="Export summary & details to Excel"
>
Export Excel
</button>
<button
onClick={exportToPDF}
disabled={loading || features.length === 0}
style={{ marginLeft: 8 }}
title="Export summary & details to PDF"
>
Export PDF
</button>
</div>
{summary && (
<div style={{ marginTop: "15px", border: "1px solid #ccc", padding: "10px" }}>
<p>
<strong>Zone:</strong> {summary.zone}
</p>
<p>
<strong>Ward:</strong> {summary.ward}
</p>
<p>
<strong>Total Shops:</strong> {summary.totalCount}
</p>
<p>
<strong>With Attachment:</strong> ✅ {summary.attachedCount}
</p>
<p>
<strong>Without Attachment:</strong> ❌ {summary.withoutAttachmentCount}
</p>
</div>
)}
{features.length > 0 && (
<div style={{ marginTop: "15px" }}>
<h4>Shopping Complex Attachment Status:</h4>
<div style={{ maxHeight: "400px", overflow: "auto" }}>
<table style={{ borderCollapse: "collapse", width: "100%" }} border={1}>
<thead>
<tr>
<th>Sl.no</th>
<th>Zone</th>
<th>Ward</th>
<th>Complex Name</th>
<th>Complex No</th>
<th>Road Name</th>
<th>Address</th>
<th>Category</th>
<th>Attachment Count</th>
<th>Attachment Status</th>
</tr>
</thead>
<tbody>
{features.map((f, index) => (
<tr key={f.objectid}>
<td>{index + 1}</td>
<td>{f.zone_no}</td>
<td>{f.ward_no}</td>
<td>{f.shopping_complex_name}</td>
<td>{f.shopping_complex_no}</td>
<td>{f.road_name}</td>
<td>{f.address}</td>
<td>{f.category}</td>
<td>{f.attachmentCount}</td>
<td>{f.attachmentStatus}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</div>
</div>
);
};
export default ShoppingComplexAttachmentStatus;

8
Attachment_ShopComplex/src/runtime/widget.tsx

@ -0,0 +1,8 @@
import { React, AllWidgetProps } from 'jimu-core';
import ZoneAbstractWidget from '../index';
const Widget = (props: AllWidgetProps<any>) => {
return <ZoneAbstractWidget {...props} />;
};
export default Widget;

24
Attachment_ShopComplex/src/styles.css

@ -0,0 +1,24 @@
.widget-container {
padding: 15px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
h3 {
font-size: 18px;
margin-bottom: 10px;
}
.export-button {
margin-top: 10px;
background-color: #0078d4;
color: white;
}
.table-container {
max-height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
}

14
Attachment_ShopComplex/tests/simple-widget.test.tsx

@ -0,0 +1,14 @@
import { React } from 'jimu-core'
import _Widget from '../src/runtime/widget'
import { widgetRender, wrapWidget } from 'jimu-for-test'
const render = widgetRender()
describe('test simple widget', () => {
it('simple test', () => {
const Widget = wrapWidget(_Widget, {
config: { exampleConfigProperty: 'a' }
})
const { queryByText } = render(<Widget widgetId="Widget_1" />)
expect(queryByText('exampleConfigProperty: a').tagName).toBe('P')
})
})

5
Attribute_Transfer/config.json

@ -0,0 +1,5 @@
{
"exampleConfigProperty": "test",
"useMapWidget": true
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save