@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,108 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback, useEffect, useRef } from "react";
|
|
2
|
-
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
3
|
-
import type { ReactNode } from "react";
|
|
4
|
-
|
|
5
|
-
export interface SectionProps {
|
|
6
|
-
sectionId: string;
|
|
7
|
-
titleLabel: string;
|
|
8
|
-
showHeader: boolean;
|
|
9
|
-
collapsible: boolean;
|
|
10
|
-
/** When provided, section is controlled (parent owns state). When undefined, section is uncontrolled (internal state). */
|
|
11
|
-
collapsed?: boolean;
|
|
12
|
-
onToggle?: (sectionId: string, collapsed: boolean) => void;
|
|
13
|
-
children: ReactNode;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Section block with optional heading and collapsible content. Controlled when
|
|
18
|
-
* `collapsed` is passed; uncontrolled otherwise. Accessible: aria-expanded, aria-controls, keyboard (Enter/Space).
|
|
19
|
-
*/
|
|
20
|
-
export function Section({
|
|
21
|
-
sectionId,
|
|
22
|
-
titleLabel,
|
|
23
|
-
showHeader,
|
|
24
|
-
collapsible,
|
|
25
|
-
collapsed: controlledCollapsed,
|
|
26
|
-
onToggle,
|
|
27
|
-
children,
|
|
28
|
-
}: SectionProps) {
|
|
29
|
-
const [internalCollapsed, setInternalCollapsed] = useState(false);
|
|
30
|
-
const isControlled = controlledCollapsed !== undefined;
|
|
31
|
-
const collapsed = isControlled ? controlledCollapsed : internalCollapsed;
|
|
32
|
-
|
|
33
|
-
const warnedUncontrolledRef = useRef(false);
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
if (
|
|
36
|
-
process.env.NODE_ENV === "development" &&
|
|
37
|
-
onToggle != null &&
|
|
38
|
-
!isControlled &&
|
|
39
|
-
!warnedUncontrolledRef.current
|
|
40
|
-
) {
|
|
41
|
-
warnedUncontrolledRef.current = true;
|
|
42
|
-
console.warn(
|
|
43
|
-
"[Section] onToggle is passed but collapsed is undefined; section is uncontrolled. Pass collapsed to control from parent.",
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
}, [onToggle, isControlled]);
|
|
47
|
-
|
|
48
|
-
const contentId = `section-content-${sectionId}`;
|
|
49
|
-
const headerId = `section-header-${sectionId}`;
|
|
50
|
-
|
|
51
|
-
const handleToggle = useCallback(() => {
|
|
52
|
-
const next = !collapsed;
|
|
53
|
-
if (!isControlled) setInternalCollapsed(next);
|
|
54
|
-
onToggle?.(sectionId, next);
|
|
55
|
-
}, [collapsed, isControlled, onToggle, sectionId]);
|
|
56
|
-
|
|
57
|
-
const handleKeyDown = useCallback(
|
|
58
|
-
(e: React.KeyboardEvent) => {
|
|
59
|
-
if (!collapsible) return;
|
|
60
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
61
|
-
e.preventDefault();
|
|
62
|
-
handleToggle();
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
[collapsible, handleToggle],
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<section
|
|
70
|
-
className="border-b border-border last:border-b-0 pb-6 last:pb-0"
|
|
71
|
-
aria-labelledby={showHeader ? headerId : undefined}
|
|
72
|
-
>
|
|
73
|
-
{showHeader && titleLabel && (
|
|
74
|
-
<h3 id={headerId} className="text-base font-semibold text-foreground mb-4">
|
|
75
|
-
{collapsible ? (
|
|
76
|
-
<button
|
|
77
|
-
type="button"
|
|
78
|
-
className="flex items-center gap-2 w-full text-left hover:opacity-80 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 rounded"
|
|
79
|
-
onClick={handleToggle}
|
|
80
|
-
onKeyDown={handleKeyDown}
|
|
81
|
-
aria-expanded={!collapsed}
|
|
82
|
-
aria-controls={contentId}
|
|
83
|
-
aria-label={`${titleLabel}, ${collapsed ? "expand" : "collapse"} section`}
|
|
84
|
-
aria-roledescription="Section toggle"
|
|
85
|
-
>
|
|
86
|
-
{collapsed ? (
|
|
87
|
-
<ChevronRight className="h-4 w-4 shrink-0" aria-hidden />
|
|
88
|
-
) : (
|
|
89
|
-
<ChevronDown className="h-4 w-4 shrink-0" aria-hidden />
|
|
90
|
-
)}
|
|
91
|
-
<span>{titleLabel}</span>
|
|
92
|
-
</button>
|
|
93
|
-
) : (
|
|
94
|
-
<span className="block">{titleLabel}</span>
|
|
95
|
-
)}
|
|
96
|
-
</h3>
|
|
97
|
-
)}
|
|
98
|
-
<div
|
|
99
|
-
id={contentId}
|
|
100
|
-
className={showHeader && collapsible ? "mt-2" : ""}
|
|
101
|
-
aria-hidden={collapsible ? collapsed : undefined}
|
|
102
|
-
hidden={collapsible && collapsed}
|
|
103
|
-
>
|
|
104
|
-
{children}
|
|
105
|
-
</div>
|
|
106
|
-
</section>
|
|
107
|
-
);
|
|
108
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
export interface SectionRowProps {
|
|
4
|
-
children: ReactNode;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* One row of the detail form: definition list (dl) with two-column grid. Each child
|
|
9
|
-
* is a layout item (field cell or placeholder) from the layout API row.
|
|
10
|
-
*/
|
|
11
|
-
export function SectionRow({ children }: SectionRowProps) {
|
|
12
|
-
return (
|
|
13
|
-
<dl
|
|
14
|
-
className="grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-4 sm:gap-y-2"
|
|
15
|
-
aria-label="Row of fields"
|
|
16
|
-
>
|
|
17
|
-
{children}
|
|
18
|
-
</dl>
|
|
19
|
-
);
|
|
20
|
-
}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo } from "react";
|
|
2
|
-
import type { LayoutResponse } from "../../types/recordDetail/recordDetail";
|
|
3
|
-
import { useRecordDetailLayout } from "../../hooks/useRecordDetailLayout";
|
|
4
|
-
import { toRecordDisplayNameMetadata } from "../../utils/fieldUtils";
|
|
5
|
-
import { DetailForm } from "./DetailForm";
|
|
6
|
-
import type { ObjectInfoResult } from "../../types/objectInfo/objectInfo";
|
|
7
|
-
import type { ObjectInfoMetadata } from "../../utils/formDataTransformUtils";
|
|
8
|
-
import type { ObjectInfo } from "../../utils/layoutTransformUtils";
|
|
9
|
-
import type { GraphQLRecordNode } from "../../api/recordListGraphQLService";
|
|
10
|
-
import { getGraphQLRecordDisplayName } from "../../utils/graphQLNodeFieldUtils";
|
|
11
|
-
|
|
12
|
-
export interface UiApiDetailFormProps {
|
|
13
|
-
objectApiName: string;
|
|
14
|
-
recordId: string;
|
|
15
|
-
recordTypeId?: string | null;
|
|
16
|
-
/** When provided, skips fetching and uses this layout (controlled mode). */
|
|
17
|
-
layout?: LayoutResponse | null;
|
|
18
|
-
/** When provided with layout, skips fetching and uses this record (controlled mode). */
|
|
19
|
-
record?: GraphQLRecordNode | null;
|
|
20
|
-
/** When provided, skips fetching and uses this object metadata (controlled mode). */
|
|
21
|
-
objectMetadata?: ObjectInfoResult | null;
|
|
22
|
-
/** When true, shows a loading spinner until layout and record are ready. */
|
|
23
|
-
loadsWithSpinner?: boolean;
|
|
24
|
-
/** Reserved for future edit mode; no-op in read-only. */
|
|
25
|
-
hideFooter?: boolean;
|
|
26
|
-
/** Callback when layout and record are ready (e.g. for parent to show record title). */
|
|
27
|
-
onRecordDataUpdate?: (payload: { recordName: string; record: unknown }) => void;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Entry component for the record detail view. When layout/record are not provided,
|
|
32
|
-
* fetches them via useRecordDetailLayout. Shows optional loading spinner and renders
|
|
33
|
-
* a read-only DetailForm when ready. Mirrors LWC uiApiDetailForm (read-only).
|
|
34
|
-
*
|
|
35
|
-
* Passes objectInfo (mapped from object metadata) to DetailForm for layout transform.
|
|
36
|
-
* lookupRecords (picklist/lookup options) are not fetched in this flow; DetailForm
|
|
37
|
-
* accepts them when provided (e.g. from a future picklist API). Omit for read-only
|
|
38
|
-
* display without API-driven picklist labels.
|
|
39
|
-
*/
|
|
40
|
-
export function UiApiDetailForm({
|
|
41
|
-
objectApiName,
|
|
42
|
-
recordId,
|
|
43
|
-
recordTypeId = null,
|
|
44
|
-
layout: layoutProp,
|
|
45
|
-
record: recordProp,
|
|
46
|
-
objectMetadata: objectMetadataProp,
|
|
47
|
-
loadsWithSpinner = false,
|
|
48
|
-
onRecordDataUpdate,
|
|
49
|
-
}: UiApiDetailFormProps) {
|
|
50
|
-
// Memoize so hook dependency doesn't change every render (avoids duplicate fetches)
|
|
51
|
-
const initialData = useMemo(
|
|
52
|
-
() =>
|
|
53
|
-
layoutProp && recordProp && objectMetadataProp
|
|
54
|
-
? {
|
|
55
|
-
layout: layoutProp,
|
|
56
|
-
record: recordProp,
|
|
57
|
-
objectMetadata: objectMetadataProp,
|
|
58
|
-
}
|
|
59
|
-
: null,
|
|
60
|
-
[layoutProp, recordProp, objectMetadataProp],
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
const fetched = useRecordDetailLayout({
|
|
64
|
-
objectApiName,
|
|
65
|
-
recordId,
|
|
66
|
-
recordTypeId,
|
|
67
|
-
initialData,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const layout = layoutProp ?? fetched.layout;
|
|
71
|
-
const record = recordProp ?? fetched.record;
|
|
72
|
-
const metadata = objectMetadataProp ?? fetched.objectMetadata;
|
|
73
|
-
const loading = layoutProp == null || recordProp == null ? fetched.loading : false;
|
|
74
|
-
const error = layoutProp == null || recordProp == null ? fetched.error : null;
|
|
75
|
-
|
|
76
|
-
const objectInfo: ObjectInfo | null = useMemo(() => {
|
|
77
|
-
if (!metadata?.fields) return null;
|
|
78
|
-
const apiName = metadata.ApiName;
|
|
79
|
-
return {
|
|
80
|
-
apiName,
|
|
81
|
-
fields: Object.fromEntries(
|
|
82
|
-
Object.entries(metadata.fields).map(([name, f]) => [
|
|
83
|
-
name,
|
|
84
|
-
{
|
|
85
|
-
compoundFieldName: f.compoundFieldName ?? undefined,
|
|
86
|
-
dataType: f.dataType ?? "",
|
|
87
|
-
},
|
|
88
|
-
]),
|
|
89
|
-
),
|
|
90
|
-
};
|
|
91
|
-
}, [metadata]);
|
|
92
|
-
|
|
93
|
-
const isReadyToRender = Boolean(layout && record && layout.sections?.length);
|
|
94
|
-
|
|
95
|
-
const showSpinner = !isReadyToRender && loadsWithSpinner && loading;
|
|
96
|
-
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
if (!record || !onRecordDataUpdate || !isReadyToRender) return;
|
|
99
|
-
onRecordDataUpdate({
|
|
100
|
-
recordName: getGraphQLRecordDisplayName(record, toRecordDisplayNameMetadata(metadata)),
|
|
101
|
-
record,
|
|
102
|
-
});
|
|
103
|
-
}, [record, metadata, onRecordDataUpdate, isReadyToRender]);
|
|
104
|
-
|
|
105
|
-
if (showSpinner) {
|
|
106
|
-
return (
|
|
107
|
-
<div
|
|
108
|
-
className="min-h-[80px] flex items-center justify-center"
|
|
109
|
-
role="status"
|
|
110
|
-
aria-live="polite"
|
|
111
|
-
aria-label="Loading record details"
|
|
112
|
-
>
|
|
113
|
-
<span className="sr-only">Loading record details</span>
|
|
114
|
-
<div
|
|
115
|
-
className="h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent"
|
|
116
|
-
aria-hidden
|
|
117
|
-
/>
|
|
118
|
-
</div>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (error || !layout || !record) {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (!isReadyToRender) {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<DetailForm
|
|
132
|
-
layout={layout}
|
|
133
|
-
record={record}
|
|
134
|
-
metadata={metadata as ObjectInfoMetadata}
|
|
135
|
-
objectInfo={objectInfo}
|
|
136
|
-
showSectionHeaders
|
|
137
|
-
collapsibleSections
|
|
138
|
-
/>
|
|
139
|
-
);
|
|
140
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Picks formatter by dataType (Address, Phone, Url, Email) or plain text. Empty values show "—" with a11y label.
|
|
3
|
-
*/
|
|
4
|
-
import { FormattedAddress } from "./FormattedAddress";
|
|
5
|
-
import { FormattedEmail } from "./FormattedEmail";
|
|
6
|
-
import { FormattedPhone } from "./FormattedPhone";
|
|
7
|
-
import { FormattedText } from "./FormattedText";
|
|
8
|
-
import { FormattedUrl } from "./FormattedUrl";
|
|
9
|
-
|
|
10
|
-
/** Salesforce UI API dataType values that have dedicated formatters. */
|
|
11
|
-
const DATA_TYPES = {
|
|
12
|
-
Address: "Address",
|
|
13
|
-
Email: "Email",
|
|
14
|
-
Phone: "Phone",
|
|
15
|
-
Url: "Url",
|
|
16
|
-
} as const;
|
|
17
|
-
|
|
18
|
-
/** Normalize dataType to canonical casing so "PHONE" / "phone" match Phone, etc. */
|
|
19
|
-
function normalizeDataType(dataType: string | undefined): string | undefined {
|
|
20
|
-
if (dataType == null || dataType === "") return dataType;
|
|
21
|
-
const lower = dataType.toLowerCase();
|
|
22
|
-
const canonical: Record<string, string> = {
|
|
23
|
-
phone: DATA_TYPES.Phone,
|
|
24
|
-
email: DATA_TYPES.Email,
|
|
25
|
-
url: DATA_TYPES.Url,
|
|
26
|
-
address: DATA_TYPES.Address,
|
|
27
|
-
};
|
|
28
|
-
return canonical[lower] ?? dataType;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface FieldValueDisplayProps {
|
|
32
|
-
/** Resolved display value (string, number, boolean, or null). */
|
|
33
|
-
value: string | number | boolean | null | undefined;
|
|
34
|
-
/** Field dataType from object info (e.g. Phone, Email, Url, Address). */
|
|
35
|
-
dataType?: string;
|
|
36
|
-
className?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const DEFAULT_CLASS = "text-sm text-foreground";
|
|
40
|
-
const LINK_CLASS =
|
|
41
|
-
"text-sm text-foreground text-primary underline underline-offset-2 hover:opacity-80";
|
|
42
|
-
|
|
43
|
-
export function FieldValueDisplay({
|
|
44
|
-
value,
|
|
45
|
-
dataType,
|
|
46
|
-
className = DEFAULT_CLASS,
|
|
47
|
-
}: FieldValueDisplayProps) {
|
|
48
|
-
const str = value || null;
|
|
49
|
-
|
|
50
|
-
if (str === null) {
|
|
51
|
-
return (
|
|
52
|
-
<span className={className} aria-label="No value">
|
|
53
|
-
—
|
|
54
|
-
</span>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const linkClassName = className === DEFAULT_CLASS ? LINK_CLASS : className;
|
|
59
|
-
const normalizedType = normalizeDataType(dataType);
|
|
60
|
-
|
|
61
|
-
switch (normalizedType) {
|
|
62
|
-
case DATA_TYPES.Address:
|
|
63
|
-
return <FormattedAddress value={str as string} className={linkClassName} />;
|
|
64
|
-
case DATA_TYPES.Phone:
|
|
65
|
-
return <FormattedPhone value={str as string} className={linkClassName} />;
|
|
66
|
-
case DATA_TYPES.Url:
|
|
67
|
-
return <FormattedUrl value={str as string} className={linkClassName} />;
|
|
68
|
-
case DATA_TYPES.Email:
|
|
69
|
-
return <FormattedEmail value={str as string} className={linkClassName} />;
|
|
70
|
-
default:
|
|
71
|
-
return <FormattedText value={value} className={className} />;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/** Address as link to Google Maps search. External link: target _blank, rel noopener noreferrer. */
|
|
2
|
-
|
|
3
|
-
const GOOGLE_MAPS_SEARCH_BASE = "https://www.google.com/maps/search/?api=1&query=";
|
|
4
|
-
|
|
5
|
-
export interface FormattedAddressProps {
|
|
6
|
-
/** Full address string (e.g. "10 Main Rd.\nNew York, NY 31349\nUSA"). */
|
|
7
|
-
value: string;
|
|
8
|
-
className?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function FormattedAddress({ value, className }: FormattedAddressProps) {
|
|
12
|
-
if (!value || !value.trim()) return null;
|
|
13
|
-
const url = GOOGLE_MAPS_SEARCH_BASE + encodeURIComponent(value.trim());
|
|
14
|
-
return (
|
|
15
|
-
<a
|
|
16
|
-
href={url}
|
|
17
|
-
target="_blank"
|
|
18
|
-
rel="noopener noreferrer"
|
|
19
|
-
className={className}
|
|
20
|
-
aria-label="Open address in Google Maps"
|
|
21
|
-
>
|
|
22
|
-
{value.split("\n").map((line, i) => (
|
|
23
|
-
<span key={i} className="block">
|
|
24
|
-
{line}
|
|
25
|
-
</span>
|
|
26
|
-
))}
|
|
27
|
-
</a>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/** Email as mailto: link. */
|
|
2
|
-
|
|
3
|
-
export interface FormattedEmailProps {
|
|
4
|
-
value: string;
|
|
5
|
-
className?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function FormattedEmail({ value, className }: FormattedEmailProps) {
|
|
9
|
-
const str = (value || "").trim();
|
|
10
|
-
if (!str) return null;
|
|
11
|
-
const href = `mailto:${encodeURIComponent(str)}`;
|
|
12
|
-
return (
|
|
13
|
-
<a href={href} className={className} aria-label={`Email ${str}`}>
|
|
14
|
-
{str}
|
|
15
|
-
</a>
|
|
16
|
-
);
|
|
17
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/** Phone value as tel: link for dialer/VoIP. */
|
|
2
|
-
|
|
3
|
-
export interface FormattedPhoneProps {
|
|
4
|
-
value: string;
|
|
5
|
-
className?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/** Normalizes value for tel: (digits and + only). */
|
|
9
|
-
function telHref(value: string): string {
|
|
10
|
-
const cleaned = value.replace(/[^\d+]/g, "");
|
|
11
|
-
return cleaned ? `tel:${cleaned}` : "#";
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function FormattedPhone({ value, className }: FormattedPhoneProps) {
|
|
15
|
-
if (!value) return null;
|
|
16
|
-
const trimmed = (value || "").trim();
|
|
17
|
-
const href = telHref(trimmed);
|
|
18
|
-
if (href === "#") return <span className={className}>{trimmed}</span>;
|
|
19
|
-
return (
|
|
20
|
-
<a href={href} className={className} aria-label={`Call ${trimmed}`}>
|
|
21
|
-
{trimmed}
|
|
22
|
-
</a>
|
|
23
|
-
);
|
|
24
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/** Plain-text field value when dataType has no dedicated formatter. */
|
|
2
|
-
|
|
3
|
-
export interface FormattedTextProps {
|
|
4
|
-
value: string | number | boolean | null | undefined;
|
|
5
|
-
className?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function FormattedText({ value, className }: FormattedTextProps) {
|
|
9
|
-
if (!value) return null;
|
|
10
|
-
return <span className={className}>{value as string}</span>;
|
|
11
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/** URL as external link (new tab, noopener noreferrer). Falls back to plain text if not http(s). */
|
|
2
|
-
|
|
3
|
-
import { isAllowedLinkUrl } from "../../../utils/linkUtils";
|
|
4
|
-
|
|
5
|
-
export interface FormattedUrlProps {
|
|
6
|
-
value: string;
|
|
7
|
-
className?: string;
|
|
8
|
-
/** Optional display text; defaults to the URL. */
|
|
9
|
-
displayText?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function FormattedUrl({ value, className, displayText }: FormattedUrlProps) {
|
|
13
|
-
const str = (value || "").trim();
|
|
14
|
-
if (!str) return null;
|
|
15
|
-
const href = str.startsWith("http://") || str.startsWith("https://") ? str : `https://${str}`;
|
|
16
|
-
if (!isAllowedLinkUrl(href)) return <span className={className}>{str}</span>;
|
|
17
|
-
const label = displayText ?? str;
|
|
18
|
-
return (
|
|
19
|
-
<a
|
|
20
|
-
href={href}
|
|
21
|
-
target="_blank"
|
|
22
|
-
rel="noopener noreferrer"
|
|
23
|
-
className={className}
|
|
24
|
-
aria-label={`Open link in new tab: ${label}`}
|
|
25
|
-
>
|
|
26
|
-
{label}
|
|
27
|
-
</a>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FilterField Component
|
|
3
|
-
*
|
|
4
|
-
* Wrapper component that renders the appropriate filter input type based on filter affordance.
|
|
5
|
-
* Routes to FilterInput for text fields or FilterSelect for picklist fields.
|
|
6
|
-
*
|
|
7
|
-
* @param filter - Filter definition containing field path, label, and affordance
|
|
8
|
-
* @param value - Current filter value
|
|
9
|
-
* @param picklistValues - Array of picklist options (for select fields)
|
|
10
|
-
* @param onChange - Callback when filter value changes
|
|
11
|
-
*
|
|
12
|
-
* @remarks
|
|
13
|
-
* - Automatically determines input type from filter.affordance
|
|
14
|
-
* - Returns null if filter is invalid
|
|
15
|
-
* - Defaults to text input if affordance is not 'select'
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```tsx
|
|
19
|
-
* <FilterField
|
|
20
|
-
* filter={filter}
|
|
21
|
-
* value={filterValue}
|
|
22
|
-
* picklistValues={picklistOptions}
|
|
23
|
-
* onChange={(value) => setFilterValue(value)}
|
|
24
|
-
* />
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
|
-
import FilterInput from "./FilterInput";
|
|
28
|
-
import FilterSelect from "./FilterSelect";
|
|
29
|
-
import type { Filter } from "../../types/filters/filters";
|
|
30
|
-
import type { PicklistValue } from "../../types/filters/picklist";
|
|
31
|
-
|
|
32
|
-
interface FilterFieldProps {
|
|
33
|
-
filter: Filter;
|
|
34
|
-
value: string;
|
|
35
|
-
picklistValues: PicklistValue[];
|
|
36
|
-
onChange: (value: string) => void;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export default function FilterField({ filter, value, picklistValues, onChange }: FilterFieldProps) {
|
|
40
|
-
// Guard against invalid filter objects
|
|
41
|
-
if (!filter || !filter.targetFieldPath) {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const affordance = filter.affordance?.toLowerCase() || "";
|
|
46
|
-
|
|
47
|
-
if (affordance === "select") {
|
|
48
|
-
const options = picklistValues || [];
|
|
49
|
-
return <FilterSelect filter={filter} value={value} options={options} onChange={onChange} />;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Default to text input
|
|
53
|
-
return <FilterInput filter={filter} value={value} onChange={onChange} />;
|
|
54
|
-
}
|
|
@@ -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
|
-
}
|