@salesforce/webapp-template-app-react-sample-b2e-experimental 1.73.0 → 1.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CHANGELOG.md +16 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/Maintenance_Request__c.object-meta.xml +11 -1
- package/dist/force-app/main/default/objects/Maintenance_Worker__c/Maintenance_Worker__c.object-meta.xml +6 -1
- package/dist/force-app/main/default/objects/Property__c/Property__c.object-meta.xml +6 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/package.json +7 -5
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/maintenanceWorkers.ts +60 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ApplicationsTable.tsx +59 -62
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/FiltersFromApi.tsx +200 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ListPageFilters.tsx +97 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/MaintenanceTable.tsx +2 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ObjectSelect.tsx +39 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/VerticalNav.tsx +6 -4
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/dashboard/GlobalSearchBar.tsx +125 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/feedback/FilterErrorAlert.tsx +15 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/feedback/PageErrorState.tsx +19 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/feedback/PageLoadingState.tsx +18 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/filters/FilterFieldRange.tsx +40 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/filters/FilterFieldSelect.tsx +190 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/filters/FilterFieldText.tsx +32 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/filters/ListPageFilterRow.tsx +100 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/layout/PageContainer.tsx +9 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/layout/PageHeader.tsx +21 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/list/ListPageWithFilters.tsx +70 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/constants.ts +39 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/index.ts +19 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/objectDetailService.ts +125 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/objectInfoGraphQLService.ts +194 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/objectInfoService.ts +199 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/recordListGraphQLService.ts +364 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/DetailFields.tsx +55 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/DetailForm.tsx +146 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/DetailHeader.tsx +34 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/DetailLayoutSections.tsx +80 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/Section.tsx +108 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/SectionRow.tsx +20 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/UiApiDetailForm.tsx +140 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FieldValueDisplay.tsx +73 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedAddress.tsx +29 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedEmail.tsx +17 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedPhone.tsx +24 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedText.tsx +11 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedUrl.tsx +29 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/index.ts +6 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/filters/FilterField.tsx +54 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/filters/FilterInput.tsx +55 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/filters/FilterSelect.tsx +72 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/filters/FiltersPanel.tsx +380 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/forms/filters-form.tsx +114 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/forms/submit-button.tsx +47 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/GlobalSearchInput.tsx +114 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/ResultCardFields.tsx +71 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/SearchHeader.tsx +31 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/SearchPagination.tsx +144 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/SearchResultCard.tsx +136 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/SearchResultsPanel.tsx +197 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/shared/LoadingFallback.tsx +61 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/filters/FilterInput.tsx +55 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/filters/FilterSelect.tsx +72 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/form.tsx +209 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/index.ts +22 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useObjectInfoBatch.ts +65 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useObjectSearchData.ts +395 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useRecordDetailLayout.ts +156 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useRecordListGraphQL.ts +135 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/pages/DetailPage.tsx +109 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/pages/GlobalSearch.tsx +229 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/filters/filters.ts +121 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/filters/picklist.ts +32 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/index.ts +5 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/objectInfo/objectInfo.ts +166 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/recordDetail/recordDetail.ts +61 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/search/searchResults.ts +229 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/apiUtils.ts +125 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/cacheUtils.ts +76 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/debounce.ts +89 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/fieldUtils.ts +354 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/fieldValueExtractor.ts +67 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/filterUtils.ts +32 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/formDataTransformUtils.ts +260 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/formUtils.ts +142 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/graphQLNodeFieldUtils.ts +186 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/graphQLObjectInfoAdapter.ts +319 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/graphQLRecordAdapter.ts +90 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/index.ts +59 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/layoutTransformUtils.ts +236 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/linkUtils.ts +14 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/paginationUtils.ts +49 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/recordUtils.ts +159 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/sanitizationUtils.ts +49 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/hooks/useAccumulatedListPages.ts +29 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/hooks/useListPage.ts +167 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/index.ts +8 -4
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/applicationAdapter.ts +33 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/applicationColumns.ts +28 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/constants.ts +24 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/fieldMappers.ts +71 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/filterUtils.ts +165 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/globalSearchConstants.ts +40 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/listFilters.ts +152 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/listPageConfig.ts +65 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceAdapter.ts +110 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceColumns.ts +24 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceWorkerAdapter.ts +29 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceWorkerColumns.ts +25 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/objectApiNames.ts +13 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/propertyAdapter.ts +68 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/propertyColumns.ts +17 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/routeConfig.ts +35 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/types.ts +10 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Applications.tsx +47 -62
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Home.tsx +130 -98
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Maintenance.tsx +74 -91
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/MaintenanceWorkers.tsx +138 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Properties.tsx +166 -85
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/routes.tsx +41 -2
- package/dist/package.json +1 -1
- package/package.json +5 -1
package/dist/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [1.74.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.73.1...v1.74.0) (2026-03-05)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [1.73.1](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.73.0...v1.73.1) (2026-03-05)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
# [1.73.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.72.0...v1.73.0) (2026-03-05)
|
|
7
23
|
|
|
8
24
|
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
@@ -61,7 +61,17 @@
|
|
|
61
61
|
<type>AutoNumber</type>
|
|
62
62
|
</nameField>
|
|
63
63
|
<pluralLabel>Maintenance Requests</pluralLabel>
|
|
64
|
-
<searchLayouts
|
|
64
|
+
<searchLayouts>
|
|
65
|
+
<searchResultsAdditionalFields>Description__c</searchResultsAdditionalFields>
|
|
66
|
+
<searchResultsAdditionalFields>Type__c</searchResultsAdditionalFields>
|
|
67
|
+
<searchResultsAdditionalFields>Priority__c</searchResultsAdditionalFields>
|
|
68
|
+
<searchResultsAdditionalFields>Status__c</searchResultsAdditionalFields>
|
|
69
|
+
<searchResultsAdditionalFields>Scheduled__c</searchResultsAdditionalFields>
|
|
70
|
+
<searchResultsAdditionalFields>Property__r.Address__c</searchResultsAdditionalFields>
|
|
71
|
+
<searchResultsAdditionalFields>Property__r.Name</searchResultsAdditionalFields>
|
|
72
|
+
<searchResultsAdditionalFields>User__r.Name</searchResultsAdditionalFields>
|
|
73
|
+
<searchResultsAdditionalFields>Owner.Name</searchResultsAdditionalFields>
|
|
74
|
+
</searchLayouts>
|
|
65
75
|
<sharingModel>ReadWrite</sharingModel>
|
|
66
76
|
<visibility>Public</visibility>
|
|
67
77
|
</CustomObject>
|
|
@@ -60,7 +60,12 @@
|
|
|
60
60
|
<type>Text</type>
|
|
61
61
|
</nameField>
|
|
62
62
|
<pluralLabel>Maintenance Workers</pluralLabel>
|
|
63
|
-
<searchLayouts
|
|
63
|
+
<searchLayouts>
|
|
64
|
+
<searchResultsAdditionalFields>Hourly_Rate__c</searchResultsAdditionalFields>
|
|
65
|
+
<searchResultsAdditionalFields>Type__c</searchResultsAdditionalFields>
|
|
66
|
+
<searchResultsAdditionalFields>Rating__c</searchResultsAdditionalFields>
|
|
67
|
+
<searchResultsAdditionalFields>Employment_Type__c</searchResultsAdditionalFields>
|
|
68
|
+
</searchLayouts>
|
|
64
69
|
<sharingModel>ReadWrite</sharingModel>
|
|
65
70
|
<visibility>Public</visibility>
|
|
66
71
|
</CustomObject>
|
|
@@ -60,7 +60,12 @@
|
|
|
60
60
|
<type>Text</type>
|
|
61
61
|
</nameField>
|
|
62
62
|
<pluralLabel>Properties</pluralLabel>
|
|
63
|
-
<searchLayouts
|
|
63
|
+
<searchLayouts>
|
|
64
|
+
<searchResultsAdditionalFields>Hero_Image__c</searchResultsAdditionalFields>
|
|
65
|
+
<searchResultsAdditionalFields>Address__c</searchResultsAdditionalFields>
|
|
66
|
+
<searchResultsAdditionalFields>Description__c</searchResultsAdditionalFields>
|
|
67
|
+
<searchResultsAdditionalFields>CREATED_DATE</searchResultsAdditionalFields>
|
|
68
|
+
</searchLayouts>
|
|
64
69
|
<sharingModel>ReadWrite</sharingModel>
|
|
65
70
|
<visibility>Public</visibility>
|
|
66
71
|
</CustomObject>
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
"graphql:schema": "node scripts/get-graphql-schema.mjs"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@salesforce/agentforce-conversation-client": "^1.
|
|
19
|
-
"@salesforce/sdk-data": "^1.
|
|
20
|
-
"@salesforce/webapp-experimental": "^1.
|
|
18
|
+
"@salesforce/agentforce-conversation-client": "^1.74.0",
|
|
19
|
+
"@salesforce/sdk-data": "^1.74.0",
|
|
20
|
+
"@salesforce/webapp-experimental": "^1.74.0",
|
|
21
21
|
"@tailwindcss/vite": "^4.1.17",
|
|
22
|
+
"@tanstack/react-form": "^1.28.4",
|
|
22
23
|
"class-variance-authority": "^0.7.1",
|
|
23
24
|
"clsx": "^2.1.1",
|
|
24
25
|
"date-fns": "^3.6.0",
|
|
@@ -30,7 +31,8 @@
|
|
|
30
31
|
"shadcn": "^3.8.5",
|
|
31
32
|
"tailwind-merge": "^3.4.0",
|
|
32
33
|
"tailwindcss": "^4.1.17",
|
|
33
|
-
"tw-animate-css": "^1.4.0"
|
|
34
|
+
"tw-animate-css": "^1.4.0",
|
|
35
|
+
"zod": "^4.3.6"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
36
38
|
"@eslint/js": "^9.39.1",
|
|
@@ -40,7 +42,7 @@
|
|
|
40
42
|
"@graphql-eslint/eslint-plugin": "^4.1.0",
|
|
41
43
|
"@graphql-tools/utils": "^11.0.0",
|
|
42
44
|
"@playwright/test": "^1.49.0",
|
|
43
|
-
"@salesforce/vite-plugin-webapp-experimental": "^1.
|
|
45
|
+
"@salesforce/vite-plugin-webapp-experimental": "^1.74.0",
|
|
44
46
|
"@testing-library/jest-dom": "^6.6.3",
|
|
45
47
|
"@testing-library/react": "^16.1.0",
|
|
46
48
|
"@testing-library/user-event": "^14.5.2",
|
package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/maintenanceWorkers.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { MaintenanceWorker } from "../lib/types.js";
|
|
2
|
+
import { getAllMaintenanceRequests } from "./maintenance.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fetches maintenance workers. Derives unique workers from maintenance requests
|
|
6
|
+
* (assigned workers) and adds fallback placeholder entries so the list is never empty.
|
|
7
|
+
*/
|
|
8
|
+
export async function getMaintenanceWorkers(): Promise<MaintenanceWorker[]> {
|
|
9
|
+
try {
|
|
10
|
+
const requests = await getAllMaintenanceRequests(200);
|
|
11
|
+
const byName = new Map<string, MaintenanceWorker>();
|
|
12
|
+
let idCounter = 1;
|
|
13
|
+
for (const req of requests) {
|
|
14
|
+
const name = req.assignedWorkerName || req.assignedWorker || "Unassigned";
|
|
15
|
+
if (!name || name === "Unassigned") continue;
|
|
16
|
+
if (!byName.has(name)) {
|
|
17
|
+
byName.set(name, {
|
|
18
|
+
id: `worker-${idCounter++}`,
|
|
19
|
+
name,
|
|
20
|
+
organization: req.assignedWorkerOrg,
|
|
21
|
+
activeRequestsCount: 0,
|
|
22
|
+
status: "Active",
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const w = byName.get(name)!;
|
|
26
|
+
w.activeRequestsCount = (w.activeRequestsCount ?? 0) + 1;
|
|
27
|
+
}
|
|
28
|
+
let workers = Array.from(byName.values());
|
|
29
|
+
if (workers.length === 0) {
|
|
30
|
+
workers = [
|
|
31
|
+
{
|
|
32
|
+
id: "worker-1",
|
|
33
|
+
name: "ABC Diamond Technicians",
|
|
34
|
+
organization: "ABC Diamond",
|
|
35
|
+
activeRequestsCount: 0,
|
|
36
|
+
status: "Active",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "worker-2",
|
|
40
|
+
name: "Maintenance Team",
|
|
41
|
+
organization: "Property Mgmt",
|
|
42
|
+
activeRequestsCount: 0,
|
|
43
|
+
status: "Active",
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
return workers;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error("Error fetching maintenance workers:", error);
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
id: "worker-1",
|
|
53
|
+
name: "ABC Diamond Technicians",
|
|
54
|
+
organization: "ABC Diamond",
|
|
55
|
+
status: "Active",
|
|
56
|
+
},
|
|
57
|
+
{ id: "worker-2", name: "Maintenance Team", organization: "Property Mgmt", status: "Active" },
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Card } from "@/components/ui/card";
|
|
3
2
|
import type { Application } from "../lib/types.js";
|
|
4
3
|
|
|
5
4
|
interface ApplicationsTableProps {
|
|
@@ -35,71 +34,69 @@ export const ApplicationsTable: React.FC<ApplicationsTableProps> = ({
|
|
|
35
34
|
};
|
|
36
35
|
|
|
37
36
|
return (
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
<
|
|
37
|
+
<div className="border border-gray-200 rounded-lg shadow-sm overflow-x-auto bg-white">
|
|
38
|
+
<table className="w-full">
|
|
39
|
+
<thead className="bg-gray-50 border-b border-gray-200">
|
|
40
|
+
<tr>
|
|
41
|
+
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
42
|
+
User
|
|
43
|
+
</th>
|
|
44
|
+
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
45
|
+
Start Date
|
|
46
|
+
</th>
|
|
47
|
+
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
48
|
+
Status
|
|
49
|
+
</th>
|
|
50
|
+
</tr>
|
|
51
|
+
</thead>
|
|
52
|
+
<tbody className="bg-white divide-y divide-gray-200">
|
|
53
|
+
{applications.length === 0 ? (
|
|
42
54
|
<tr>
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
</
|
|
46
|
-
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
47
|
-
Start Date
|
|
48
|
-
</th>
|
|
49
|
-
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
|
50
|
-
Status
|
|
51
|
-
</th>
|
|
55
|
+
<td colSpan={3} className="px-6 py-8 text-center text-gray-500">
|
|
56
|
+
No applications found
|
|
57
|
+
</td>
|
|
52
58
|
</tr>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<div className="w-10 h-10 bg-purple-100 rounded-full flex items-center justify-center flex-shrink-0">
|
|
71
|
-
<span className="text-sm font-medium text-purple-700">
|
|
72
|
-
{application.applicantName?.charAt(0) || "?"}
|
|
73
|
-
</span>
|
|
59
|
+
) : (
|
|
60
|
+
applications.map((application) => (
|
|
61
|
+
<tr
|
|
62
|
+
key={application.id}
|
|
63
|
+
onClick={() => onRowClick(application)}
|
|
64
|
+
className="hover:bg-gray-50 cursor-pointer transition-colors"
|
|
65
|
+
>
|
|
66
|
+
<td className="px-6 py-4 whitespace-nowrap">
|
|
67
|
+
<div className="flex items-center">
|
|
68
|
+
<div className="w-10 h-10 bg-purple-100 rounded-full flex items-center justify-center flex-shrink-0">
|
|
69
|
+
<span className="text-sm font-medium text-purple-700">
|
|
70
|
+
{application.applicantName?.charAt(0) || "?"}
|
|
71
|
+
</span>
|
|
72
|
+
</div>
|
|
73
|
+
<div className="ml-4">
|
|
74
|
+
<div className="text-sm font-medium text-gray-900">
|
|
75
|
+
{application.applicantName || "Unknown"}
|
|
74
76
|
</div>
|
|
75
|
-
<div className="
|
|
76
|
-
|
|
77
|
-
{application.applicantName || "Unknown"}
|
|
78
|
-
</div>
|
|
79
|
-
<div className="text-sm text-gray-500">
|
|
80
|
-
{application.propertyName || application.propertyAddress}
|
|
81
|
-
</div>
|
|
77
|
+
<div className="text-sm text-gray-500">
|
|
78
|
+
{application.propertyName || application.propertyAddress}
|
|
82
79
|
</div>
|
|
83
80
|
</div>
|
|
84
|
-
</
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
</
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
</
|
|
97
|
-
</
|
|
98
|
-
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
</
|
|
102
|
-
</
|
|
103
|
-
</
|
|
81
|
+
</div>
|
|
82
|
+
</td>
|
|
83
|
+
<td className="px-6 py-4 whitespace-nowrap">
|
|
84
|
+
<div className="text-sm text-gray-900">
|
|
85
|
+
{formatDate(application.startDate || application.submittedDate)}
|
|
86
|
+
</div>
|
|
87
|
+
</td>
|
|
88
|
+
<td className="px-6 py-4 whitespace-nowrap">
|
|
89
|
+
<span
|
|
90
|
+
className={`inline-flex px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(application.status)}`}
|
|
91
|
+
>
|
|
92
|
+
{application.status}
|
|
93
|
+
</span>
|
|
94
|
+
</td>
|
|
95
|
+
</tr>
|
|
96
|
+
))
|
|
97
|
+
)}
|
|
98
|
+
</tbody>
|
|
99
|
+
</table>
|
|
100
|
+
</div>
|
|
104
101
|
);
|
|
105
102
|
};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
import {
|
|
3
|
+
useObjectListMetadata,
|
|
4
|
+
parseFilterValue,
|
|
5
|
+
sanitizeFilterValue,
|
|
6
|
+
type FilterCriteria,
|
|
7
|
+
} from "@salesforce/webapp-template-feature-react-global-search-experimental";
|
|
8
|
+
|
|
9
|
+
const inputClass =
|
|
10
|
+
"h-9 rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary-purple/20 focus:border-primary-purple";
|
|
11
|
+
|
|
12
|
+
interface FiltersFromApiProps {
|
|
13
|
+
objectApiName: string | null;
|
|
14
|
+
onApplyFilters: (criteria: FilterCriteria[]) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Renders filter UI from the react-global-search filters API (getObjectListFilters).
|
|
19
|
+
* Uses useObjectListMetadata to load filters and picklist values, then builds FilterCriteria on Apply.
|
|
20
|
+
*/
|
|
21
|
+
export function FiltersFromApi({ objectApiName, onApplyFilters }: FiltersFromApiProps) {
|
|
22
|
+
const { filters, picklistValues, loading, error } = useObjectListMetadata(objectApiName);
|
|
23
|
+
const [formValues, setFormValues] = useState<Record<string, string>>({});
|
|
24
|
+
const [rangeMin, setRangeMin] = useState<Record<string, string>>({});
|
|
25
|
+
const [rangeMax, setRangeMax] = useState<Record<string, string>>({});
|
|
26
|
+
|
|
27
|
+
const handleChange = useCallback((fieldPath: string, value: string) => {
|
|
28
|
+
setFormValues((prev) => ({ ...prev, [fieldPath]: value }));
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
const handleRangeChange = useCallback((fieldPath: string, min: string, max: string) => {
|
|
32
|
+
setRangeMin((prev) => ({ ...prev, [fieldPath]: min }));
|
|
33
|
+
setRangeMax((prev) => ({ ...prev, [fieldPath]: max }));
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
const handleApply = useCallback(() => {
|
|
37
|
+
if (!objectApiName || !filters?.length) {
|
|
38
|
+
onApplyFilters([]);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const criteria: FilterCriteria[] = [];
|
|
42
|
+
for (const filter of filters) {
|
|
43
|
+
if (!filter?.targetFieldPath) continue;
|
|
44
|
+
const affordance = (filter.affordance ?? "").toLowerCase();
|
|
45
|
+
if (affordance === "range") {
|
|
46
|
+
const minVal = sanitizeFilterValue(rangeMin[filter.targetFieldPath] ?? "");
|
|
47
|
+
const maxVal = sanitizeFilterValue(rangeMax[filter.targetFieldPath] ?? "");
|
|
48
|
+
if (minVal) {
|
|
49
|
+
const parsed = parseFilterValue(minVal);
|
|
50
|
+
if (parsed !== "")
|
|
51
|
+
criteria.push({
|
|
52
|
+
objectApiName,
|
|
53
|
+
fieldPath: filter.targetFieldPath,
|
|
54
|
+
operator: "gte",
|
|
55
|
+
values: [parsed],
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (maxVal) {
|
|
59
|
+
const parsed = parseFilterValue(maxVal);
|
|
60
|
+
if (parsed !== "")
|
|
61
|
+
criteria.push({
|
|
62
|
+
objectApiName,
|
|
63
|
+
fieldPath: filter.targetFieldPath,
|
|
64
|
+
operator: "lte",
|
|
65
|
+
values: [parsed],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
const raw = formValues[filter.targetFieldPath] ?? "";
|
|
70
|
+
const fieldValue = sanitizeFilterValue(raw);
|
|
71
|
+
if (!fieldValue) continue;
|
|
72
|
+
if (affordance === "select") {
|
|
73
|
+
criteria.push({
|
|
74
|
+
objectApiName,
|
|
75
|
+
fieldPath: filter.targetFieldPath,
|
|
76
|
+
operator: "eq",
|
|
77
|
+
values: [fieldValue],
|
|
78
|
+
});
|
|
79
|
+
} else {
|
|
80
|
+
criteria.push({
|
|
81
|
+
objectApiName,
|
|
82
|
+
fieldPath: filter.targetFieldPath,
|
|
83
|
+
operator: "like",
|
|
84
|
+
values: [`%${fieldValue}%`],
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
onApplyFilters(criteria);
|
|
90
|
+
}, [objectApiName, filters, formValues, rangeMin, rangeMax, onApplyFilters]);
|
|
91
|
+
|
|
92
|
+
const handleReset = useCallback(() => {
|
|
93
|
+
setFormValues({});
|
|
94
|
+
setRangeMin({});
|
|
95
|
+
setRangeMax({});
|
|
96
|
+
onApplyFilters([]);
|
|
97
|
+
}, [onApplyFilters]);
|
|
98
|
+
|
|
99
|
+
if (!objectApiName) return null;
|
|
100
|
+
if (loading) {
|
|
101
|
+
return (
|
|
102
|
+
<div className="mb-4 flex flex-wrap gap-2 items-center text-sm text-gray-500">
|
|
103
|
+
Loading filters…
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (error || !filters?.length) return null;
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<div className="mb-4 flex flex-wrap items-end gap-3">
|
|
111
|
+
{filters.map((filter) => {
|
|
112
|
+
if (!filter.targetFieldPath) return null;
|
|
113
|
+
const affordance = (filter.affordance ?? "").toLowerCase();
|
|
114
|
+
if (affordance === "range") {
|
|
115
|
+
return (
|
|
116
|
+
<div key={filter.targetFieldPath} className="flex items-center gap-2 flex-wrap">
|
|
117
|
+
<span className="text-sm text-gray-600">{filter.label}</span>
|
|
118
|
+
<input
|
|
119
|
+
type="text"
|
|
120
|
+
className={inputClass}
|
|
121
|
+
placeholder="Min"
|
|
122
|
+
value={rangeMin[filter.targetFieldPath] ?? ""}
|
|
123
|
+
onChange={(e) =>
|
|
124
|
+
handleRangeChange(
|
|
125
|
+
filter.targetFieldPath,
|
|
126
|
+
e.target.value,
|
|
127
|
+
rangeMax[filter.targetFieldPath] ?? "",
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
aria-label={`${filter.label} min`}
|
|
131
|
+
/>
|
|
132
|
+
<input
|
|
133
|
+
type="text"
|
|
134
|
+
className={inputClass}
|
|
135
|
+
placeholder="Max"
|
|
136
|
+
value={rangeMax[filter.targetFieldPath] ?? ""}
|
|
137
|
+
onChange={(e) =>
|
|
138
|
+
handleRangeChange(
|
|
139
|
+
filter.targetFieldPath,
|
|
140
|
+
rangeMin[filter.targetFieldPath] ?? "",
|
|
141
|
+
e.target.value,
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
aria-label={`${filter.label} max`}
|
|
145
|
+
/>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (affordance === "select") {
|
|
150
|
+
const options = picklistValues?.[filter.targetFieldPath] ?? [];
|
|
151
|
+
return (
|
|
152
|
+
<div key={filter.targetFieldPath} className="flex items-center gap-2">
|
|
153
|
+
<label className="text-sm text-gray-600">{filter.label}</label>
|
|
154
|
+
<select
|
|
155
|
+
className={inputClass}
|
|
156
|
+
value={formValues[filter.targetFieldPath] ?? ""}
|
|
157
|
+
onChange={(e) => handleChange(filter.targetFieldPath, e.target.value)}
|
|
158
|
+
aria-label={filter.label}
|
|
159
|
+
>
|
|
160
|
+
<option value="">Any</option>
|
|
161
|
+
{options.map((opt) => (
|
|
162
|
+
<option key={opt.value} value={opt.value}>
|
|
163
|
+
{opt.label}
|
|
164
|
+
</option>
|
|
165
|
+
))}
|
|
166
|
+
</select>
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return (
|
|
171
|
+
<div key={filter.targetFieldPath} className="flex items-center gap-2">
|
|
172
|
+
<label className="text-sm text-gray-600">{filter.label}</label>
|
|
173
|
+
<input
|
|
174
|
+
type="text"
|
|
175
|
+
className={inputClass}
|
|
176
|
+
placeholder={filter.attributes?.placeholder ?? filter.label}
|
|
177
|
+
value={formValues[filter.targetFieldPath] ?? ""}
|
|
178
|
+
onChange={(e) => handleChange(filter.targetFieldPath, e.target.value)}
|
|
179
|
+
aria-label={filter.label}
|
|
180
|
+
/>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
})}
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
onClick={handleApply}
|
|
187
|
+
className="h-9 px-3 rounded-md bg-purple-700 text-white text-sm font-medium hover:bg-purple-800"
|
|
188
|
+
>
|
|
189
|
+
Apply Filters
|
|
190
|
+
</button>
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
onClick={handleReset}
|
|
194
|
+
className="h-9 px-3 text-sm text-gray-600 hover:text-gray-800"
|
|
195
|
+
>
|
|
196
|
+
Reset
|
|
197
|
+
</button>
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ActiveFilter, FilterFieldConfig } from "../lib/listFilters.js";
|
|
2
|
+
|
|
3
|
+
const inputClass =
|
|
4
|
+
"h-9 rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary-purple/20 focus:border-primary-purple";
|
|
5
|
+
|
|
6
|
+
interface ListPageFiltersProps {
|
|
7
|
+
fieldConfigs: FilterFieldConfig[];
|
|
8
|
+
activeFilters: ActiveFilter[];
|
|
9
|
+
onFiltersChange: (filters: ActiveFilter[]) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ListPageFilters({
|
|
13
|
+
fieldConfigs,
|
|
14
|
+
activeFilters,
|
|
15
|
+
onFiltersChange,
|
|
16
|
+
}: ListPageFiltersProps) {
|
|
17
|
+
const addFilter = () => {
|
|
18
|
+
const first = fieldConfigs[0];
|
|
19
|
+
if (!first) return;
|
|
20
|
+
onFiltersChange([...activeFilters, { fieldKey: first.key, value: "" }]);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const updateFilter = (index: number, updates: Partial<ActiveFilter>) => {
|
|
24
|
+
const next = [...activeFilters];
|
|
25
|
+
next[index] = { ...next[index]!, ...updates };
|
|
26
|
+
onFiltersChange(next);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const removeFilter = (index: number) => {
|
|
30
|
+
onFiltersChange(activeFilters.filter((_, i) => i !== index));
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
if (fieldConfigs.length === 0) return null;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="flex flex-wrap items-center gap-2 mb-4">
|
|
37
|
+
{activeFilters.map((filter, index) => {
|
|
38
|
+
const config = fieldConfigs.find((c) => c.key === filter.fieldKey) ?? fieldConfigs[0]!;
|
|
39
|
+
return (
|
|
40
|
+
<div key={index} className="flex items-center gap-2 flex-wrap">
|
|
41
|
+
<select
|
|
42
|
+
className={inputClass}
|
|
43
|
+
value={filter.fieldKey}
|
|
44
|
+
onChange={(e) => updateFilter(index, { fieldKey: e.target.value })}
|
|
45
|
+
aria-label="Filter field"
|
|
46
|
+
>
|
|
47
|
+
{fieldConfigs.map((c) => (
|
|
48
|
+
<option key={c.key} value={c.key}>
|
|
49
|
+
{c.label}
|
|
50
|
+
</option>
|
|
51
|
+
))}
|
|
52
|
+
</select>
|
|
53
|
+
{config.type === "select" && config.options && config.options.length > 0 ? (
|
|
54
|
+
<select
|
|
55
|
+
className={inputClass}
|
|
56
|
+
value={filter.value}
|
|
57
|
+
onChange={(e) => updateFilter(index, { value: e.target.value })}
|
|
58
|
+
aria-label={`Filter by ${config.label}`}
|
|
59
|
+
>
|
|
60
|
+
<option value="">Any</option>
|
|
61
|
+
{config.options.map((opt) => (
|
|
62
|
+
<option key={opt.value} value={opt.value}>
|
|
63
|
+
{opt.label}
|
|
64
|
+
</option>
|
|
65
|
+
))}
|
|
66
|
+
</select>
|
|
67
|
+
) : (
|
|
68
|
+
<input
|
|
69
|
+
type="text"
|
|
70
|
+
className={inputClass}
|
|
71
|
+
value={filter.value}
|
|
72
|
+
onChange={(e) => updateFilter(index, { value: e.target.value })}
|
|
73
|
+
placeholder={config.label}
|
|
74
|
+
aria-label={`Filter by ${config.label}`}
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
onClick={() => removeFilter(index)}
|
|
80
|
+
className="text-gray-500 hover:text-gray-700 text-sm px-1"
|
|
81
|
+
aria-label="Remove filter"
|
|
82
|
+
>
|
|
83
|
+
✕
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
})}
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
onClick={addFilter}
|
|
91
|
+
className="text-sm text-purple-700 hover:text-purple-800 font-medium"
|
|
92
|
+
>
|
|
93
|
+
+ Add filter
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { useNavigate } from "react-router";
|
|
3
|
+
import { PATHS } from "../lib/routeConfig.js";
|
|
3
4
|
import { Card } from "@/components/ui/card";
|
|
4
5
|
import { Button } from "@/components/ui/button";
|
|
5
6
|
import type { MaintenanceRequest } from "../lib/types.js";
|
|
@@ -34,7 +35,7 @@ export const MaintenanceTable: React.FC<MaintenanceTableProps> = ({ requests })
|
|
|
34
35
|
const navigate = useNavigate();
|
|
35
36
|
|
|
36
37
|
const handleSeeAll = () => {
|
|
37
|
-
navigate(
|
|
38
|
+
navigate(PATHS.MAINTENANCE_REQUESTS);
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
return (
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const selectClass =
|
|
2
|
+
"h-9 rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary-purple/20 focus:border-primary-purple min-w-[180px]";
|
|
3
|
+
|
|
4
|
+
export interface ObjectSelectOption {
|
|
5
|
+
value: string;
|
|
6
|
+
label: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ObjectSelectProps {
|
|
10
|
+
options: ObjectSelectOption[];
|
|
11
|
+
value: string;
|
|
12
|
+
onChange: (value: string) => void;
|
|
13
|
+
"aria-label"?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Shadcn-style select for object type (Properties, Maintenance Requests, Maintenance Workers).
|
|
18
|
+
*/
|
|
19
|
+
export function ObjectSelect({
|
|
20
|
+
options,
|
|
21
|
+
value,
|
|
22
|
+
onChange,
|
|
23
|
+
"aria-label": ariaLabel = "Search in",
|
|
24
|
+
}: ObjectSelectProps) {
|
|
25
|
+
return (
|
|
26
|
+
<select
|
|
27
|
+
className={selectClass}
|
|
28
|
+
value={value}
|
|
29
|
+
onChange={(e) => onChange(e.target.value)}
|
|
30
|
+
aria-label={ariaLabel}
|
|
31
|
+
>
|
|
32
|
+
{options.map((opt) => (
|
|
33
|
+
<option key={opt.value} value={opt.value}>
|
|
34
|
+
{opt.label}
|
|
35
|
+
</option>
|
|
36
|
+
))}
|
|
37
|
+
</select>
|
|
38
|
+
);
|
|
39
|
+
}
|