@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.
Files changed (117) hide show
  1. package/dist/CHANGELOG.md +16 -0
  2. package/dist/force-app/main/default/objects/Maintenance_Request__c/Maintenance_Request__c.object-meta.xml +11 -1
  3. package/dist/force-app/main/default/objects/Maintenance_Worker__c/Maintenance_Worker__c.object-meta.xml +6 -1
  4. package/dist/force-app/main/default/objects/Property__c/Property__c.object-meta.xml +6 -1
  5. package/dist/force-app/main/default/webapplications/appreactsampleb2e/package.json +7 -5
  6. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/maintenanceWorkers.ts +60 -0
  7. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ApplicationsTable.tsx +59 -62
  8. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/FiltersFromApi.tsx +200 -0
  9. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ListPageFilters.tsx +97 -0
  10. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/MaintenanceTable.tsx +2 -1
  11. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ObjectSelect.tsx +39 -0
  12. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/VerticalNav.tsx +6 -4
  13. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/dashboard/GlobalSearchBar.tsx +125 -0
  14. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/feedback/FilterErrorAlert.tsx +15 -0
  15. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/feedback/PageErrorState.tsx +19 -0
  16. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/feedback/PageLoadingState.tsx +18 -0
  17. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/filters/FilterFieldRange.tsx +40 -0
  18. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/filters/FilterFieldSelect.tsx +190 -0
  19. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/filters/FilterFieldText.tsx +32 -0
  20. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/filters/ListPageFilterRow.tsx +100 -0
  21. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/layout/PageContainer.tsx +9 -0
  22. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/layout/PageHeader.tsx +21 -0
  23. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/list/ListPageWithFilters.tsx +70 -0
  24. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/constants.ts +39 -0
  25. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/index.ts +19 -0
  26. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/objectDetailService.ts +125 -0
  27. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/objectInfoGraphQLService.ts +194 -0
  28. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/objectInfoService.ts +199 -0
  29. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/recordListGraphQLService.ts +364 -0
  30. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/DetailFields.tsx +55 -0
  31. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/DetailForm.tsx +146 -0
  32. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/DetailHeader.tsx +34 -0
  33. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/DetailLayoutSections.tsx +80 -0
  34. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/Section.tsx +108 -0
  35. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/SectionRow.tsx +20 -0
  36. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/UiApiDetailForm.tsx +140 -0
  37. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FieldValueDisplay.tsx +73 -0
  38. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedAddress.tsx +29 -0
  39. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedEmail.tsx +17 -0
  40. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedPhone.tsx +24 -0
  41. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedText.tsx +11 -0
  42. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedUrl.tsx +29 -0
  43. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/index.ts +6 -0
  44. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/filters/FilterField.tsx +54 -0
  45. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/filters/FilterInput.tsx +55 -0
  46. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/filters/FilterSelect.tsx +72 -0
  47. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/filters/FiltersPanel.tsx +380 -0
  48. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/forms/filters-form.tsx +114 -0
  49. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/forms/submit-button.tsx +47 -0
  50. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/GlobalSearchInput.tsx +114 -0
  51. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/ResultCardFields.tsx +71 -0
  52. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/SearchHeader.tsx +31 -0
  53. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/SearchPagination.tsx +144 -0
  54. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/SearchResultCard.tsx +136 -0
  55. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/SearchResultsPanel.tsx +197 -0
  56. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/shared/LoadingFallback.tsx +61 -0
  57. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/filters/FilterInput.tsx +55 -0
  58. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/filters/FilterSelect.tsx +72 -0
  59. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/form.tsx +209 -0
  60. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/index.ts +22 -0
  61. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useObjectInfoBatch.ts +65 -0
  62. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useObjectSearchData.ts +395 -0
  63. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useRecordDetailLayout.ts +156 -0
  64. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useRecordListGraphQL.ts +135 -0
  65. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/pages/DetailPage.tsx +109 -0
  66. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/pages/GlobalSearch.tsx +229 -0
  67. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/filters/filters.ts +121 -0
  68. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/filters/picklist.ts +32 -0
  69. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/index.ts +5 -0
  70. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/objectInfo/objectInfo.ts +166 -0
  71. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/recordDetail/recordDetail.ts +61 -0
  72. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/search/searchResults.ts +229 -0
  73. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/apiUtils.ts +125 -0
  74. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/cacheUtils.ts +76 -0
  75. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/debounce.ts +89 -0
  76. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/fieldUtils.ts +354 -0
  77. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/fieldValueExtractor.ts +67 -0
  78. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/filterUtils.ts +32 -0
  79. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/formDataTransformUtils.ts +260 -0
  80. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/formUtils.ts +142 -0
  81. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/graphQLNodeFieldUtils.ts +186 -0
  82. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/graphQLObjectInfoAdapter.ts +319 -0
  83. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/graphQLRecordAdapter.ts +90 -0
  84. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/index.ts +59 -0
  85. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/layoutTransformUtils.ts +236 -0
  86. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/linkUtils.ts +14 -0
  87. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/paginationUtils.ts +49 -0
  88. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/recordUtils.ts +159 -0
  89. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/sanitizationUtils.ts +49 -0
  90. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/hooks/useAccumulatedListPages.ts +29 -0
  91. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/hooks/useListPage.ts +167 -0
  92. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/index.ts +8 -4
  93. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/applicationAdapter.ts +33 -0
  94. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/applicationColumns.ts +28 -0
  95. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/constants.ts +24 -0
  96. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/fieldMappers.ts +71 -0
  97. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/filterUtils.ts +165 -0
  98. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/globalSearchConstants.ts +40 -0
  99. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/listFilters.ts +152 -0
  100. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/listPageConfig.ts +65 -0
  101. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceAdapter.ts +110 -0
  102. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceColumns.ts +24 -0
  103. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceWorkerAdapter.ts +29 -0
  104. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceWorkerColumns.ts +25 -0
  105. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/objectApiNames.ts +13 -0
  106. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/propertyAdapter.ts +68 -0
  107. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/propertyColumns.ts +17 -0
  108. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/routeConfig.ts +35 -0
  109. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/types.ts +10 -0
  110. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Applications.tsx +47 -62
  111. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Home.tsx +130 -98
  112. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Maintenance.tsx +74 -91
  113. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/MaintenanceWorkers.tsx +138 -0
  114. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Properties.tsx +166 -85
  115. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/routes.tsx +41 -2
  116. package/dist/package.json +1 -1
  117. package/package.json +5 -1
@@ -0,0 +1,72 @@
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
+ }
@@ -0,0 +1,209 @@
1
+ import { useId } from "react";
2
+ import { createFormHookContexts, createFormHook } from "@tanstack/react-form";
3
+ import {
4
+ Field,
5
+ FieldDescription,
6
+ FieldError,
7
+ FieldLabel,
8
+ } from "../../../components/ui/field";
9
+ import { Input } from "../../../components/ui/input";
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from "../../../components/ui/select";
17
+ import { cn } from "../../../lib/utils";
18
+ import type { PicklistValue } from "../types/filters/picklist";
19
+ import { getUniqueErrors } from "../utils/formUtils";
20
+
21
+ export type { FormError } from "../utils/formUtils";
22
+ export { validateRangeValues } from "../utils/formUtils";
23
+
24
+ export const { fieldContext, formContext, useFieldContext, useFormContext } =
25
+ createFormHookContexts();
26
+
27
+ interface FilterTextFieldProps extends Omit<
28
+ React.ComponentProps<typeof Input>,
29
+ "name" | "value" | "onBlur" | "onChange" | "aria-invalid"
30
+ > {
31
+ label: string;
32
+ description?: React.ReactNode;
33
+ placeholder?: string;
34
+ }
35
+
36
+ function FilterTextField({
37
+ label,
38
+ id: providedId,
39
+ description,
40
+ placeholder,
41
+ type = "text",
42
+ ...props
43
+ }: FilterTextFieldProps) {
44
+ const field = useFieldContext<string>();
45
+ const generatedId = useId();
46
+ const id = providedId ?? generatedId;
47
+ const descriptionId = `${id}-description`;
48
+ const errorId = `${id}-error`;
49
+ const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
50
+
51
+ const uniqueErrors = getUniqueErrors(field.state.meta.errors);
52
+
53
+ return (
54
+ <Field data-invalid={isInvalid}>
55
+ <FieldLabel htmlFor={id}>{label}</FieldLabel>
56
+ {description && <FieldDescription id={descriptionId}>{description}</FieldDescription>}
57
+ <Input
58
+ id={id}
59
+ name={field.name as string}
60
+ type={type}
61
+ value={field.state.value ?? ""}
62
+ onBlur={field.handleBlur}
63
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => field.handleChange(e.target.value)}
64
+ placeholder={placeholder}
65
+ aria-invalid={isInvalid}
66
+ aria-describedby={cn(description && descriptionId, isInvalid && errorId)}
67
+ {...props}
68
+ />
69
+ {isInvalid && uniqueErrors.length > 0 && <FieldError errors={uniqueErrors} />}
70
+ </Field>
71
+ );
72
+ }
73
+
74
+ interface FilterSelectFieldProps {
75
+ label: string;
76
+ id?: string;
77
+ description?: React.ReactNode;
78
+ placeholder?: string;
79
+ options: PicklistValue[];
80
+ }
81
+
82
+ function FilterSelectField({
83
+ label,
84
+ id: providedId,
85
+ description,
86
+ placeholder = "Select...",
87
+ options,
88
+ }: FilterSelectFieldProps) {
89
+ const field = useFieldContext<string>();
90
+ const generatedId = useId();
91
+ const id = providedId ?? generatedId;
92
+ const descriptionId = `${id}-description`;
93
+ const errorId = `${id}-error`;
94
+ const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
95
+
96
+ const uniqueErrors = getUniqueErrors(field.state.meta.errors);
97
+
98
+ return (
99
+ <Field data-invalid={isInvalid}>
100
+ <FieldLabel htmlFor={id}>{label}</FieldLabel>
101
+ {description && <FieldDescription id={descriptionId}>{description}</FieldDescription>}
102
+ <Select value={field.state.value ?? ""} onValueChange={(value) => field.handleChange(value)}>
103
+ <SelectTrigger
104
+ id={id}
105
+ aria-invalid={isInvalid}
106
+ aria-describedby={cn(description && descriptionId, isInvalid && errorId)}
107
+ >
108
+ <SelectValue placeholder={placeholder} />
109
+ </SelectTrigger>
110
+ <SelectContent>
111
+ {options.map((option) => {
112
+ if (!option || !option.value) return null;
113
+ return (
114
+ <SelectItem key={option.value} value={option.value}>
115
+ {option.label || option.value}
116
+ </SelectItem>
117
+ );
118
+ })}
119
+ </SelectContent>
120
+ </Select>
121
+ {isInvalid && uniqueErrors.length > 0 && <FieldError errors={uniqueErrors} />}
122
+ </Field>
123
+ );
124
+ }
125
+
126
+ interface FilterRangeFieldProps extends Omit<
127
+ React.ComponentProps<typeof Input>,
128
+ "name" | "value" | "onBlur" | "onChange" | "aria-invalid"
129
+ > {
130
+ label?: string;
131
+ description?: React.ReactNode;
132
+ placeholder?: string;
133
+ }
134
+
135
+ function FilterRangeFieldBase({
136
+ label,
137
+ id: providedId,
138
+ description,
139
+ placeholder,
140
+ type = "text",
141
+ ...props
142
+ }: FilterRangeFieldProps) {
143
+ const field = useFieldContext<string>();
144
+ const generatedId = useId();
145
+ const id = providedId ?? generatedId;
146
+ const descriptionId = `${id}-description`;
147
+ const errorId = `${id}-error`;
148
+ const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
149
+
150
+ const uniqueErrors = getUniqueErrors(field.state.meta.errors);
151
+
152
+ return (
153
+ <div>
154
+ {label && <FieldLabel htmlFor={id}>{label}</FieldLabel>}
155
+ {description && <FieldDescription id={descriptionId}>{description}</FieldDescription>}
156
+ <Input
157
+ id={id}
158
+ name={field.name as string}
159
+ type={type}
160
+ value={field.state.value ?? ""}
161
+ onBlur={field.handleBlur}
162
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => field.handleChange(e.target.value)}
163
+ placeholder={placeholder}
164
+ aria-invalid={isInvalid}
165
+ aria-describedby={cn(description && descriptionId, isInvalid && errorId)}
166
+ {...props}
167
+ />
168
+ {isInvalid && uniqueErrors.length > 0 && <FieldError errors={uniqueErrors} />}
169
+ </div>
170
+ );
171
+ }
172
+
173
+ interface FilterRangeMinFieldProps extends Omit<
174
+ React.ComponentProps<typeof Input>,
175
+ "name" | "value" | "onBlur" | "onChange" | "aria-invalid"
176
+ > {
177
+ label?: string;
178
+ description?: React.ReactNode;
179
+ placeholder?: string;
180
+ }
181
+
182
+ interface FilterRangeMaxFieldProps extends Omit<
183
+ React.ComponentProps<typeof Input>,
184
+ "name" | "value" | "onBlur" | "onChange" | "aria-invalid"
185
+ > {
186
+ label?: string;
187
+ description?: React.ReactNode;
188
+ placeholder?: string;
189
+ }
190
+
191
+ function FilterRangeMinField({ placeholder = "Min", ...props }: FilterRangeMinFieldProps) {
192
+ return <FilterRangeFieldBase placeholder={placeholder} {...props} />;
193
+ }
194
+
195
+ function FilterRangeMaxField({ placeholder = "Max", ...props }: FilterRangeMaxFieldProps) {
196
+ return <FilterRangeFieldBase placeholder={placeholder} {...props} />;
197
+ }
198
+
199
+ export const { useAppForm } = createFormHook({
200
+ fieldContext,
201
+ formContext,
202
+ fieldComponents: {
203
+ FilterTextField,
204
+ FilterSelectField,
205
+ FilterRangeMinField,
206
+ FilterRangeMaxField,
207
+ },
208
+ formComponents: {},
209
+ });
@@ -0,0 +1,22 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Hooks
3
+ // ---------------------------------------------------------------------------
4
+ export {
5
+ useObjectColumns,
6
+ useObjectSearchResults,
7
+ useObjectFilters,
8
+ useObjectListMetadata,
9
+ } from "./useObjectSearchData";
10
+ export type { ObjectListMetadata } from "./useObjectSearchData";
11
+ export { useRecordListGraphQL } from "./useRecordListGraphQL";
12
+ export type {
13
+ UseRecordListGraphQLReturn,
14
+ UseRecordListGraphQLOptions,
15
+ } from "./useRecordListGraphQL";
16
+ export { useRecordDetailLayout } from "./useRecordDetailLayout";
17
+ export type {
18
+ UseRecordDetailLayoutReturn,
19
+ UseRecordDetailLayoutParams,
20
+ } from "./useRecordDetailLayout";
21
+ export { useObjectInfoBatch } from "./useObjectInfoBatch";
22
+ export type { UseObjectInfoBatchResult } from "./useObjectInfoBatch";
@@ -0,0 +1,65 @@
1
+ /**
2
+ * useObjectInfoBatch
3
+ *
4
+ * Fetches object metadata (label, labelPlural, fields, etc.) for the given object API names.
5
+ * Uses the shared cache in objectInfoService so List, Home, and Detail views reuse one request.
6
+ */
7
+
8
+ import { useState, useEffect, useRef } from "react";
9
+ import { objectInfoService } from "../api/objectInfoService";
10
+ import type { ObjectInfoResult } from "../types/objectInfo/objectInfo";
11
+
12
+ export interface UseObjectInfoBatchResult {
13
+ /** Object metadata in the same order as the requested objectApiNames. */
14
+ objectInfos: ObjectInfoResult[];
15
+ loading: boolean;
16
+ error: string | null;
17
+ }
18
+
19
+ /**
20
+ * Fetches batch object info for the given object API names. Results are cached;
21
+ * multiple callers (List, Home, Detail) share the same request.
22
+ *
23
+ * @param objectApiNames - Array of object API names (e.g. OBJECT_API_NAMES)
24
+ * @returns objectInfos (same order as input), loading, error
25
+ */
26
+ export function useObjectInfoBatch(objectApiNames: string[]): UseObjectInfoBatchResult {
27
+ const [state, setState] = useState<UseObjectInfoBatchResult>({
28
+ objectInfos: [],
29
+ loading: objectApiNames.length > 0,
30
+ error: null,
31
+ });
32
+ const isCancelled = useRef(false);
33
+
34
+ useEffect(() => {
35
+ isCancelled.current = false;
36
+ const names = objectApiNames.filter(Boolean);
37
+ if (names.length === 0) {
38
+ setState({ objectInfos: [], loading: false, error: null });
39
+ return;
40
+ }
41
+ setState((s) => ({ ...s, loading: true, error: null }));
42
+ objectInfoService
43
+ .getObjectInfoBatch(names.join(","))
44
+ .then((res) => {
45
+ if (isCancelled.current) return;
46
+ const objectInfos = names
47
+ .map((apiName) => res.results?.find((r) => r.result?.apiName === apiName)?.result)
48
+ .filter((r) => r != null) as ObjectInfoResult[];
49
+ setState({ objectInfos, loading: false, error: null });
50
+ })
51
+ .catch((err) => {
52
+ if (isCancelled.current) return;
53
+ setState({
54
+ objectInfos: [],
55
+ loading: false,
56
+ error: err instanceof Error ? err.message : (err as string),
57
+ });
58
+ });
59
+ return () => {
60
+ isCancelled.current = true;
61
+ };
62
+ }, [objectApiNames.join(",")]);
63
+
64
+ return state;
65
+ }