Browse Source

Custom_Widgets

exp1.15
Anitha_M 4 weeks ago
parent
commit
e465c1261f
  1. 1
      Form7ApprovalPending/config.json
  2. 1
      Form7ApprovalPending/icon.svg
  3. 29
      Form7ApprovalPending/manifest.json
  4. 7
      Form7ApprovalPending/src/config.ts
  5. 1317
      Form7ApprovalPending/src/runtime/widget.tsx
  6. 5
      Form7Buildingdatesummary/config.json
  7. 3
      Form7Buildingdatesummary/icon.svg
  8. 19
      Form7Buildingdatesummary/manifest.json
  9. 7
      Form7Buildingdatesummary/src/config.ts
  10. 45
      Form7Buildingdatesummary/src/runtime/components/LoadingPopup.css
  11. 23
      Form7Buildingdatesummary/src/runtime/components/LoadingPopup.tsx
  12. 23
      Form7Buildingdatesummary/src/runtime/components/LoadingSpinner.jsx
  13. 1548
      Form7Buildingdatesummary/src/runtime/widget.tsx
  14. 20
      Form7Buildingdatesummary/src/setting/setting.tsx
  15. BIN
      Form7Buildingdatesummary/src/zoom-in.png
  16. 5
      Form7Generartion_Filter/config.json
  17. 4
      Form7Generartion_Filter/icon.svg
  18. 19
      Form7Generartion_Filter/manifest.json
  19. 7
      Form7Generartion_Filter/src/config.ts
  20. 25
      Form7Generartion_Filter/src/runtime/assets/zoom-in.svg
  21. 45
      Form7Generartion_Filter/src/runtime/components/LoadingPopup.css
  22. 23
      Form7Generartion_Filter/src/runtime/components/LoadingPopup.tsx
  23. 23
      Form7Generartion_Filter/src/runtime/components/LoadingSpinner.jsx
  24. 857
      Form7Generartion_Filter/src/runtime/simple_widget.css
  25. 1006
      Form7Generartion_Filter/src/runtime/widget.tsx
  26. 20
      Form7Generartion_Filter/src/setting/setting.tsx
  27. BIN
      Form7Generartion_Filter/src/zoom-in.png
  28. 14
      Form7Generartion_Filter/tests/simple-widget.test.tsx
  29. 5
      Form7Query_Approval/config.json
  30. 27
      Form7Query_Approval/icon.svg
  31. 19
      Form7Query_Approval/manifest.json
  32. 7
      Form7Query_Approval/src/config.ts
  33. 25
      Form7Query_Approval/src/runtime/assets/zoom-in.svg
  34. 45
      Form7Query_Approval/src/runtime/components/LoadingPopup.css
  35. 23
      Form7Query_Approval/src/runtime/components/LoadingPopup.tsx
  36. 23
      Form7Query_Approval/src/runtime/components/LoadingSpinner.jsx
  37. 155
      Form7Query_Approval/src/runtime/simple_widget.css
  38. 820
      Form7Query_Approval/src/runtime/widget.tsx
  39. 20
      Form7Query_Approval/src/setting/setting.tsx
  40. BIN
      Form7Query_Approval/src/zoom-in.png
  41. 14
      Form7Query_Approval/tests/simple-widget.test.tsx
  42. 1
      custom-popup/config.json
  43. 4
      custom-popup/icon.svg
  44. 20
      custom-popup/manifest.json
  45. 80
      custom-popup/src/runtime/custom-popup.css
  46. 2571
      custom-popup/src/runtime/widget.tsx
  47. 18
      custom-popup/src/setting/setting.tsx

1
Form7ApprovalPending/config.json

@ -0,0 +1 @@
{}

1
Form7ApprovalPending/icon.svg

@ -0,0 +1 @@
<svg class="svg-icon" style="width: 1em;height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M526.791111 1005.037037h-329.955555c-61.62963 0-111.691852-50.062222-111.691852-111.691852V246.518519c0-61.62963 50.062222-111.691852 111.691852-111.691852h145.066666c21.617778-57.457778 74.145185-132.740741 168.770371-132.740741 101.451852 0 147.152593 85.712593 164.598518 132.740741h132.361482c61.62963 0 111.691852 50.062222 111.691851 111.691852v376.794074c0 20.859259-17.066667 37.925926-37.925925 37.925926s-37.925926-17.066667-37.925926-37.925926V246.518519c0-19.721481-16.118519-35.84-35.84-35.84h-160.237037c-17.825185 0-33.185185-12.515556-36.977778-29.771852-0.948148-3.982222-24.082963-102.968889-99.745185-102.968889-80.213333 0-103.537778 101.831111-103.727408 102.968889a37.736296 37.736296 0 0 1-36.977778 29.771852h-173.131851c-19.721481 0-35.84 16.118519-35.84 35.84v646.637037c0 19.721481 16.118519 35.84 35.84 35.84h329.955555c20.859259 0 37.925926 17.066667 37.925926 37.925925s-17.066667 38.115556-37.925926 38.115556z" fill="#FF8206" /><path d="M545.754074 211.057778h-76.420741c-10.24 0-18.773333-8.343704-18.773333-18.773334v-0.568888c0-10.24 8.343704-18.773333 18.773333-18.773334h76.420741c10.24 0 18.773333 8.343704 18.773333 18.773334v0.568888c0 10.24-8.533333 18.773333-18.773333 18.773334zM754.346667 387.602963H260.740741c-10.24 0-18.773333-8.343704-18.773334-18.773333v-0.568889c0-10.24 8.343704-18.773333 18.773334-18.773334h493.605926c10.24 0 18.773333 8.343704 18.773333 18.773334v0.568889c0 10.42963-8.533333 18.773333-18.773333 18.773333zM754.346667 564.337778H260.740741c-10.24 0-18.773333-8.343704-18.773334-18.773334v-0.568888c0-10.24 8.343704-18.773333 18.773334-18.773334h493.605926c10.24 0 18.773333 8.343704 18.773333 18.773334v0.568888c0 10.42963-8.533333 18.773333-18.773333 18.773334zM521.481481 741.072593H255.620741c-10.24 0-18.773333-8.343704-18.773334-18.773334v-0.568889c0-10.24 8.343704-18.773333 18.773334-18.773333H521.481481c10.24 0 18.773333 8.343704 18.773334 18.773333v0.568889c0 10.24-8.343704 18.773333-18.773334 18.773334zM769.137778 987.780741c-3.982222 0-8.154074-1.327407-11.567408-3.982222l-104.485926-80.213334a18.811259 18.811259 0 0 1-3.413333-26.548148c6.447407-8.343704 18.204444-9.860741 26.548148-3.413333l89.884445 69.214815 156.065185-189.44a18.962963 18.962963 0 0 1 26.737778-2.654815 18.962963 18.962963 0 0 1 2.654814 26.737777l-167.632592 203.662223c-3.982222 4.171852-9.291852 6.637037-14.791111 6.637037z" fill="#FFAF28" /></svg>

29
Form7ApprovalPending/manifest.json

@ -0,0 +1,29 @@
{
"name": "Form7ApprovalPending",
"label": "Form7ApprovalPending",
"type": "widget",
"version": "1.16.0",
"exbVersion": "1.16.0",
"author": "Tanii GIS 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": "Approval_Pending",
"label": "Approval_Pending",
"thumbnail": "thumbnail.png",
"description": "Summarizes Street Light, Highmast, and Pillar Box counts by zone."
}
],
"framework": "react",
"translatedLocales": ["en"],
"dataSources": [],
"elements": []
}

7
Form7ApprovalPending/src/config.ts

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

1317
Form7ApprovalPending/src/runtime/widget.tsx
File diff suppressed because it is too large
View File

5
Form7Buildingdatesummary/config.json

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

3
Form7Buildingdatesummary/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="M965.282 186.746H62.449c-25.477 0-46.129 21.177-46.129 47.298v7.139c0 26.121 20.652 47.296 46.129 47.296h902.833c25.48 0 46.131-21.175 46.131-47.296v-7.139c0-26.121-20.65-47.298-46.131-47.298z" fill="#4A5699" /><path d="M965.282 821.697H62.449c-25.477 0-46.129 21.173-46.129 47.296v7.141c0 26.119 20.652 47.297 46.129 47.297h902.833c25.48 0 46.131-21.178 46.131-47.297v-7.141c0-26.123-20.65-47.296-46.131-47.296z" fill="#C45FA0" /><path d="M69.412 186.746H62.33c-26.121 0-47.294 21.177-47.294 47.298v642.09c0 26.119 21.173 47.297 47.294 47.297h7.082c26.121 0 47.294-21.178 47.294-47.297v-642.09c0.001-26.121-21.173-47.298-47.294-47.298zM964.117 186.746h-7.082c-26.119 0-47.296 21.177-47.296 47.298v642.09c0 26.119 21.177 47.297 47.296 47.297h7.082c26.122 0 47.296-21.178 47.296-47.297v-642.09c0-26.121-21.174-47.298-47.296-47.298z" fill="#F39A2B" /><path d="M426.617 435.818h-7.082c-26.121 0-47.296 21.171-47.296 47.294v38.715c0 26.119 21.175 47.296 47.296 47.296h7.082c26.121 0 47.298-21.177 47.298-47.296v-38.715c0-26.122-21.177-47.294-47.298-47.294zM601.912 435.818h-7.082c-26.118 0-47.292 21.171-47.292 47.294v38.715c0 26.119 21.174 47.296 47.292 47.296h7.082c26.119 0 47.3-21.177 47.3-47.296v-38.715c0-26.122-21.181-47.294-47.3-47.294zM777.211 435.818h-7.082c-26.122 0-47.296 21.171-47.296 47.294v38.715c0 26.119 21.174 47.296 47.296 47.296h7.082c26.119 0 47.292-21.177 47.292-47.296v-38.715c0-26.122-21.173-47.294-47.292-47.294zM777.211 611.22h-7.082c-26.122 0-47.296 21.17-47.296 47.293v38.716c0 26.115 21.174 47.293 47.296 47.293h7.082c26.119 0 47.292-21.178 47.292-47.293v-38.716c0-26.123-21.173-47.293-47.292-47.293zM601.912 611.22h-7.082c-26.118 0-47.292 21.17-47.292 47.293v38.716c0 26.115 21.174 47.293 47.292 47.293h7.082c26.119 0 47.3-21.178 47.3-47.293v-38.716c0-26.123-21.181-47.293-47.3-47.293zM426.617 611.22h-7.082c-26.121 0-47.296 21.17-47.296 47.293v38.716c0 26.115 21.175 47.293 47.296 47.293h7.082c26.121 0 47.298-21.178 47.298-47.293v-38.716c0-26.123-21.177-47.293-47.298-47.293zM251.32 611.22h-7.08c-26.123 0-47.294 21.17-47.294 47.293v38.716c0 26.115 21.171 47.293 47.294 47.293h7.08c26.123 0 47.294-21.178 47.294-47.293v-38.716c0-26.123-21.171-47.293-47.294-47.293z" fill="#E5594F" /><path d="M299.245 91.914h-7.435c-26.125 0-47.296 19.988-47.296 44.649V312.24c0 24.657 21.171 44.649 47.296 44.649h7.435c26.125 0 47.296-19.992 47.296-44.649V136.563c0-24.661-21.172-44.649-47.296-44.649zM719.956 91.914h-7.438c-26.118 0-47.292 19.988-47.292 44.649V312.24c0 24.657 21.174 44.649 47.292 44.649h7.438c26.123 0 47.296-19.992 47.296-44.649V136.563c0-24.661-21.173-44.649-47.296-44.649z" fill="#6277BA" /></svg>

19
Form7Buildingdatesummary/manifest.json

@ -0,0 +1,19 @@
{
"name": "Form7Buildingdatesummary",
"label": "Form7Buildingdatesummary",
"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
Form7Buildingdatesummary/src/config.ts

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

45
Form7Buildingdatesummary/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
Form7Buildingdatesummary/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
Form7Buildingdatesummary/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;

1548
Form7Buildingdatesummary/src/runtime/widget.tsx
File diff suppressed because it is too large
View File

20
Form7Buildingdatesummary/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
Form7Buildingdatesummary/src/zoom-in.png

After

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

5
Form7Generartion_Filter/config.json

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

4
Form7Generartion_Filter/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>

19
Form7Generartion_Filter/manifest.json

@ -0,0 +1,19 @@
{
"name": "Form7Generartion_Filter",
"label": "Form7Generartion_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
Form7Generartion_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
Form7Generartion_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
Form7Generartion_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
Form7Generartion_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
Form7Generartion_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;

857
Form7Generartion_Filter/src/runtime/simple_widget.css

@ -0,0 +1,857 @@
.form7-container {
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
background: #f4f8fc;
font-family:
"Segoe UI",
Arial,
sans-serif;
color: #1f2937;
box-sizing: border-box;
padding: 16px;
}
/* =========================================================
HEADER
========================================================= */
.form7-header {
display: flex;
align-items: center;
gap: 12px;
background: #ffffff;
border: 1px solid #dbe7f2;
border-radius: 12px;
padding: 14px 16px;
margin-bottom: 14px;
box-shadow:
0 2px 8px rgba(30, 64, 100, 0.06);
}
.form7-header-icon {
width: 42px;
height: 42px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
background: #e0f2fe;
color: #0369a1;
font-size: 14px;
font-weight: 700;
border: 1px solid #bae6fd;
}
.form7-title {
font-size: 18px;
font-weight: 700;
color: #123b5d;
line-height: 1.2;
}
.form7-subtitle {
margin-top: 4px;
font-size: 12px;
color: #6b7f93;
}
/* =========================================================
SUMMARY
========================================================= */
.form7-summary {
display: grid;
grid-template-columns:
repeat(3, 1fr);
gap: 10px;
margin-bottom: 14px;
}
.summary-card {
display: flex;
align-items: center;
gap: 10px;
background: #ffffff;
border: 1px solid #dbe7f2;
border-radius: 11px;
padding: 12px;
box-shadow:
0 2px 7px rgba(30, 64, 100, 0.05);
}
.summary-icon {
width: 34px;
height: 34px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 9px;
background: #e0f2fe;
color: #0284c7;
font-size: 14px;
font-weight: 700;
}
.summary-label {
font-size: 10px;
color: #718096;
margin-bottom: 2px;
text-transform: uppercase;
letter-spacing: 0.4px;
}
.summary-value {
font-size: 20px;
font-weight: 700;
color: #164e70;
}
.summary-small {
font-size: 17px;
}
/* =========================================================
FILTER CARD
========================================================= */
.form7-filter-card {
background: #ffffff;
border: 1px solid #dbe7f2;
border-radius: 12px;
padding: 16px;
margin-bottom: 14px;
box-shadow:
0 2px 8px rgba(30, 64, 100, 0.06);
}
.section-title {
font-size: 14px;
font-weight: 700;
color: #174b70;
}
.section-description {
font-size: 11px;
color: #718096;
margin-top: 3px;
margin-bottom: 14px;
}
.filter-grid {
display: grid;
grid-template-columns:
repeat(3, 1fr);
gap: 12px;
}
.filter-field {
display: flex;
flex-direction: column;
min-width: 0;
}
.filter-field label {
font-size: 11px;
font-weight: 600;
color: #486174;
margin-bottom: 6px;
}
.required {
color: #dc2626;
margin-left: 3px;
}
.filter-field select,
.filter-field input {
width: 100%;
height: 36px;
box-sizing: border-box;
border: 1px solid #cbd9e6;
border-radius: 7px;
background: #ffffff;
color: #263f52;
padding: 0 10px;
font-size: 12px;
outline: none;
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
}
.filter-field select:hover,
.filter-field input:hover {
border-color: #8dbbd8;
}
.filter-field select:focus,
.filter-field input:focus {
border-color: #38a3d1;
box-shadow:
0 0 0 3px
rgba(56, 163, 209, 0.12);
}
.filter-field select:disabled,
.filter-field input:disabled {
background: #f3f6f9;
color: #9aa8b5;
cursor: not-allowed;
}
.disabled-input-wrapper {
position: relative;
}
.disabled-input-wrapper input {
padding-right: 48px;
}
.tracking-badge {
position: absolute;
right: 7px;
top: 50%;
transform: translateY(-50%);
background: #e5eef5;
color: #7890a2;
border-radius: 5px;
padding: 3px 5px;
font-size: 9px;
font-weight: 600;
}
/* =========================================================
ACTION BUTTONS
========================================================= */
.filter-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 16px;
padding-top: 13px;
border-top: 1px solid #edf2f7;
}
.reset-button,
.search-button {
height: 36px;
border-radius: 7px;
padding: 0 16px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
transition:
background 0.15s ease,
border-color 0.15s ease,
transform 0.1s ease;
}
.reset-button {
background: #ffffff;
color: #526b7d;
border: 1px solid #cbd9e6;
}
.reset-button:hover {
background: #f3f8fc;
border-color: #9dbbd0;
}
.search-button {
background: #168ac1;
color: #ffffff;
border: 1px solid #168ac1;
}
.search-button:hover {
background: #0f78a9;
}
.search-button:disabled {
opacity: 0.55;
cursor: not-allowed;
}
.button-spinner {
width: 12px;
height: 12px;
border: 2px solid
rgba(255, 255, 255, 0.4);
border-top-color: #ffffff;
border-radius: 50%;
animation:
form7-spin 0.7s linear infinite;
}
@keyframes form7-spin {
to {
transform: rotate(360deg);
}
}
/* =========================================================
ERROR
========================================================= */
.error-message {
margin-top: 12px;
display: flex;
align-items: center;
gap: 7px;
background: #fff5f5;
border: 1px solid #fecaca;
color: #b91c1c;
border-radius: 7px;
padding: 9px 11px;
font-size: 11px;
}
.error-message span {
width: 17px;
height: 17px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: #fee2e2;
font-weight: 700;
}
/* =========================================================
RESULTS
========================================================= */
.form7-results-card {
background: #ffffff;
border: 1px solid #dbe7f2;
border-radius: 12px;
overflow: hidden;
box-shadow:
0 2px 8px rgba(30, 64, 100, 0.06);
}
.results-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
border-bottom: 1px solid #e5edf4;
}
.results-count {
margin-top: 3px;
font-size: 10px;
color: #7b8d9d;
}
.result-badge {
background: #e0f2fe;
color: #0369a1;
border: 1px solid #bae6fd;
padding: 5px 9px;
border-radius: 6px;
font-size: 10px;
font-weight: 600;
}
/* =========================================================
TABLE
========================================================= */
.table-wrapper {
width: 100%;
overflow-x: auto;
}
.form7-table {
width: 100%;
border-collapse: collapse;
min-width: 850px;
}
.form7-table th {
background: #f1f7fb;
color: #49677b;
font-size: 10px;
font-weight: 700;
text-align: left;
padding: 10px 9px;
border-bottom: 1px solid #dbe7f2;
white-space: nowrap;
}
.form7-table td {
padding: 9px;
border-bottom: 1px solid #edf2f7;
font-size: 11px;
color: #43596a;
vertical-align: middle;
}
.form7-table tbody tr {
transition:
background 0.12s ease;
}
.form7-table tbody tr:hover {
background: #f7fbfe;
}
.form7-table tbody tr.selected-row {
background: #eaf7fd;
}
.serial-cell {
width: 35px;
color: #8a9bab !important;
text-align: center;
}
.zone-badge,
.ward-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 30px;
padding: 4px 7px;
border-radius: 5px;
font-size: 10px;
font-weight: 600;
}
.zone-badge {
background: #e0f2fe;
color: #0369a1;
}
.ward-badge {
background: #edf6ff;
color: #2563a6;
}
.road-cell {
max-width: 170px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.gis-cell {
max-width: 130px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family:
Consolas,
monospace;
font-size: 10px !important;
color: #24627f !important;
}
.name-cell {
max-width: 140px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.status-badge {
display: inline-block;
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background: #f0f7fb;
color: #39708c;
border: 1px solid #d7eaf4;
border-radius: 5px;
padding: 4px 7px;
font-size: 9px;
font-weight: 600;
}
/* =========================================================
ZOOM BUTTON
========================================================= */
.zoom-button {
display: inline-flex;
align-items: center;
gap: 5px;
border: 1px solid #b9dceb;
background: #eff9fd;
color: #0876a5;
border-radius: 6px;
height: 28px;
padding: 0 8px;
font-size: 10px;
font-weight: 600;
cursor: pointer;
}
.zoom-button:hover {
background: #dff3fb;
border-color: #83c7df;
}
.zoom-button span {
font-size: 13px;
}
/* =========================================================
PAGINATION
========================================================= */
.pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 12px;
border-top: 1px solid #edf2f7;
color: #6d8090;
font-size: 10px;
}
.pagination button {
width: 28px;
height: 28px;
border-radius: 6px;
border: 1px solid #cbd9e6;
background: #ffffff;
color: #477089;
cursor: pointer;
font-size: 16px;
}
.pagination button:hover:not(:disabled) {
background: #edf8fd;
border-color: #8fc7df;
}
.pagination button:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.pagination strong {
color: #185579;
}
/* =========================================================
EMPTY STATE
========================================================= */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 180px;
padding: 25px;
}
.empty-icon {
width: 42px;
height: 42px;
border-radius: 50%;
background: #eef7fc;
border: 1px solid #d6ebf5;
display: flex;
align-items: center;
justify-content: center;
color: #7db6cf;
font-size: 22px;
margin-bottom: 10px;
}
.empty-title {
font-size: 13px;
font-weight: 600;
color: #526b7d;
}
.empty-description {
margin-top: 5px;
font-size: 10px;
color: #8a9aa8;
text-align: center;
}
/* =========================================================
RESPONSIVE
========================================================= */
@media (max-width: 700px) {
.form7-summary {
grid-template-columns:
1fr;
}
.filter-grid {
grid-template-columns:
1fr;
}
.filter-actions {
justify-content: stretch;
}
.reset-button,
.search-button {
flex: 1;
}
}

1006
Form7Generartion_Filter/src/runtime/widget.tsx
File diff suppressed because it is too large
View File

20
Form7Generartion_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
Form7Generartion_Filter/src/zoom-in.png

After

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

14
Form7Generartion_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')
})
})

5
Form7Query_Approval/config.json

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

27
Form7Query_Approval/icon.svg

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#000000" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="800px" height="800px" viewBox="0 0 31.709 31.709"
xml:space="preserve">
<g>
<g>
<path d="M10.595,25.719H4.696c-1.127,0-2.06-0.886-2.06-2.013V5.42c0-1.127,0.933-2.006,2.06-2.006h14.277
c1.127,0,2.047,0.879,2.047,2.006v15.323l2.637-3.135V3.462c0-1.482-1.172-2.684-2.652-2.684H2.559C1.136,0.778,0,1.932,0,3.354
v22.382c0,1.482,1.185,2.688,2.669,2.688h10.358l-1.224-1.063C11.267,26.896,10.864,26.327,10.595,25.719z"/>
<path d="M17.875,6.794H6.034c-0.728,0-1.314,0.591-1.314,1.318c0,0.726,0.587,1.317,1.314,1.317h11.84
c0.728,0,1.312-0.591,1.312-1.317C19.188,7.386,18.602,6.794,17.875,6.794z"/>
<path d="M17.875,11.187H6.034c-0.728,0-1.314,0.59-1.314,1.318c0,0.724,0.587,1.318,1.314,1.318h11.84
c0.728,0,1.312-0.594,1.312-1.318C19.188,11.777,18.602,11.187,17.875,11.187z"/>
<path d="M17.875,15.581H6.034c-0.728,0-1.314,0.558-1.314,1.286c0,0.725,0.587,1.282,1.314,1.282h11.84
c0.728,0,1.312-0.56,1.312-1.282C19.188,16.139,18.602,15.581,17.875,15.581z"/>
<path d="M4.719,21.056c0,0.727,0.587,1.283,1.314,1.283h4.418c0.185-0.473,0.469-1.022,0.857-1.479
c0.408-0.473,0.889-0.82,1.412-1.092H6.034C5.306,19.771,4.719,20.331,4.719,21.056z"/>
<path d="M17.875,19.771h-0.988c0.324,0.137,0.633,0.366,0.916,0.611l1.312,1.123c0.05-0.135,0.076-0.28,0.076-0.437
C19.188,20.346,18.602,19.771,17.875,19.771z"/>
<path d="M30.898,16.249c-0.965-0.828-2.42-0.71-3.246,0.26l-7.564,8.867l-3.781-3.248c-0.968-0.826-2.421-0.717-3.248,0.248
c-0.829,0.967-0.717,2.418,0.248,3.246l5.533,4.752c0.422,0.358,0.951,0.557,1.5,0.557c0.062,0,0.119-0.002,0.182-0.008
c0.607-0.047,1.176-0.336,1.572-0.801l9.066-10.627C31.982,18.528,31.869,17.077,30.898,16.249z"/>
</g>
</g>
</svg>

19
Form7Query_Approval/manifest.json

@ -0,0 +1,19 @@
{
"name": "Form7Query_Approval",
"label": "Form7Query_Approval",
"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
Form7Query_Approval/src/config.ts

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

25
Form7Query_Approval/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
Form7Query_Approval/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
Form7Query_Approval/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
Form7Query_Approval/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;

155
Form7Query_Approval/src/runtime/simple_widget.css

@ -0,0 +1,155 @@
.property-widget-parent {
/* Enables scroll for the whole widget */
padding: 8px;
overflow-x: hidden;
max-width: 100%;
}
.property-widget {
font-family: Arial, sans-serif;
background-color: #f9f9f9;
border-radius: 8px;
padding: 16px;
min-width: 350px;
}
.widget-title {
font-size: 20px;
margin-bottom: 16px;
color: #1a1a1a;
font-weight: 600;
border-bottom: 1px solid #ddd;
padding-bottom: 4px;
}
.property_form {
display: grid;
grid-template-columns: 1fr 2fr;
align-items: center;
gap: 8px;
margin-bottom: 10px;
overflow-x: auto;
overflow-y: auto;
max-height: 300px; /* Restrict form height */
}
.property_form label {
margin-bottom: 4px;
font-weight: 500;
color: #333;
}
.property_form select {
padding: 6px 10px;
border-radius: 6px;
border: 1px solid #ccc;
font-size: 14px;
background-color: #fdfdfd;
transition: border-color 0.2s ease;
}
.property_form select:focus {
outline: none;
border-color: #0078d4;
}
#countDiv {
margin-bottom: 20px;
background: #eef6fd;
padding: 12px;
border-radius: 6px;
border: 1px solid #d0e4f3;
}
#countDiv p {
margin: 4px 0;
font-size: 14px;
color: #333;
}
button.property_form {
padding: 10px;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
border: none;
font-size: 14px;
transition: background 0.2s ease;
}
button.property_form:hover {
opacity: 0.9;
}
.attribute-table {
margin-top: 20px;
background: #fff;
border: 1px solid #ccc;
border-radius: 8px;
padding: 10px;
overflow: auto; /* Enables both vertical and horizontal scroll */
max-height: 300px; /* Restrict vertical space */
max-width: 100%; /* Ensure it doesn't overflow container */
}
.attribute-table table {
width: max-content; /* Enables horizontal scrolling */
min-width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.attribute-table thead th {
position: sticky;
top: 0;
background-color: #f0f0f0;
z-index: 2;
}
.attribute-table th {
background: #f3f3f3;
font-weight: 600;
}
.attribute-table tr:hover {
background-color: #f9f9f9;
}
button {
font-family: inherit;
}
.submit-button-fixed {
position: fixed;
bottom: 20px;
right: 20px;
background-color: #0078d4;
color: white;
padding: 10px 20px;
border: none;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
cursor: pointer;
z-index: 1000;
}
.submit-button-fixed:hover {
background-color: #005fa3;
}
.property-widget {
position: relative;
}
.submit-button-fixed {
position: absolute;
bottom: 10px;
right: 10px;
}
.widget-container, .esri-widget, .property_form {
max-width: 100%;
box-sizing: border-box;
overflow-x: hidden;
}

820
Form7Query_Approval/src/runtime/widget.tsx

@ -0,0 +1,820 @@
import React, { useState, useEffect } from "react";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import { type AllWidgetProps } from 'jimu-core';
//import "./simple_widget.css";
import { JimuMapViewComponent, type JimuMapView } from 'jimu-arcgis';
//import { mapViewManager } from '../../../utils/mapview';
import zoomInIcon from './assets/zoom-in.svg';
const RoadAssetFilter = (props: AllWidgetProps<any>) => {
// State variables (building-only, no road/gis)
const [selectedZone, setSelectedZone] = useState<string>("All");
const [selectedWard, setSelectedWard] = useState<string>("All");
const [zoneOptions, setZoneOptions] = useState<string[]>([]);
const [wardOptions, setWardOptions] = useState<string[]>([]);
const [jimuMapView, setJimuMapView] = useState<JimuMapView>();
const [highlightHandle, setHighlightHandle] = useState<any>(null);
const [selectedUser, setSelectedUser] = useState<string>("All");
const [selectedDate, setSelectedDate] = useState<string>("All"); // ISO yyyy-mm-dd
const [selectedApproval, setSelectedApproval] = useState<string>("All");
const [userOptions, setUserOptions] = useState<string[]>([]);
const [dateOptions, setDateOptions] = useState<string[]>([]);
const [approvalOptions, setApprovalOptions] = useState<string[]>([]);
const [buildingCount, setBuildingCount] = useState<number | null>(null);
const [tableData, setTableData] = useState<any[]>([]);
const [currentPage, setCurrentPage] = useState<number>(1);
const [itemsPerPage] = useState<number>(10);
const [selectedFeatureId, setSelectedFeatureId] = useState<number | null>(null);
// URLs for layers (only Building)
const ZONE_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/GCC_Administration_Boundaries/FeatureServer/3";
const WARD_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/GCCDepts/GCC_Administration_Boundaries/FeatureServer/4";
const BUILDING_LAYER_URL = "https://gisgcc.chennaicorporation.gov.in/server/rest/services/Revenue/Building_Updation/FeatureServer/0";
// Layer name (used to locate map layer)
const BUILDING_LAYER_NAME = "Form 6 Building";
// Field mapping (Building only)
const FIELD_MAPPING = {
Building: {
zone: "zone",
ward: "ward",
user: "created_user",
date: "created_date",
approval: "approval"
}
};
// Utility to escape single quotes in SQL
const safe = (val: string) => val.replace(/'/g, "''");
// ------------------------------------------------------------------------
// DATE HELPER – using TIMESTAMP literals (proven in summary widget)
// ------------------------------------------------------------------------
const toTimestampWhere = (fieldName: string, startISO: string, endISO: string) => {
return `${fieldName} >= TIMESTAMP '${startISO} 00:00:00' AND ${fieldName} <= TIMESTAMP '${endISO} 23:59:59'`;
};
// Format a value (ms or ISO/string) for display in table (locale)
const formatDate = (dateValue: any) => {
if (!dateValue) return '-';
if (typeof dateValue === 'number') {
return new Date(dateValue).toLocaleString();
}
if (typeof dateValue === 'string' && dateValue.includes('T')) {
return new Date(dateValue).toLocaleString();
}
if (typeof dateValue === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(dateValue)) {
return new Date(`${dateValue} GMT+0530`).toLocaleString();
}
return dateValue;
};
// Convert a feature attribute date (number or string) into ISO yyyy-mm-dd for dropdown
const formatAttrDateToISO = (val: any): string | null => {
if (val == null) return null;
let d: Date;
if (typeof val === 'number') {
d = new Date(val);
} else {
d = new Date(val);
}
if (isNaN(d.getTime())) return null;
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
return `${yyyy}-${mm}-${dd}`;
};
// Apply ward definition expression on building layer
const applyWardDefinition = async (wardValue: string) => {
if (!jimuMapView?.view || !wardValue || wardValue === 'All') return;
const view = jimuMapView.view;
const buildingLayer = view.map.layers.find(l => l.title === BUILDING_LAYER_NAME) as FeatureLayer;
if (buildingLayer) {
buildingLayer.definitionExpression = `${FIELD_MAPPING.Building.ward} = '${safe(wardValue)}'`;
await view.whenLayerView(buildingLayer);
}
};
const clearWardDefinition = async () => {
if (!jimuMapView?.view) return;
const map = jimuMapView.view.map;
const layer = map.layers.find(l => l.title === BUILDING_LAYER_NAME) as FeatureLayer;
if (layer) {
layer.definitionExpression = '';
}
};
// Zoom to Zone geometry
const zoomToZoneGeometry = async (zoneValue: string) => {
if (!jimuMapView || !zoneValue || zoneValue === 'All') return;
const view = jimuMapView.view;
const zoneLayer = new FeatureLayer({ url: ZONE_LAYER_URL });
try {
await zoneLayer.load();
const zoneField = zoneLayer.fields.find(f => f.name === "zone");
let whereClause: string;
if (zoneField && (zoneField.type === "integer" || zoneField.type === "double")) {
const num = Number(zoneValue);
if (isNaN(num)) return;
whereClause = `zone = ${num}`;
} else {
whereClause = `zone = '${safe(zoneValue)}'`;
}
const query = zoneLayer.createQuery();
query.where = whereClause;
query.outFields = ["zone"];
query.returnGeometry = true;
const result = await zoneLayer.queryFeatures(query);
if (result.features.length > 0) {
const geometry = result.features[0].geometry;
await view.goTo({
target: geometry,
padding: { top: 50, bottom: 50, left: 50, right: 50 },
duration: 1000
});
}
} catch (error) {
console.error("Error zooming to zone geometry:", error);
}
};
// Zoom to Ward geometry
const zoomToWardGeometry = async (wardValue: string) => {
if (!jimuMapView || !wardValue || wardValue === 'All') return;
const view = jimuMapView.view;
const wardLayer = new FeatureLayer({ url: WARD_LAYER_URL });
try {
await wardLayer.load();
const wardField = wardLayer.fields.find(f => f.name === "ward_no");
let whereClause: string;
if (wardField && (wardField.type === "integer" || wardField.type === "double")) {
const num = Number(wardValue);
if (isNaN(num)) return;
whereClause = `ward_no = ${num}`;
} else {
whereClause = `ward_no = '${safe(wardValue)}'`;
}
const query = wardLayer.createQuery();
query.where = whereClause;
query.outFields = ["ward_no"];
query.returnGeometry = true;
const result = await wardLayer.queryFeatures(query);
if (result.features.length > 0) {
const geometry = result.features[0].geometry;
sessionStorage.setItem("wardgeometry", JSON.stringify(geometry.toJSON()));
await view.goTo({
target: geometry,
padding: { top: 50, bottom: 50, left: 50, right: 50 },
duration: 1000
});
}
} catch (error) {
console.error("Error zooming to ward geometry:", error);
}
};
// Zoom to filtered features (building-only)
const zoomToFilteredFeature = async () => {
if (!jimuMapView) return;
const view = jimuMapView.view;
const layerUrl = BUILDING_LAYER_URL;
const whereClause = buildWhereClause();
const layer = new FeatureLayer({ url: layerUrl });
try {
await layer.load();
const query = layer.createQuery();
try {
query.where = whereClause;
query.returnGeometry = true;
query.outFields = ["*"];
} catch (error) {
console.error("Invalid where clause in zoomToFilteredFeature:", whereClause, error);
return;
}
const result = await layer.queryFeatures(query);
if (result.features.length === 1) {
const feature = result.features[0];
await handleRowZoom(feature);
} else if (result.features.length > 1) {
const extentResult = await layer.queryExtent(query);
if (extentResult.extent) {
await view.goTo({ target: extentResult.extent.expand(1.2), duration: 1000 });
}
}
} catch (err) {
console.error("Error zooming to filtered feature:", err);
}
};
useEffect(() => {
const shouldZoom =
(selectedUser && selectedUser !== 'All') ||
(selectedDate && selectedDate !== 'All') ||
(selectedApproval && selectedApproval !== 'All');
if (shouldZoom) {
zoomToFilteredFeature();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedUser, selectedDate, selectedApproval]);
// Populate Zone dropdown from Building layer distinct zone values
const populateZone = async () => {
try {
const layer = new FeatureLayer({ url: BUILDING_LAYER_URL });
const query = layer.createQuery();
query.returnDistinctValues = true;
query.returnGeometry = false;
query.outFields = [FIELD_MAPPING.Building.zone];
query.where = "1=1";
const result = await layer.queryFeatures(query);
if (result) {
const zones = Array.from(new Set(
result.features.map(f => f.attributes[FIELD_MAPPING.Building.zone]).filter(Boolean)
));
zones.sort((a, b) => {
const na = Number(a), nb = Number(b);
if (!isNaN(na) && !isNaN(nb)) return na - nb;
return String(a).localeCompare(String(b));
});
setZoneOptions(['All', ...zones]);
}
} catch (error) {
console.error("Error fetching zones:", error);
}
};
useEffect(() => {
populateZone();
}, []);
// Populate Ward dropdown based on selected Zone (from Building layer)
const populateWard = async () => {
try {
const layer = new FeatureLayer({ url: BUILDING_LAYER_URL });
const query = layer.createQuery();
query.returnDistinctValues = true;
query.returnGeometry = false;
query.outFields = [FIELD_MAPPING.Building.ward];
if (selectedZone && selectedZone !== 'All') {
query.where = `${FIELD_MAPPING.Building.zone} = '${safe(selectedZone)}'`;
} else {
query.where = "1=1";
}
const result = await layer.queryFeatures(query);
if (result) {
const wards = Array.from(new Set(
result.features.map(f => f.attributes[FIELD_MAPPING.Building.ward]).filter(Boolean)
));
wards.sort((a, b) => {
const na = Number(a), nb = Number(b);
if (!isNaN(na) && !isNaN(nb)) return na - nb;
return String(a).localeCompare(String(b));
});
setWardOptions(['All', ...wards]);
}
} catch (error) {
console.error("Error fetching wards:", error);
}
};
useEffect(() => {
populateWard();
}, [selectedZone]);
// Fetch filter options based on building layer (user, date, approval)
const fetchFilterOptions = async () => {
if (!selectedZone) return;
const layerUrl = BUILDING_LAYER_URL;
const mapping = FIELD_MAPPING.Building;
try {
const layer = new FeatureLayer({ url: layerUrl });
const baseQuery = layer.createQuery();
baseQuery.returnDistinctValues = true;
baseQuery.returnGeometry = false;
// Zone and ward filtering
let where = "1=1";
if (selectedZone && selectedZone !== 'All') where = `${mapping.zone} = '${safe(selectedZone)}'`;
if (selectedWard && selectedWard !== 'All') where += ` AND ${mapping.ward} = '${safe(selectedWard)}'`;
// Approval filter (if already selected) should further narrow options
if (selectedApproval && selectedApproval !== 'All') {
where += ` AND ${mapping.approval} = '${safe(selectedApproval)}'`;
}
// Fetch Created Users
const userQuery = layer.createQuery();
userQuery.returnDistinctValues = true;
userQuery.returnGeometry = false;
userQuery.where = where;
userQuery.outFields = [mapping.user];
const userResult = await layer.queryFeatures(userQuery);
const users = Array.from(new Set(
userResult.features.map(f => f.attributes[mapping.user]).filter(v => v != null)
));
users.sort();
setUserOptions(['All', ...users]);
// Fetch Created Dates -> store as ISO (yyyy-mm-dd)
const dateQuery = layer.createQuery();
dateQuery.returnDistinctValues = true;
dateQuery.returnGeometry = false;
dateQuery.where = where;
dateQuery.outFields = [mapping.date];
const dateResult = await layer.queryFeatures(dateQuery);
const rawDates = dateResult.features
.map(f => formatAttrDateToISO(f.attributes[mapping.date]))
.filter(v => v != null) as string[];
const uniqueDates = Array.from(new Set(rawDates));
uniqueDates.sort();
setDateOptions(['All', ...uniqueDates]);
// Fetch Approval (distinct values). If none, fallback to coded values array.
try {
const approvalQuery = layer.createQuery();
approvalQuery.returnDistinctValues = true;
approvalQuery.returnGeometry = false;
approvalQuery.where = where;
approvalQuery.outFields = [mapping.approval];
const approvalResult = await layer.queryFeatures(approvalQuery);
let approvals = Array.from(new Set(
approvalResult.features.map(f => f.attributes[mapping.approval]).filter(v => v != null)
));
approvals.sort();
if (approvals.length === 0) {
approvals = ['Approved', 'Pending']; // fallback to coded values
}
setApprovalOptions(['All', ...approvals]);
} catch (err) {
setApprovalOptions(['All', 'Approved', 'Pending']);
}
} catch (error) {
console.error("Error fetching filter options:", error);
}
};
useEffect(() => {
fetchFilterOptions();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedZone, selectedWard, selectedApproval]);
// ------------------------------------------------------------------------
// BUILD WHERE CLAUSE – FIXED DATE USING TIMESTAMP LITERALS
// ------------------------------------------------------------------------
const buildWhereClause = () => {
const clauses: string[] = [];
const mapping = FIELD_MAPPING.Building;
if (selectedZone && selectedZone !== 'All') {
clauses.push(`${mapping.zone}='${safe(selectedZone)}'`);
}
if (selectedWard && selectedWard !== 'All') {
clauses.push(`${mapping.ward}='${safe(selectedWard)}'`);
}
if (selectedUser && selectedUser !== 'All') {
clauses.push(`${mapping.user}='${safe(selectedUser)}'`);
}
if (selectedApproval && selectedApproval !== 'All') {
clauses.push(`${mapping.approval}='${safe(selectedApproval)}'`);
}
// Date range – using TIMESTAMP literals (works for esriFieldTypeDate)
if (selectedDate && selectedDate !== 'All') {
const dateWhere = toTimestampWhere(mapping.date, selectedDate, selectedDate);
clauses.push(dateWhere);
}
return clauses.length > 0 ? clauses.join(" AND ") : "1=1";
};
// Count helper for building layer
const getFeatureCount = async (layerUrl: string, whereClause: string): Promise<number> => {
const layer = new FeatureLayer({ url: layerUrl });
const query = layer.createQuery();
query.returnGeometry = false;
try {
query.where = whereClause;
} catch (error) {
console.error("Invalid where clause in count query:", whereClause, error);
return 0;
}
try {
return await layer.queryFeatureCount(query);
} catch (error) {
console.error("Error counting features:", error);
return 0;
}
};
// Fetch counts whenever filters change (only building)
useEffect(() => {
const fetchCounts = async () => {
try {
const buildingWhere = buildWhereClause();
const bc = await getFeatureCount(BUILDING_LAYER_URL, buildingWhere);
setBuildingCount(bc);
} catch (err) {
console.error(err);
setBuildingCount(null);
}
};
fetchCounts();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedZone, selectedWard, selectedUser, selectedDate, selectedApproval]);
// Row Zoom: zoom to a single feature, highlight it (building)
const handleRowZoom = async (feature: any) => {
const view = jimuMapView?.view;
if (!view || !feature) return;
if (highlightHandle) {
highlightHandle.remove();
setHighlightHandle(null);
}
try {
const layerForHighlight = view.map.layers.find(l => l.url === BUILDING_LAYER_URL);
if (layerForHighlight) {
await layerForHighlight.load();
const layerView = await view.whenLayerView(layerForHighlight as any);
const highlight = layerView.highlight(feature);
setHighlightHandle(highlight);
}
setSelectedFeatureId(feature.attributes.OBJECTID);
const geom = feature.geometry;
if (geom) {
await view.goTo({ target: geom, zoom: 18, duration: 1000 });
}
} catch (err) {
console.error("Error zooming to feature:", err);
}
};
// Submit: apply filters, populate table
const handleSubmit = async (e: React.MouseEvent) => {
e.preventDefault();
if (!selectedZone) {
alert("Please select a Zone");
return;
}
const view = jimuMapView?.view;
if (!view) return;
// Zoom to Ward extent if selected
if (selectedWard && selectedWard !== 'All') {
await zoomToWardGeometry(selectedWard);
}
// Build whereClause
const whereClause = buildWhereClause();
console.log("Built WHERE clause:", whereClause);
// Query features for table (building layer)
const layerUrl = BUILDING_LAYER_URL;
const queryLayer = new FeatureLayer({ url: layerUrl });
const query = queryLayer.createQuery();
query.where = whereClause;
query.outFields = ["*"];
query.returnGeometry = true;
try {
const results = await queryLayer.queryFeatures(query);
setTableData(results.features);
setCurrentPage(1);
if (results.features.length === 0) {
alert(`No features found for the selected filters in Building layer.`);
}
// Apply filter to building layer in the active map
const viewMap = view.map;
const buildingLayer = viewMap.layers.find(l => l.title === BUILDING_LAYER_NAME) as FeatureLayer;
if (buildingLayer) {
try {
await buildingLayer.load();
const lv = await view.whenLayerView(buildingLayer);
if (lv) {
lv.filter = { where: whereClause };
}
} catch (err) {
console.error("Error applying filter to building layer:", buildingLayer.title, err);
}
}
// Zoom to filtered extent if needed
const shouldZoomToFeatures =
!selectedWard || selectedWard === 'All';
if (shouldZoomToFeatures) {
const fl = new FeatureLayer({ url: layerUrl });
await fl.load();
const extentResult = await fl.queryExtent({ where: whereClause });
if (extentResult.extent) {
await view.goTo({
target: extentResult.extent.expand(1.2),
duration: 1000
});
}
}
} catch (error) {
console.error("Query failed:", error);
alert("An error occurred while querying features.");
}
};
// Reset: clear all selections (building-only)
const handleReset = () => {
setSelectedZone("All");
setSelectedWard("All");
setSelectedUser("All");
setSelectedDate("All");
setSelectedApproval("All");
setUserOptions([]);
setDateOptions([]);
setApprovalOptions([]);
setBuildingCount(null);
setTableData([]);
setCurrentPage(1);
setSelectedFeatureId(null);
const view = jimuMapView?.view;
if (highlightHandle) {
highlightHandle.remove();
setHighlightHandle(null);
}
view?.graphics.removeAll();
if (view) {
const clearFilter = async (layerTitle: string) => {
const layer = view.map.layers.find(l => l.title === layerTitle) as FeatureLayer;
if (layer) {
try {
await layer.load();
const lv = await view.whenLayerView(layer);
if (lv) lv.filter = null;
} catch (err) {
console.error("Error clearing filter on layer:", layerTitle, err);
}
}
};
clearFilter(BUILDING_LAYER_NAME);
}
clearWardDefinition();
};
// Active view change handler
const activeViewChangeHandler = (jmv: JimuMapView) => {
if (jmv && jmv.view) {
setJimuMapView(jmv);
}
};
useEffect(() => {
const interval = setInterval(() => {
const view = mapViewManager.getMapView();
if (view && view.view) {
setJimuMapView(view);
clearInterval(interval);
}
}, 500);
return () => clearInterval(interval);
}, []);
// Pagination for table
const paginatedData = tableData.slice(
(currentPage - 1) * itemsPerPage,
currentPage * itemsPerPage
);
return (
<div className="property-widget-parent">
<div className="property-widget">
<JimuMapViewComponent
useMapWidgetId={props.useMapWidgetIds?.[0]}
onActiveViewChange={activeViewChangeHandler}
/>
<h3 className="widget-title">Building Search</h3>
<div id="countDiv">
<strong>Count Summary:</strong>
<p>Buildings: <span id="buildingCount">{buildingCount !== null ? buildingCount : '-'}</span></p>
</div>
{/* Zone selection */}
<div className="property_form">
<label>Zone:<span style={{ color: 'red' }}>*</span></label>
<select
value={selectedZone}
onChange={e => {
const v = e.target.value;
setSelectedZone(v);
zoomToZoneGeometry(v);
}}
required
>
<option value="All">--Select Zone--</option>
{zoneOptions.map(zone => <option key={zone} value={zone}>{zone}</option>)}
</select>
</div>
{/* Ward selection */}
<div className="property_form">
<label>Ward:</label>
<select
value={selectedWard}
onChange={e => {
const v = e.target.value;
setSelectedWard(v);
zoomToWardGeometry(v);
applyWardDefinition(v);
}}
>
<option value="All">--Select Ward--</option>
{wardOptions.map(ward => <option key={ward} value={ward}>{ward}</option>)}
</select>
</div>
{/* Filters (Zone-Ward-Approval-Date only) */}
<div className="property_form">
{/* Approval */}
<label>Approval Status:</label>
<select
value={selectedApproval}
onChange={e => setSelectedApproval(e.target.value)}
>
<option value="All">--All--</option>
{approvalOptions.map(a => <option key={a} value={a}>{a}</option>)}
</select>
{/* Created User */}
<label>Created User:</label>
<select
value={selectedUser}
onChange={e => setSelectedUser(e.target.value)}
>
<option value="All">--All--</option>
{userOptions.map(u => <option key={u} value={u}>{u}</option>)}
</select>
{/* Date */}
<label>Created Date:</label>
<select
value={selectedDate}
onChange={e => setSelectedDate(e.target.value)}
>
<option value="All">--All--</option>
{dateOptions.map(ts => (
<option key={ts} value={ts}>
{ts}
</option>
))}
</select>
{/* Submit / Reset */}
<div style={{
display: 'flex',
gap: '10px',
marginTop: '10px',
width: '100%'
}}>
<button
className="property_form"
style={{
backgroundColor: '#0078d4',
color: '#fff',
flex: 1,
minWidth: 0,
padding: '8px'
}}
onClick={handleSubmit}
>
Submit
</button>
<button
className="property_form"
style={{
backgroundColor: '#fff',
color: '#0078d4',
border: '1px solid #0078d4',
flex: 1,
minWidth: 0,
padding: '8px'
}}
onClick={handleReset}
>
Reset
</button>
</div>
</div>
{/* Feature table */}
{tableData.length > 0 && (
<div className="attribute-table">
<h4>Feature Details</h4>
<table
border={1}
cellPadding={5}
cellSpacing={0}
style={{ width: '100%', borderCollapse: 'collapse' }}
>
<thead>
<tr>
<th>Action</th>
<th>Sl.No</th>
<th>Zone</th>
<th>Ward</th>
<th>Approval</th>
<th>Created Date</th>
</tr>
</thead>
<tbody>
{paginatedData.map((feature, index) => {
const attr = feature.attributes;
const mapping = FIELD_MAPPING.Building;
return (
<tr
key={index}
style={{
cursor: 'default',
backgroundColor: selectedFeatureId === feature.attributes.OBJECTID ? '#e6f7ff' : 'transparent'
}}
>
<tr>
<button
onClick={e => {
e.stopPropagation();
handleRowZoom(feature);
}}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: '4px'
}}
title="Zoom to this feature"
>
<img
src={zoomInIcon}
alt="Zoom"
style={{ width: '16px', height: '16px' }}
/>
</button>
</td>
<td>{(currentPage - 1) * itemsPerPage + index + 1}</td>
<td>{attr[mapping.zone]}</td>
<td>{attr[mapping.ward]}</td>
<td>{attr[mapping.approval] || '-'}</td>
<td>{formatDate(attr[mapping.date])}</td>
</tr>
);
})}
</tbody>
</table>
{/* Pagination controls */}
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '10px' }}>
<button
disabled={currentPage === 1}
onClick={() => setCurrentPage(prev => prev - 1)}
style={{ margin: '0 5px', padding: '5px 10px' }}
>
Previous
</button>
<span style={{ margin: '0 10px', lineHeight: '30px' }}>
Page {currentPage} of {Math.ceil(tableData.length / itemsPerPage)}
</span>
<button
disabled={currentPage >= Math.ceil(tableData.length / itemsPerPage)}
onClick={() => setCurrentPage(prev => prev + 1)}
style={{ margin: '0 5px', padding: '5px 10px' }}
>
Next
</button>
</div>
</div>
)}
</div>
</div>
);
};
export default RoadAssetFilter;

20
Form7Query_Approval/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
Form7Query_Approval/src/zoom-in.png

After

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

14
Form7Query_Approval/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
custom-popup/config.json

@ -0,0 +1 @@
{}

4
custom-popup/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>

20
custom-popup/manifest.json

@ -0,0 +1,20 @@
{
"name": "custom-popup",
"type": "widget",
"version": "1.15.0",
"dependency": [
"jimu-arcgis"
],
"exbVersion": "1.15.0",
"description": "A hello world starter widget",
"copyright": "",
"license": "",
"properties": {},
"translatedLocales": [
"en"
],
"defaultSize": {
"width": 800,
"height": 500
}
}

80
custom-popup/src/runtime/custom-popup.css

@ -0,0 +1,80 @@
.content-wrapper {
background-color: white !important;
}
td {
width: 47%;
word-wrap: break-word;
padding: 0% 3%;
}
.loading-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
color: rgb(13, 42, 78, 0.79);
}
/*
.custom-popup {
padding: 10px;
background-color: #fff;
border-radius: 8px;
max-height: 100%;
overflow-y: auto;
}
.popup-item {
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 6px;
background-color: #f0f0f0;
padding: 10px;
}
.popup-header {
cursor: pointer;
font-weight: bold;
display: flex;
justify-content: space-between;
padding: 10px;
background-color: #eee;
border-bottom: 1px solid #ddd;
}
.expand-toggle {
font-size: 18px;
color: #007bff;
}
.popup-content {
background-color: #fff;
border-top: 1px solid #ddd;
display: flex;
flex-direction: column;
row-gap: -2px;
font-size: 0.7rem;
}
.popup-row {
display: flex;
padding: 3px 9px;
justify-content: space-between;
box-shadow: 0px 1px #dcd7d7;
}
.popup-key {
font-weight: bold;
width: 50%;
word-wrap: break-word
}
.popup-value {
word-wrap: break-word;
flex: 1;
font-weight: bold;
width: 50%;
} */

2571
custom-popup/src/runtime/widget.tsx
File diff suppressed because it is too large
View File

18
custom-popup/src/setting/setting.tsx

@ -0,0 +1,18 @@
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
Loading…
Cancel
Save