47 changed files with 8973 additions and 0 deletions
-
1Form7ApprovalPending/config.json
-
1Form7ApprovalPending/icon.svg
-
29Form7ApprovalPending/manifest.json
-
7Form7ApprovalPending/src/config.ts
-
1317Form7ApprovalPending/src/runtime/widget.tsx
-
5Form7Buildingdatesummary/config.json
-
3Form7Buildingdatesummary/icon.svg
-
19Form7Buildingdatesummary/manifest.json
-
7Form7Buildingdatesummary/src/config.ts
-
45Form7Buildingdatesummary/src/runtime/components/LoadingPopup.css
-
23Form7Buildingdatesummary/src/runtime/components/LoadingPopup.tsx
-
23Form7Buildingdatesummary/src/runtime/components/LoadingSpinner.jsx
-
1548Form7Buildingdatesummary/src/runtime/widget.tsx
-
20Form7Buildingdatesummary/src/setting/setting.tsx
-
BINForm7Buildingdatesummary/src/zoom-in.png
-
5Form7Generartion_Filter/config.json
-
4Form7Generartion_Filter/icon.svg
-
19Form7Generartion_Filter/manifest.json
-
7Form7Generartion_Filter/src/config.ts
-
25Form7Generartion_Filter/src/runtime/assets/zoom-in.svg
-
45Form7Generartion_Filter/src/runtime/components/LoadingPopup.css
-
23Form7Generartion_Filter/src/runtime/components/LoadingPopup.tsx
-
23Form7Generartion_Filter/src/runtime/components/LoadingSpinner.jsx
-
857Form7Generartion_Filter/src/runtime/simple_widget.css
-
1006Form7Generartion_Filter/src/runtime/widget.tsx
-
20Form7Generartion_Filter/src/setting/setting.tsx
-
BINForm7Generartion_Filter/src/zoom-in.png
-
14Form7Generartion_Filter/tests/simple-widget.test.tsx
-
5Form7Query_Approval/config.json
-
27Form7Query_Approval/icon.svg
-
19Form7Query_Approval/manifest.json
-
7Form7Query_Approval/src/config.ts
-
25Form7Query_Approval/src/runtime/assets/zoom-in.svg
-
45Form7Query_Approval/src/runtime/components/LoadingPopup.css
-
23Form7Query_Approval/src/runtime/components/LoadingPopup.tsx
-
23Form7Query_Approval/src/runtime/components/LoadingSpinner.jsx
-
155Form7Query_Approval/src/runtime/simple_widget.css
-
820Form7Query_Approval/src/runtime/widget.tsx
-
20Form7Query_Approval/src/setting/setting.tsx
-
BINForm7Query_Approval/src/zoom-in.png
-
14Form7Query_Approval/tests/simple-widget.test.tsx
-
1custom-popup/config.json
-
4custom-popup/icon.svg
-
20custom-popup/manifest.json
-
80custom-popup/src/runtime/custom-popup.css
-
2571custom-popup/src/runtime/widget.tsx
-
18custom-popup/src/setting/setting.tsx
@ -0,0 +1 @@ |
|||||
|
{} |
||||
@ -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> |
||||
@ -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": [] |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
@ -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
File diff suppressed because it is too large
View File
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"exampleConfigProperty": "test", |
||||
|
"useMapWidget": true |
||||
|
|
||||
|
} |
||||
@ -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> |
||||
@ -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 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
import { type ImmutableObject } from 'seamless-immutable' |
||||
|
|
||||
|
export interface Config { |
||||
|
exampleConfigProperty: string |
||||
|
} |
||||
|
|
||||
|
export type IMConfig = ImmutableObject<Config> |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
@ -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; |
||||
@ -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
File diff suppressed because it is too large
View File
@ -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 |
||||
|
|
||||
|
After Width: 512 | Height: 512 | Size: 23 KiB |
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"exampleConfigProperty": "test", |
||||
|
"useMapWidget": true |
||||
|
|
||||
|
} |
||||
@ -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> |
||||
@ -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 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
import { type ImmutableObject } from 'seamless-immutable' |
||||
|
|
||||
|
export interface Config { |
||||
|
exampleConfigProperty: string |
||||
|
} |
||||
|
|
||||
|
export type IMConfig = ImmutableObject<Config> |
||||
@ -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> |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
@ -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; |
||||
@ -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; |
||||
@ -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
File diff suppressed because it is too large
View File
@ -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 |
||||
|
|
||||
|
After Width: 512 | Height: 512 | Size: 23 KiB |
@ -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') |
||||
|
}) |
||||
|
}) |
||||
@ -0,0 +1,5 @@ |
|||||
|
{ |
||||
|
"exampleConfigProperty": "test", |
||||
|
"useMapWidget": true |
||||
|
|
||||
|
} |
||||
@ -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> |
||||
@ -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 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
import { type ImmutableObject } from 'seamless-immutable' |
||||
|
|
||||
|
export interface Config { |
||||
|
exampleConfigProperty: string |
||||
|
} |
||||
|
|
||||
|
export type IMConfig = ImmutableObject<Config> |
||||
@ -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> |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
@ -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; |
||||
@ -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; |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
@ -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 |
||||
|
|
||||
|
After Width: 512 | Height: 512 | Size: 23 KiB |
@ -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') |
||||
|
}) |
||||
|
}) |
||||
@ -0,0 +1 @@ |
|||||
|
{} |
||||
@ -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> |
||||
@ -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 |
||||
|
} |
||||
|
} |
||||
@ -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
File diff suppressed because it is too large
View File
@ -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 |
||||
|
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue