@salesforce/webapp-template-app-react-sample-b2x-experimental 1.112.7 → 1.112.9
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 +19 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/package.json +6 -5
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/graphql-operations-types.ts +12058 -214
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/graphqlClient.ts +18 -15
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/{propertyListingGraphQL.ts → properties/propertyListingGraphQL.ts} +1 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/appLayout.tsx +4 -2
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{TopBar.tsx → layout/TopBar.tsx} +2 -2
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{MaintenanceRequestList.tsx → maintenanceRequests/MaintenanceRequestList.tsx} +4 -4
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{MaintenanceRequestListItem.tsx → maintenanceRequests/MaintenanceRequestListItem.tsx} +3 -3
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/maintenanceRequests/MaintenanceSummaryDetailsModal.tsx +87 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{PropertyListingCard.tsx → properties/PropertyListingCard.tsx} +1 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/{features/global-search/components/search/SearchPagination.tsx → components/properties/PropertyListingSearchPagination.tsx} +20 -28
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/constants/propertyListing.ts +4 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/api/accountSearchService.ts +46 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +19 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +19 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/api/query/getAccountDetail.graphql +121 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/api/query/searchAccounts.graphql +51 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +357 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/pages/AccountSearch.tsx +303 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/pages/Home.tsx +34 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/api/objectSearchService.ts +84 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/ActiveFilters.tsx +89 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/FilterContext.tsx +73 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/ObjectBreadcrumb.tsx +66 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/PaginationControls.tsx +109 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/SearchBar.tsx +41 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/SortControl.tsx +143 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/BooleanFilter.tsx +74 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/DateFilter.tsx +121 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/DateRangeFilter.tsx +69 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/MultiSelectFilter.tsx +98 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/NumericRangeFilter.tsx +85 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/SearchFilter.tsx +37 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/SelectFilter.tsx +93 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/TextFilter.tsx +74 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/hooks/useAsyncData.ts +54 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/hooks/useCachedAsyncData.ts +184 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/hooks/useObjectSearchParams.ts +247 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/utils/debounce.ts +22 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/utils/fieldUtils.ts +29 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/utils/filterUtils.ts +372 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/utils/sortUtils.ts +38 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useMaintenanceRequests.ts +1 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyAddresses.ts +2 -2
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyDetail.ts +1 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingAmenities.ts +2 -2
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingSearch.ts +2 -2
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyMapMarkers.ts +3 -3
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyPrimaryImages.ts +2 -2
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Application.tsx +3 -3
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Dashboard.tsx +2 -2
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Home.tsx +4 -2
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Maintenance.tsx +2 -2
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyDetails.tsx +1 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertySearch.tsx +12 -10
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/routes.tsx +6 -18
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/propertyListingPaginationUtils.ts +18 -0
- package/dist/package-lock.json +2 -2
- package/dist/package.json +1 -1
- package/dist/scripts/graphql-search.sh +69 -17
- package/package.json +1 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/MaintenanceDetailsModal.tsx +0 -128
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/api/objectDetailService.ts +0 -102
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/api/objectInfoGraphQLService.ts +0 -137
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/api/objectInfoService.ts +0 -95
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/api/recordListGraphQLService.ts +0 -364
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/DetailFields.tsx +0 -55
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/DetailForm.tsx +0 -146
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/DetailHeader.tsx +0 -34
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/DetailLayoutSections.tsx +0 -80
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/Section.tsx +0 -108
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/SectionRow.tsx +0 -20
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/UiApiDetailForm.tsx +0 -140
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/formatted/FieldValueDisplay.tsx +0 -73
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/formatted/FormattedAddress.tsx +0 -29
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/formatted/FormattedEmail.tsx +0 -17
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/formatted/FormattedPhone.tsx +0 -24
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/formatted/FormattedText.tsx +0 -11
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/formatted/FormattedUrl.tsx +0 -29
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/filters/FilterField.tsx +0 -54
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/filters/FilterInput.tsx +0 -55
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/filters/FilterSelect.tsx +0 -72
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/filters/FiltersPanel.tsx +0 -380
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/forms/filters-form.tsx +0 -114
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/forms/submit-button.tsx +0 -47
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/search/GlobalSearchInput.tsx +0 -114
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/search/ResultCardFields.tsx +0 -71
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/search/SearchHeader.tsx +0 -31
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/search/SearchResultCard.tsx +0 -138
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/search/SearchResultsPanel.tsx +0 -197
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/shared/LoadingFallback.tsx +0 -61
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/constants.ts +0 -39
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/filters/FilterInput.tsx +0 -55
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/filters/FilterSelect.tsx +0 -72
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/hooks/form.tsx +0 -209
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/hooks/useObjectInfoBatch.ts +0 -72
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/hooks/useObjectSearchData.ts +0 -174
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/hooks/useRecordDetailLayout.ts +0 -137
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/hooks/useRecordListGraphQL.ts +0 -135
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/pages/DetailPage.tsx +0 -109
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/pages/GlobalSearch.tsx +0 -235
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/types/filters/filters.ts +0 -121
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/types/filters/picklist.ts +0 -6
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/types/objectInfo/objectInfo.ts +0 -49
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/types/recordDetail/recordDetail.ts +0 -61
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/types/schema.d.ts +0 -200
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/apiUtils.ts +0 -59
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/cacheUtils.ts +0 -76
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/debounce.ts +0 -90
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/fieldUtils.ts +0 -354
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/fieldValueExtractor.ts +0 -67
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/filterUtils.ts +0 -32
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/formDataTransformUtils.ts +0 -260
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/formUtils.ts +0 -142
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/graphQLNodeFieldUtils.ts +0 -186
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/graphQLObjectInfoAdapter.ts +0 -77
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/graphQLRecordAdapter.ts +0 -90
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/layoutTransformUtils.ts +0 -236
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/linkUtils.ts +0 -14
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/paginationUtils.ts +0 -49
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/recordUtils.ts +0 -159
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/sanitizationUtils.ts +0 -50
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingPriceRange.ts +0 -64
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/index.ts +0 -120
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/About.tsx +0 -8
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/HelpCenter.tsx +0 -29
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyListings.tsx +0 -100
- /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/{applicationApi.ts → applications/applicationApi.ts} +0 -0
- /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/{maintenanceRequestApi.ts → maintenanceRequests/maintenanceRequestApi.ts} +0 -0
- /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/{propertyDetailGraphQL.ts → properties/propertyDetailGraphQL.ts} +0 -0
- /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{WeatherWidget.tsx → dashboard/WeatherWidget.tsx} +0 -0
- /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{NavMenu.tsx → layout/VerticalNav.tsx} +0 -0
- /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{MaintenanceRequestIcon.tsx → maintenanceRequests/MaintenanceRequestIcon.tsx} +0 -0
- /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{StatusBadge.tsx → maintenanceRequests/StatusBadge.tsx} +0 -0
- /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{PropertyMap.tsx → properties/PropertyMap.tsx} +0 -0
- /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{PropertySearchFilters.tsx → properties/PropertySearchFilters.tsx} +0 -0
- /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/{features/global-search/types/search → types}/searchResults.ts +0 -0
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ResultCardFields Component
|
|
3
|
-
*
|
|
4
|
-
* Displays secondary fields (up to 3) for a search result card.
|
|
5
|
-
* Excludes the primary field and handles nested field values.
|
|
6
|
-
*
|
|
7
|
-
* @param record - The search result record data
|
|
8
|
-
* @param columns - Array of column definitions
|
|
9
|
-
* @param excludeFieldApiName - Field API name to exclude (usually the primary field)
|
|
10
|
-
*
|
|
11
|
-
* @remarks
|
|
12
|
-
* - Displays up to 3 secondary fields
|
|
13
|
-
* - Handles nested field paths (e.g., "Owner.Alias")
|
|
14
|
-
* - Skips fields with null/undefined/empty values
|
|
15
|
-
* - Responsive layout (vertical on mobile, horizontal on desktop)
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```tsx
|
|
19
|
-
* <ResultCardFields
|
|
20
|
-
* record={searchResult}
|
|
21
|
-
* columns={columns}
|
|
22
|
-
* excludeFieldApiName="Name"
|
|
23
|
-
* />
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
import type { Column, SearchResultRecordData } from "../../types/search/searchResults";
|
|
27
|
-
import { getNestedFieldValue } from "../../utils/fieldUtils";
|
|
28
|
-
|
|
29
|
-
interface ResultCardFieldsProps {
|
|
30
|
-
record: SearchResultRecordData;
|
|
31
|
-
columns: Column[];
|
|
32
|
-
excludeFieldApiName?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export default function ResultCardFields({
|
|
36
|
-
record,
|
|
37
|
-
columns,
|
|
38
|
-
excludeFieldApiName,
|
|
39
|
-
}: ResultCardFieldsProps) {
|
|
40
|
-
const secondaryFields = columns.filter(
|
|
41
|
-
(col) => col && col.fieldApiName && col.fieldApiName !== excludeFieldApiName,
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<dl className="space-y-2" aria-label="Additional record information">
|
|
46
|
-
{secondaryFields.map((column) => {
|
|
47
|
-
if (!column || !column.fieldApiName) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const displayValue = getNestedFieldValue(record.fields, column.fieldApiName);
|
|
52
|
-
|
|
53
|
-
if (displayValue === null || displayValue === undefined || displayValue === "") {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<div
|
|
59
|
-
key={column.fieldApiName}
|
|
60
|
-
className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2"
|
|
61
|
-
>
|
|
62
|
-
<dt className="text-sm font-medium text-muted-foreground min-w-[100px]">
|
|
63
|
-
{column.label || column.fieldApiName}:
|
|
64
|
-
</dt>
|
|
65
|
-
<dd className="text-sm text-foreground">{displayValue}</dd>
|
|
66
|
-
</div>
|
|
67
|
-
);
|
|
68
|
-
})}
|
|
69
|
-
</dl>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SearchHeader Component
|
|
3
|
-
*
|
|
4
|
-
* Displays the header for search results or browse-all (same UI).
|
|
5
|
-
* labelPlural comes from object metadata (e.g. useObjectInfoBatch) so it is not hard-coded.
|
|
6
|
-
*/
|
|
7
|
-
interface SearchHeaderProps {
|
|
8
|
-
query?: string;
|
|
9
|
-
isBrowseAll?: boolean;
|
|
10
|
-
/** Plural label for the primary object (e.g. "Accounts"). From object metadata. */
|
|
11
|
-
labelPlural?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default function SearchHeader({
|
|
15
|
-
query,
|
|
16
|
-
isBrowseAll,
|
|
17
|
-
labelPlural = "records",
|
|
18
|
-
}: SearchHeaderProps) {
|
|
19
|
-
return (
|
|
20
|
-
<header className="mb-6" aria-label="Search results header">
|
|
21
|
-
<h1 className="text-3xl font-bold mb-2">
|
|
22
|
-
{isBrowseAll ? `Browse All ${labelPlural}` : "Search Results"}
|
|
23
|
-
</h1>
|
|
24
|
-
{!isBrowseAll && query && (
|
|
25
|
-
<p className="text-lg" aria-live="polite">
|
|
26
|
-
Results for: <span className="font-semibold">"{query}"</span>
|
|
27
|
-
</p>
|
|
28
|
-
)}
|
|
29
|
-
</header>
|
|
30
|
-
);
|
|
31
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SearchResultCard Component
|
|
3
|
-
*
|
|
4
|
-
* Displays a single search result as a card with primary and secondary fields.
|
|
5
|
-
* Clicking the card navigates to the detail page for that record.
|
|
6
|
-
*
|
|
7
|
-
* @param record - The search result record data to display
|
|
8
|
-
* @param columns - Array of column definitions for field display
|
|
9
|
-
* @param objectApiName - API name of the object (path param in detail URL: /object/:objectApiName/:recordId)
|
|
10
|
-
*
|
|
11
|
-
* @remarks
|
|
12
|
-
* - Automatically identifies the primary field (usually "Name")
|
|
13
|
-
* - Displays up to 3 secondary fields
|
|
14
|
-
* - Supports keyboard navigation (Enter/Space to navigate)
|
|
15
|
-
* - Handles nested field values (e.g., "Owner.Alias")
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```tsx
|
|
19
|
-
* <SearchResultCard
|
|
20
|
-
* record={searchResult}
|
|
21
|
-
* columns={columns}
|
|
22
|
-
* objectApiName="Account"
|
|
23
|
-
* />
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
import React from "react";
|
|
27
|
-
import { useNavigate } from "react-router";
|
|
28
|
-
import { useMemo, useCallback } from "react";
|
|
29
|
-
import {
|
|
30
|
-
Card,
|
|
31
|
-
CardContent,
|
|
32
|
-
CardHeader,
|
|
33
|
-
CardTitle,
|
|
34
|
-
} from "../../../../components/ui/card";
|
|
35
|
-
import type { Column, SearchResultRecordData } from "../../types/search/searchResults";
|
|
36
|
-
import { getNestedFieldValue } from "../../utils/fieldUtils";
|
|
37
|
-
import ResultCardFields from "./ResultCardFields";
|
|
38
|
-
import { OBJECT_API_NAMES } from "../../constants";
|
|
39
|
-
|
|
40
|
-
interface SearchResultCardProps {
|
|
41
|
-
record: SearchResultRecordData;
|
|
42
|
-
columns: Column[];
|
|
43
|
-
objectApiName?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export default function SearchResultCard({
|
|
47
|
-
record,
|
|
48
|
-
columns,
|
|
49
|
-
objectApiName,
|
|
50
|
-
}: SearchResultCardProps) {
|
|
51
|
-
const navigate = useNavigate();
|
|
52
|
-
|
|
53
|
-
const validColumns = useMemo(
|
|
54
|
-
() => (columns && Array.isArray(columns) && columns.length > 0 ? columns : []),
|
|
55
|
-
[columns],
|
|
56
|
-
);
|
|
57
|
-
const validRecord =
|
|
58
|
-
record?.id && record?.fields && typeof record.fields === "object" ? record : null;
|
|
59
|
-
|
|
60
|
-
const detailPath = useMemo(
|
|
61
|
-
() =>
|
|
62
|
-
validRecord
|
|
63
|
-
? `/object/${objectApiName?.trim() || OBJECT_API_NAMES[0]}/${validRecord.id}`
|
|
64
|
-
: "",
|
|
65
|
-
[validRecord, objectApiName],
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
const handleClick = useCallback(() => {
|
|
69
|
-
if (validRecord?.id) navigate(detailPath);
|
|
70
|
-
}, [validRecord?.id, detailPath, navigate]);
|
|
71
|
-
|
|
72
|
-
const handleKeyDown = useCallback(
|
|
73
|
-
(e: React.KeyboardEvent) => {
|
|
74
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
75
|
-
e.preventDefault();
|
|
76
|
-
handleClick();
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
[handleClick],
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const primaryField = useMemo(() => {
|
|
83
|
-
return (
|
|
84
|
-
validColumns.find(
|
|
85
|
-
(col) =>
|
|
86
|
-
col &&
|
|
87
|
-
col.fieldApiName &&
|
|
88
|
-
(col.fieldApiName.toLowerCase() === "name" ||
|
|
89
|
-
col.fieldApiName.toLowerCase().includes("name")),
|
|
90
|
-
) ||
|
|
91
|
-
validColumns[0] ||
|
|
92
|
-
null
|
|
93
|
-
);
|
|
94
|
-
}, [validColumns]);
|
|
95
|
-
|
|
96
|
-
const primaryValue = useMemo(() => {
|
|
97
|
-
return primaryField && primaryField.fieldApiName && validRecord?.fields
|
|
98
|
-
? getNestedFieldValue(validRecord.fields, primaryField.fieldApiName) || "Untitled"
|
|
99
|
-
: "Untitled";
|
|
100
|
-
}, [primaryField, validRecord]);
|
|
101
|
-
|
|
102
|
-
const secondaryColumns = useMemo(() => {
|
|
103
|
-
return validColumns.filter(
|
|
104
|
-
(col) => col && col.fieldApiName && col.fieldApiName !== primaryField?.fieldApiName,
|
|
105
|
-
);
|
|
106
|
-
}, [validColumns, primaryField]);
|
|
107
|
-
|
|
108
|
-
if (!validRecord) return null;
|
|
109
|
-
if (validColumns.length === 0) return null;
|
|
110
|
-
|
|
111
|
-
return (
|
|
112
|
-
<Card
|
|
113
|
-
className="cursor-pointer hover:shadow-md transition-shadow focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-offset-2"
|
|
114
|
-
onClick={handleClick}
|
|
115
|
-
onKeyDown={handleKeyDown}
|
|
116
|
-
role="button"
|
|
117
|
-
tabIndex={0}
|
|
118
|
-
aria-label={`View details for ${primaryValue}`}
|
|
119
|
-
aria-describedby={`result-${validRecord.id}-description`}
|
|
120
|
-
>
|
|
121
|
-
<CardHeader>
|
|
122
|
-
<CardTitle className="text-lg" id={`result-${validRecord.id}-title`}>
|
|
123
|
-
{primaryValue}
|
|
124
|
-
</CardTitle>
|
|
125
|
-
</CardHeader>
|
|
126
|
-
<CardContent>
|
|
127
|
-
<div id={`result-${validRecord.id}-description`} className="sr-only">
|
|
128
|
-
Search result: {primaryValue}
|
|
129
|
-
</div>
|
|
130
|
-
<ResultCardFields
|
|
131
|
-
record={validRecord}
|
|
132
|
-
columns={secondaryColumns}
|
|
133
|
-
excludeFieldApiName={primaryField?.fieldApiName}
|
|
134
|
-
/>
|
|
135
|
-
</CardContent>
|
|
136
|
-
</Card>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SearchResultsPanel Component
|
|
3
|
-
*
|
|
4
|
-
* Displays the search results panel with loading, error, and empty states.
|
|
5
|
-
* Renders a list of SearchResultCard components and pagination controls.
|
|
6
|
-
*
|
|
7
|
-
* @param columns - Array of column definitions for displaying result data
|
|
8
|
-
* @param results - Array of search result records to display
|
|
9
|
-
* @param columnsLoading - Whether column metadata is currently loading
|
|
10
|
-
* @param resultsLoading - Whether search results are currently loading
|
|
11
|
-
* @param columnsError - Error message if column fetch failed
|
|
12
|
-
* @param resultsError - Error message if results fetch failed
|
|
13
|
-
* @param currentPageToken - Current pagination token
|
|
14
|
-
* @param pageSize - Number of results per page
|
|
15
|
-
* @param onPageChange - Callback when pagination changes; second arg optional: "next" | "prev" | "first" (cursor-stack pagination)
|
|
16
|
-
* @param onPageSizeChange - Callback when page size changes
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```tsx
|
|
20
|
-
* <SearchResultsPanel
|
|
21
|
-
* columns={columns}
|
|
22
|
-
* results={results}
|
|
23
|
-
* columnsLoading={false}
|
|
24
|
-
* resultsLoading={false}
|
|
25
|
-
* columnsError={null}
|
|
26
|
-
* resultsError={null}
|
|
27
|
-
* currentPageToken="0"
|
|
28
|
-
* pageSize={25}
|
|
29
|
-
* onPageChange={(token, direction) => handlePageChange(token, direction)}
|
|
30
|
-
* onPageSizeChange={(size) => setPageSize(size)}
|
|
31
|
-
* />
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
import { useMemo } from "react";
|
|
35
|
-
import { Alert, AlertDescription, AlertTitle } from "../../../../components/ui/alert";
|
|
36
|
-
import { Skeleton } from "../../../../components/ui/skeleton";
|
|
37
|
-
import { AlertCircle } from "lucide-react";
|
|
38
|
-
import {
|
|
39
|
-
Select,
|
|
40
|
-
SelectContent,
|
|
41
|
-
SelectItem,
|
|
42
|
-
SelectTrigger,
|
|
43
|
-
SelectValue,
|
|
44
|
-
} from "../../../../components/ui/select";
|
|
45
|
-
import { Label } from "../../../../components/ui/label";
|
|
46
|
-
import SearchResultCard from "./SearchResultCard";
|
|
47
|
-
import SearchPagination from "./SearchPagination";
|
|
48
|
-
import type { Column, SearchResultRecord } from "../../types/search/searchResults";
|
|
49
|
-
import { getSafeKey } from "../../utils/recordUtils";
|
|
50
|
-
|
|
51
|
-
interface SearchResultsPanelProps {
|
|
52
|
-
/** API name of the object being searched (e.g. for detail page navigation). */
|
|
53
|
-
objectApiName?: string;
|
|
54
|
-
columns: Column[];
|
|
55
|
-
results: SearchResultRecord[];
|
|
56
|
-
columnsLoading: boolean;
|
|
57
|
-
resultsLoading: boolean;
|
|
58
|
-
columnsError: string | null;
|
|
59
|
-
resultsError: string | null;
|
|
60
|
-
currentPageToken: string;
|
|
61
|
-
nextPageToken: string | null;
|
|
62
|
-
previousPageToken: string | null;
|
|
63
|
-
hasNextPage?: boolean;
|
|
64
|
-
hasPreviousPage?: boolean;
|
|
65
|
-
pageSize: number;
|
|
66
|
-
sortBy: string;
|
|
67
|
-
onPageChange: (newPageToken: string, direction?: "next" | "prev" | "first") => void;
|
|
68
|
-
onPageSizeChange: (newPageSize: number) => void;
|
|
69
|
-
onSortByChange: (newSortBy: string) => void;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export default function SearchResultsPanel({
|
|
73
|
-
objectApiName,
|
|
74
|
-
columns,
|
|
75
|
-
results,
|
|
76
|
-
columnsLoading,
|
|
77
|
-
resultsLoading,
|
|
78
|
-
columnsError,
|
|
79
|
-
resultsError,
|
|
80
|
-
currentPageToken,
|
|
81
|
-
nextPageToken,
|
|
82
|
-
previousPageToken,
|
|
83
|
-
hasNextPage = false,
|
|
84
|
-
hasPreviousPage = false,
|
|
85
|
-
pageSize,
|
|
86
|
-
sortBy,
|
|
87
|
-
onPageChange,
|
|
88
|
-
onPageSizeChange,
|
|
89
|
-
onSortByChange,
|
|
90
|
-
}: SearchResultsPanelProps) {
|
|
91
|
-
const sortableColumns = useMemo(() => columns.filter(({ sortable }) => sortable), [columns]);
|
|
92
|
-
|
|
93
|
-
const validResults = useMemo(
|
|
94
|
-
() => results.filter((record) => record && record.record && record.record.id),
|
|
95
|
-
[results],
|
|
96
|
-
);
|
|
97
|
-
if (columnsError || resultsError) {
|
|
98
|
-
return (
|
|
99
|
-
<Alert variant="destructive" role="alert">
|
|
100
|
-
<AlertCircle className="h-4 w-4" aria-hidden="true" />
|
|
101
|
-
<AlertTitle>Error</AlertTitle>
|
|
102
|
-
<AlertDescription>
|
|
103
|
-
{columnsError || resultsError || "Failed to load search results"}
|
|
104
|
-
</AlertDescription>
|
|
105
|
-
</Alert>
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (resultsLoading || columnsLoading) {
|
|
110
|
-
return (
|
|
111
|
-
<div
|
|
112
|
-
className="space-y-4"
|
|
113
|
-
role="status"
|
|
114
|
-
aria-live="polite"
|
|
115
|
-
aria-label="Loading search results"
|
|
116
|
-
>
|
|
117
|
-
<span className="sr-only">Loading search results</span>
|
|
118
|
-
{[1, 2, 3].map((i) => (
|
|
119
|
-
<div key={i} className="border rounded-lg p-6" aria-hidden="true">
|
|
120
|
-
<Skeleton className="h-6 w-3/4 mb-4" />
|
|
121
|
-
<Skeleton className="h-4 w-full mb-2" />
|
|
122
|
-
<Skeleton className="h-4 w-2/3" />
|
|
123
|
-
</div>
|
|
124
|
-
))}
|
|
125
|
-
</div>
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (results.length === 0) {
|
|
130
|
-
return (
|
|
131
|
-
<div className="text-center py-12" role="status" aria-live="polite">
|
|
132
|
-
<p className="text-lg mb-2">No results found</p>
|
|
133
|
-
<p className="text-sm">Try adjusting your search query or filters</p>
|
|
134
|
-
</div>
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return (
|
|
139
|
-
<>
|
|
140
|
-
{sortableColumns.length > 0 && (
|
|
141
|
-
<div
|
|
142
|
-
className="mb-6 flex items-center gap-2 justify-end"
|
|
143
|
-
role="group"
|
|
144
|
-
aria-label="Sort options"
|
|
145
|
-
>
|
|
146
|
-
<Label htmlFor="sort-by-select" className="text-sm font-normal whitespace-nowrap">
|
|
147
|
-
Sort by:
|
|
148
|
-
</Label>
|
|
149
|
-
<Select value={sortBy || ""} onValueChange={onSortByChange}>
|
|
150
|
-
<SelectTrigger
|
|
151
|
-
id="sort-by-select"
|
|
152
|
-
className="w-[200px]"
|
|
153
|
-
aria-label="Sort search results by field"
|
|
154
|
-
>
|
|
155
|
-
<SelectValue placeholder="Select field..." />
|
|
156
|
-
</SelectTrigger>
|
|
157
|
-
<SelectContent>
|
|
158
|
-
<SelectItem value="relevance">Relevance</SelectItem>
|
|
159
|
-
{sortableColumns.map((column) => (
|
|
160
|
-
<SelectItem key={column.fieldApiName} value={column.fieldApiName}>
|
|
161
|
-
{column.label}
|
|
162
|
-
</SelectItem>
|
|
163
|
-
))}
|
|
164
|
-
</SelectContent>
|
|
165
|
-
</Select>
|
|
166
|
-
</div>
|
|
167
|
-
)}
|
|
168
|
-
|
|
169
|
-
<div className="space-y-4 mb-6" role="list" aria-label="Search results list">
|
|
170
|
-
{validResults.map((record, index) => {
|
|
171
|
-
const recordId = record.record.id;
|
|
172
|
-
const safeKey = getSafeKey(recordId, index);
|
|
173
|
-
return (
|
|
174
|
-
<div key={safeKey} role="listitem">
|
|
175
|
-
<SearchResultCard
|
|
176
|
-
record={record.record}
|
|
177
|
-
columns={columns}
|
|
178
|
-
objectApiName={objectApiName}
|
|
179
|
-
/>
|
|
180
|
-
</div>
|
|
181
|
-
);
|
|
182
|
-
})}
|
|
183
|
-
</div>
|
|
184
|
-
|
|
185
|
-
<SearchPagination
|
|
186
|
-
currentPageToken={currentPageToken}
|
|
187
|
-
nextPageToken={nextPageToken}
|
|
188
|
-
previousPageToken={previousPageToken}
|
|
189
|
-
hasNextPage={hasNextPage}
|
|
190
|
-
hasPreviousPage={hasPreviousPage}
|
|
191
|
-
pageSize={pageSize}
|
|
192
|
-
onPageChange={onPageChange}
|
|
193
|
-
onPageSizeChange={onPageSizeChange}
|
|
194
|
-
/>
|
|
195
|
-
</>
|
|
196
|
-
);
|
|
197
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LoadingFallback Component
|
|
3
|
-
*
|
|
4
|
-
* Loading fallback component for Suspense boundaries.
|
|
5
|
-
* Displays a centered spinner while lazy-loaded components are being fetched.
|
|
6
|
-
*
|
|
7
|
-
* @remarks
|
|
8
|
-
* - Used with React Suspense for code splitting
|
|
9
|
-
* - Simple centered spinner design
|
|
10
|
-
* - Responsive and accessible
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```tsx
|
|
14
|
-
* <Suspense fallback={<LoadingFallback />}>
|
|
15
|
-
* <LazyComponent />
|
|
16
|
-
* </Suspense>
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
20
|
-
import { Spinner } from "../../../../components/ui/spinner";
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Spinner size variants based on content width.
|
|
24
|
-
*/
|
|
25
|
-
const spinnerVariants = cva("", {
|
|
26
|
-
variants: {
|
|
27
|
-
contentMaxWidth: {
|
|
28
|
-
sm: "size-6",
|
|
29
|
-
md: "size-8",
|
|
30
|
-
lg: "size-10",
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
defaultVariants: {
|
|
34
|
-
contentMaxWidth: "sm",
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
interface LoadingFallbackProps extends VariantProps<typeof spinnerVariants> {
|
|
39
|
-
/**
|
|
40
|
-
* Maximum width of the content container. Also scales the spinner size.
|
|
41
|
-
* @default "sm"
|
|
42
|
-
*/
|
|
43
|
-
contentMaxWidth?: "sm" | "md" | "lg";
|
|
44
|
-
/**
|
|
45
|
-
* Accessible label for screen readers.
|
|
46
|
-
* @default "Loading…"
|
|
47
|
-
*/
|
|
48
|
-
loadingText?: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export default function LoadingFallback({
|
|
52
|
-
contentMaxWidth = "sm",
|
|
53
|
-
loadingText = "Loading…",
|
|
54
|
-
}: LoadingFallbackProps) {
|
|
55
|
-
return (
|
|
56
|
-
<div className="flex justify-center" role="status" aria-live="polite">
|
|
57
|
-
<Spinner className={spinnerVariants({ contentMaxWidth })} aria-hidden="true" />
|
|
58
|
-
<span className="sr-only">{loadingText}</span>
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Application-wide Constants
|
|
3
|
-
*
|
|
4
|
-
* Defines constants used throughout the global search feature.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Object API names to search across in the global search feature.
|
|
9
|
-
* Currently supports single object search.
|
|
10
|
-
*
|
|
11
|
-
* @remarks
|
|
12
|
-
* - Array of Salesforce object API names
|
|
13
|
-
* - First element is used as the primary search object
|
|
14
|
-
* - Can be extended to support multiple objects in the future
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```tsx
|
|
18
|
-
* const objectApiName = OBJECT_API_NAMES[0]; // 'Account'
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export const OBJECT_API_NAMES = ["Account"] as const;
|
|
22
|
-
|
|
23
|
-
/** Fallback title when record display name cannot be resolved (e.g. before load or no name field). */
|
|
24
|
-
export const DEFAULT_DETAIL_PAGE_TITLE = "Untitled";
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Default page size for search results pagination.
|
|
28
|
-
* This should match one of the values in PAGE_SIZE_OPTIONS from paginationUtils.
|
|
29
|
-
*
|
|
30
|
-
* @remarks
|
|
31
|
-
* - Default value is 20 (second option in PAGE_SIZE_OPTIONS)
|
|
32
|
-
* - Can be changed by user via pagination controls
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```tsx
|
|
36
|
-
* const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export const DEFAULT_PAGE_SIZE = 20;
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FilterInput Component
|
|
3
|
-
*
|
|
4
|
-
* Renders a text input field for filter values.
|
|
5
|
-
* Used for filters that don't have a picklist (affordance !== 'select').
|
|
6
|
-
*
|
|
7
|
-
* @param filter - Filter definition containing field path, label, and attributes
|
|
8
|
-
* @param value - Current filter input value
|
|
9
|
-
* @param onChange - Callback when input value changes
|
|
10
|
-
*
|
|
11
|
-
* @remarks
|
|
12
|
-
* - Displays filter label or field path as the label
|
|
13
|
-
* - Shows placeholder text from filter attributes or generates default
|
|
14
|
-
* - Displays help message if available
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```tsx
|
|
18
|
-
* <FilterInput
|
|
19
|
-
* filter={textFilter}
|
|
20
|
-
* value={filterValue}
|
|
21
|
-
* onChange={(value) => setFilterValue(value)}
|
|
22
|
-
* />
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
import { Input } from "../../../components/ui/input";
|
|
26
|
-
import { Field, FieldLabel, FieldDescription } from "../../../components/ui/field";
|
|
27
|
-
import type { Filter } from "../types/filters/filters";
|
|
28
|
-
|
|
29
|
-
interface FilterInputProps {
|
|
30
|
-
filter: Filter;
|
|
31
|
-
value: string;
|
|
32
|
-
onChange: (value: string) => void;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export default function FilterInput({ filter, value, onChange }: FilterInputProps) {
|
|
36
|
-
return (
|
|
37
|
-
<Field>
|
|
38
|
-
<FieldLabel htmlFor={filter.targetFieldPath}>
|
|
39
|
-
{filter.label || filter.targetFieldPath}
|
|
40
|
-
</FieldLabel>
|
|
41
|
-
<Input
|
|
42
|
-
id={filter.targetFieldPath}
|
|
43
|
-
type="text"
|
|
44
|
-
value={value}
|
|
45
|
-
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
|
|
46
|
-
placeholder={
|
|
47
|
-
filter.attributes?.placeholder ||
|
|
48
|
-
`Enter ${(filter.label || filter.targetFieldPath).toLowerCase()}`
|
|
49
|
-
}
|
|
50
|
-
aria-label={filter.label || filter.targetFieldPath}
|
|
51
|
-
/>
|
|
52
|
-
{filter.helpMessage && <FieldDescription>{filter.helpMessage}</FieldDescription>}
|
|
53
|
-
</Field>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FilterSelect Component
|
|
3
|
-
*
|
|
4
|
-
* Renders a dropdown select field for filter values with picklist options.
|
|
5
|
-
* Used for filters with affordance === 'select'.
|
|
6
|
-
*
|
|
7
|
-
* @param filter - Filter definition containing field path, label, and attributes
|
|
8
|
-
* @param value - Currently selected filter value
|
|
9
|
-
* @param options - Array of picklist values to display as options
|
|
10
|
-
* @param onChange - Callback when selection changes
|
|
11
|
-
*
|
|
12
|
-
* @remarks
|
|
13
|
-
* - Filters out invalid options (null/undefined values)
|
|
14
|
-
* - Displays option label if available, otherwise uses value
|
|
15
|
-
* - Shows placeholder from filter attributes or default "Select..."
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```tsx
|
|
19
|
-
* <FilterSelect
|
|
20
|
-
* filter={selectFilter}
|
|
21
|
-
* value={selectedValue}
|
|
22
|
-
* options={picklistOptions}
|
|
23
|
-
* onChange={(value) => setSelectedValue(value)}
|
|
24
|
-
* />
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
|
-
import {
|
|
28
|
-
Select,
|
|
29
|
-
SelectContent,
|
|
30
|
-
SelectItem,
|
|
31
|
-
SelectTrigger,
|
|
32
|
-
SelectValue,
|
|
33
|
-
} from "../../../components/ui/select";
|
|
34
|
-
import { Field, FieldLabel, FieldDescription } from "../../../components/ui/field";
|
|
35
|
-
import type { Filter } from "../types/filters/filters";
|
|
36
|
-
import type { PicklistValue } from "../types/filters/picklist";
|
|
37
|
-
|
|
38
|
-
interface FilterSelectProps {
|
|
39
|
-
filter: Filter;
|
|
40
|
-
value: string;
|
|
41
|
-
options: PicklistValue[];
|
|
42
|
-
onChange: (value: string) => void;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export default function FilterSelect({ filter, value, options, onChange }: FilterSelectProps) {
|
|
46
|
-
return (
|
|
47
|
-
<Field>
|
|
48
|
-
<FieldLabel htmlFor={filter.targetFieldPath}>
|
|
49
|
-
{filter.label || filter.targetFieldPath}
|
|
50
|
-
</FieldLabel>
|
|
51
|
-
<Select value={value} onValueChange={onChange}>
|
|
52
|
-
<SelectTrigger
|
|
53
|
-
id={filter.targetFieldPath}
|
|
54
|
-
aria-label={filter.label || filter.targetFieldPath}
|
|
55
|
-
>
|
|
56
|
-
<SelectValue placeholder={filter.attributes?.placeholder || "Select..."} />
|
|
57
|
-
</SelectTrigger>
|
|
58
|
-
<SelectContent>
|
|
59
|
-
{options.map((option) => {
|
|
60
|
-
if (!option || !option.value) return null;
|
|
61
|
-
return (
|
|
62
|
-
<SelectItem key={option.value} value={option.value}>
|
|
63
|
-
{option.label || option.value}
|
|
64
|
-
</SelectItem>
|
|
65
|
-
);
|
|
66
|
-
})}
|
|
67
|
-
</SelectContent>
|
|
68
|
-
</Select>
|
|
69
|
-
{filter.helpMessage && <FieldDescription>{filter.helpMessage}</FieldDescription>}
|
|
70
|
-
</Field>
|
|
71
|
-
);
|
|
72
|
-
}
|