@salesforce/webapp-template-app-react-sample-b2e-experimental 1.73.0 → 1.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CHANGELOG.md +16 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/Maintenance_Request__c.object-meta.xml +11 -1
- package/dist/force-app/main/default/objects/Maintenance_Worker__c/Maintenance_Worker__c.object-meta.xml +6 -1
- package/dist/force-app/main/default/objects/Property__c/Property__c.object-meta.xml +6 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/package.json +7 -5
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/maintenanceWorkers.ts +60 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ApplicationsTable.tsx +59 -62
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/FiltersFromApi.tsx +200 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ListPageFilters.tsx +97 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/MaintenanceTable.tsx +2 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ObjectSelect.tsx +39 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/VerticalNav.tsx +6 -4
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/dashboard/GlobalSearchBar.tsx +125 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/feedback/FilterErrorAlert.tsx +15 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/feedback/PageErrorState.tsx +19 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/feedback/PageLoadingState.tsx +18 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/filters/FilterFieldRange.tsx +40 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/filters/FilterFieldSelect.tsx +190 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/filters/FilterFieldText.tsx +32 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/filters/ListPageFilterRow.tsx +100 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/layout/PageContainer.tsx +9 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/layout/PageHeader.tsx +21 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/list/ListPageWithFilters.tsx +70 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/constants.ts +39 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/index.ts +19 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/objectDetailService.ts +125 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/objectInfoGraphQLService.ts +194 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/objectInfoService.ts +199 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/api/recordListGraphQLService.ts +364 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/DetailFields.tsx +55 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/DetailForm.tsx +146 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/DetailHeader.tsx +34 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/DetailLayoutSections.tsx +80 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/Section.tsx +108 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/SectionRow.tsx +20 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/UiApiDetailForm.tsx +140 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FieldValueDisplay.tsx +73 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedAddress.tsx +29 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedEmail.tsx +17 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedPhone.tsx +24 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedText.tsx +11 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/FormattedUrl.tsx +29 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/detail/formatted/index.ts +6 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/filters/FilterField.tsx +54 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/filters/FilterInput.tsx +55 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/filters/FilterSelect.tsx +72 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/filters/FiltersPanel.tsx +380 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/forms/filters-form.tsx +114 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/forms/submit-button.tsx +47 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/GlobalSearchInput.tsx +114 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/ResultCardFields.tsx +71 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/SearchHeader.tsx +31 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/SearchPagination.tsx +144 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/SearchResultCard.tsx +136 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/search/SearchResultsPanel.tsx +197 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/components/shared/LoadingFallback.tsx +61 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/filters/FilterInput.tsx +55 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/filters/FilterSelect.tsx +72 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/form.tsx +209 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/index.ts +22 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useObjectInfoBatch.ts +65 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useObjectSearchData.ts +395 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useRecordDetailLayout.ts +156 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/hooks/useRecordListGraphQL.ts +135 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/pages/DetailPage.tsx +109 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/pages/GlobalSearch.tsx +229 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/filters/filters.ts +121 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/filters/picklist.ts +32 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/index.ts +5 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/objectInfo/objectInfo.ts +166 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/recordDetail/recordDetail.ts +61 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/types/search/searchResults.ts +229 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/apiUtils.ts +125 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/cacheUtils.ts +76 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/debounce.ts +89 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/fieldUtils.ts +354 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/fieldValueExtractor.ts +67 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/filterUtils.ts +32 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/formDataTransformUtils.ts +260 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/formUtils.ts +142 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/graphQLNodeFieldUtils.ts +186 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/graphQLObjectInfoAdapter.ts +319 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/graphQLRecordAdapter.ts +90 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/index.ts +59 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/layoutTransformUtils.ts +236 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/linkUtils.ts +14 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/paginationUtils.ts +49 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/recordUtils.ts +159 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/features/global-search/utils/sanitizationUtils.ts +49 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/hooks/useAccumulatedListPages.ts +29 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/hooks/useListPage.ts +167 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/index.ts +8 -4
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/applicationAdapter.ts +33 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/applicationColumns.ts +28 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/constants.ts +24 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/fieldMappers.ts +71 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/filterUtils.ts +165 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/globalSearchConstants.ts +40 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/listFilters.ts +152 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/listPageConfig.ts +65 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceAdapter.ts +110 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceColumns.ts +24 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceWorkerAdapter.ts +29 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceWorkerColumns.ts +25 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/objectApiNames.ts +13 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/propertyAdapter.ts +68 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/propertyColumns.ts +17 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/routeConfig.ts +35 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/types.ts +10 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Applications.tsx +47 -62
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Home.tsx +130 -98
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Maintenance.tsx +74 -91
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/MaintenanceWorkers.tsx +138 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Properties.tsx +166 -85
- package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/routes.tsx +41 -2
- package/dist/package.json +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Object Search Data Hooks
|
|
3
|
+
*
|
|
4
|
+
* - useObjectListMetadata: single source for list-view metadata (filters → columns + picklists). Use in list pages to avoid duplicate state and API calls.
|
|
5
|
+
* - useObjectColumns / useObjectFilters: thin wrappers over useObjectListMetadata for backward compatibility.
|
|
6
|
+
* - getSharedFilters: module-level deduplication for getObjectListFilters across hook instances.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useState, useEffect, useRef, useMemo } from "react";
|
|
10
|
+
import { objectInfoService, type SearchParams } from "../api/objectInfoService";
|
|
11
|
+
import type {
|
|
12
|
+
Column,
|
|
13
|
+
SearchResultRecord,
|
|
14
|
+
SearchResultRecordData,
|
|
15
|
+
} from "../types/search/searchResults";
|
|
16
|
+
import type { Filter, FilterCriteria } from "../types/filters/filters";
|
|
17
|
+
import type { PicklistValue } from "../types/filters/picklist";
|
|
18
|
+
import { createFiltersKey } from "../utils/cacheUtils";
|
|
19
|
+
|
|
20
|
+
// --- Shared filters cache (deduplicates getObjectListFilters across useObjectColumns + useObjectFilters) ---
|
|
21
|
+
const sharedFiltersCache = new Map<string, Filter[]>();
|
|
22
|
+
const sharedFiltersInFlight = new Map<string, Promise<Filter[]>>();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns filters for the object, deduplicating the API call across hook instances.
|
|
26
|
+
* Does not pass abort signal to the API so the shared request is not aborted when
|
|
27
|
+
* one consumer's effect cleans up (e.g. React Strict Mode); callers still guard with isCancelled.
|
|
28
|
+
*/
|
|
29
|
+
function getSharedFilters(objectApiName: string): Promise<Filter[]> {
|
|
30
|
+
const cached = sharedFiltersCache.get(objectApiName);
|
|
31
|
+
if (cached) return Promise.resolve(cached);
|
|
32
|
+
const inFlight = sharedFiltersInFlight.get(objectApiName);
|
|
33
|
+
if (inFlight) return inFlight;
|
|
34
|
+
const promise = objectInfoService
|
|
35
|
+
.getObjectListFilters(objectApiName)
|
|
36
|
+
.then((filters) => {
|
|
37
|
+
sharedFiltersCache.set(objectApiName, filters);
|
|
38
|
+
sharedFiltersInFlight.delete(objectApiName);
|
|
39
|
+
return filters;
|
|
40
|
+
})
|
|
41
|
+
.catch((err) => {
|
|
42
|
+
sharedFiltersInFlight.delete(objectApiName);
|
|
43
|
+
throw err;
|
|
44
|
+
});
|
|
45
|
+
sharedFiltersInFlight.set(objectApiName, promise);
|
|
46
|
+
return promise;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// --- Shared Types ---
|
|
50
|
+
export interface FiltersData {
|
|
51
|
+
filters: Filter[];
|
|
52
|
+
picklistValues: Record<string, PicklistValue[]>;
|
|
53
|
+
loading: boolean;
|
|
54
|
+
error: string | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Derives column definitions from filter definitions for list/result UI.
|
|
59
|
+
*/
|
|
60
|
+
function filtersToColumns(filters: Filter[]): Column[] {
|
|
61
|
+
return filters.map((f) => ({
|
|
62
|
+
fieldApiName: f.targetFieldPath,
|
|
63
|
+
label: f.label,
|
|
64
|
+
searchable: true,
|
|
65
|
+
sortable: true,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ObjectListMetadata {
|
|
70
|
+
columns: Column[];
|
|
71
|
+
filters: Filter[];
|
|
72
|
+
picklistValues: Record<string, PicklistValue[]>;
|
|
73
|
+
loading: boolean;
|
|
74
|
+
error: string | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Single hook for list-view metadata: filters (shared API), derived columns, and picklist values.
|
|
79
|
+
* Use this in list/search pages to avoid duplicate useObjectColumns + useObjectFilters and duplicate state.
|
|
80
|
+
*/
|
|
81
|
+
export function useObjectListMetadata(objectApiName: string | null): ObjectListMetadata {
|
|
82
|
+
const [state, setState] = useState<{
|
|
83
|
+
columns: Column[];
|
|
84
|
+
filters: Filter[];
|
|
85
|
+
picklistValues: Record<string, PicklistValue[]>;
|
|
86
|
+
loading: boolean;
|
|
87
|
+
error: string | null;
|
|
88
|
+
}>({
|
|
89
|
+
columns: [],
|
|
90
|
+
filters: [],
|
|
91
|
+
picklistValues: {},
|
|
92
|
+
loading: true,
|
|
93
|
+
error: null,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (!objectApiName) {
|
|
98
|
+
setState((s) => ({ ...s, loading: false, error: "Invalid object" }));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let isCancelled = false;
|
|
103
|
+
const ac = new AbortController();
|
|
104
|
+
|
|
105
|
+
const run = async () => {
|
|
106
|
+
setState((s) => ({ ...s, loading: true, error: null }));
|
|
107
|
+
try {
|
|
108
|
+
const filters = await getSharedFilters(objectApiName!);
|
|
109
|
+
if (isCancelled) return;
|
|
110
|
+
|
|
111
|
+
const selectFilters = filters.filter((f) => f.affordance?.toLowerCase() === "select");
|
|
112
|
+
const picklistPromises = selectFilters.map((f) =>
|
|
113
|
+
objectInfoService
|
|
114
|
+
.getPicklistValues(objectApiName!, f.targetFieldPath, undefined, ac.signal)
|
|
115
|
+
.then((values) => ({ fieldPath: f.targetFieldPath, values }))
|
|
116
|
+
.catch((err) => {
|
|
117
|
+
if (err?.name === "AbortError") throw err;
|
|
118
|
+
return { fieldPath: f.targetFieldPath, values: [] as PicklistValue[] };
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
const picklistResults = await Promise.all(picklistPromises);
|
|
122
|
+
if (isCancelled) return;
|
|
123
|
+
|
|
124
|
+
const picklistValues: Record<string, PicklistValue[]> = {};
|
|
125
|
+
picklistResults.forEach(({ fieldPath, values }) => {
|
|
126
|
+
picklistValues[fieldPath] = values;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
setState({
|
|
130
|
+
columns: filtersToColumns(filters),
|
|
131
|
+
filters,
|
|
132
|
+
picklistValues,
|
|
133
|
+
loading: false,
|
|
134
|
+
error: null,
|
|
135
|
+
});
|
|
136
|
+
} catch (err) {
|
|
137
|
+
if (isCancelled || (err instanceof Error && err.name === "AbortError")) return;
|
|
138
|
+
setState((s) => ({
|
|
139
|
+
...s,
|
|
140
|
+
columns: [],
|
|
141
|
+
filters: [],
|
|
142
|
+
picklistValues: {},
|
|
143
|
+
loading: false,
|
|
144
|
+
error: "Failed to load list metadata",
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
run();
|
|
150
|
+
return () => {
|
|
151
|
+
isCancelled = true;
|
|
152
|
+
ac.abort();
|
|
153
|
+
};
|
|
154
|
+
}, [objectApiName]);
|
|
155
|
+
|
|
156
|
+
return state;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Hook: useObjectColumns
|
|
161
|
+
* Thin wrapper over useObjectListMetadata for backward compatibility.
|
|
162
|
+
*/
|
|
163
|
+
export function useObjectColumns(objectApiName: string | null) {
|
|
164
|
+
const { columns, loading, error } = useObjectListMetadata(objectApiName);
|
|
165
|
+
return {
|
|
166
|
+
columns: objectApiName ? columns : [],
|
|
167
|
+
columnsLoading: loading,
|
|
168
|
+
columnsError: error,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Hook: useObjectSearchResults
|
|
174
|
+
*
|
|
175
|
+
* Fetches search results for a specific object based on the provided query parameters.
|
|
176
|
+
* Maintains the *latest* result set for the object in state to prevent redundant
|
|
177
|
+
* network requests when the component re-renders with the same parameters.
|
|
178
|
+
* Includes debouncing for search queries (but not pagination).
|
|
179
|
+
*
|
|
180
|
+
* @param objectApiName - The API name of the object to search
|
|
181
|
+
* @param searchQuery - The search query string
|
|
182
|
+
* @param searchPageSize - Number of results per page (default: 50)
|
|
183
|
+
* @param searchPageToken - Pagination token (default: '0')
|
|
184
|
+
* @param filters - Array of filter criteria to apply (default: [])
|
|
185
|
+
* @param sortBy - Sort field and direction (default: 'relevance')
|
|
186
|
+
* @returns Object containing results array, pagination tokens, loading state, and error state
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```tsx
|
|
190
|
+
* const { results, nextPageToken, previousPageToken, currentPageToken, resultsLoading, resultsError } = useObjectSearchResults(
|
|
191
|
+
* 'Account',
|
|
192
|
+
* 'test query',
|
|
193
|
+
* 25,
|
|
194
|
+
* '0',
|
|
195
|
+
* [{ objectApiName: 'Account', fieldPath: 'Name', operator: 'contains', values: ['test'] }]
|
|
196
|
+
* );
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
export function useObjectSearchResults(
|
|
200
|
+
objectApiName: string | null,
|
|
201
|
+
searchQuery: string,
|
|
202
|
+
searchPageSize: number = 50,
|
|
203
|
+
searchPageToken: string = "0",
|
|
204
|
+
filters: FilterCriteria[] = [],
|
|
205
|
+
sortBy: string = "relevance",
|
|
206
|
+
) {
|
|
207
|
+
const [resultsCache, setResultsCache] = useState<
|
|
208
|
+
Record<
|
|
209
|
+
string,
|
|
210
|
+
{
|
|
211
|
+
results: SearchResultRecord[];
|
|
212
|
+
query: string;
|
|
213
|
+
pageToken: string;
|
|
214
|
+
pageSize: number;
|
|
215
|
+
filtersKey: string;
|
|
216
|
+
sortBy: string;
|
|
217
|
+
nextPageToken: string | null;
|
|
218
|
+
previousPageToken: string | null;
|
|
219
|
+
currentPageToken: string;
|
|
220
|
+
}
|
|
221
|
+
>
|
|
222
|
+
>({});
|
|
223
|
+
const [loading, setLoading] = useState<Record<string, boolean>>({});
|
|
224
|
+
const [error, setError] = useState<Record<string, string | null>>({});
|
|
225
|
+
|
|
226
|
+
const debounceTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
227
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
228
|
+
const resultsCacheRef = useRef(resultsCache);
|
|
229
|
+
|
|
230
|
+
const filtersKey = useMemo(() => {
|
|
231
|
+
const filtersArray = Array.isArray(filters) ? filters : [];
|
|
232
|
+
return createFiltersKey(filtersArray);
|
|
233
|
+
}, [filters]);
|
|
234
|
+
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
resultsCacheRef.current = resultsCache;
|
|
237
|
+
}, [resultsCache]);
|
|
238
|
+
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
if (!objectApiName || !searchQuery.trim()) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let isCancelled = false;
|
|
245
|
+
const abortController = new AbortController();
|
|
246
|
+
|
|
247
|
+
if (abortControllerRef.current) {
|
|
248
|
+
abortControllerRef.current.abort();
|
|
249
|
+
}
|
|
250
|
+
abortControllerRef.current = abortController;
|
|
251
|
+
|
|
252
|
+
if (debounceTimeout.current) {
|
|
253
|
+
clearTimeout(debounceTimeout.current);
|
|
254
|
+
debounceTimeout.current = null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const cached = resultsCacheRef.current[objectApiName];
|
|
258
|
+
if (
|
|
259
|
+
!abortController.signal.aborted &&
|
|
260
|
+
cached &&
|
|
261
|
+
cached.query === searchQuery &&
|
|
262
|
+
cached.pageToken === searchPageToken &&
|
|
263
|
+
cached.pageSize === searchPageSize &&
|
|
264
|
+
cached.filtersKey === filtersKey &&
|
|
265
|
+
cached.sortBy === sortBy
|
|
266
|
+
) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (abortController.signal.aborted) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const fetchResults = async () => {
|
|
275
|
+
setLoading((prev) => ({ ...prev, [objectApiName]: true }));
|
|
276
|
+
setError((prev) => ({ ...prev, [objectApiName]: null }));
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const searchParams: SearchParams = {
|
|
280
|
+
sortBy: sortBy === "relevance" ? "" : sortBy,
|
|
281
|
+
filters: filters,
|
|
282
|
+
pageSize: searchPageSize,
|
|
283
|
+
pageToken: searchPageToken,
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const keywordSearchResult = await objectInfoService.searchResults(
|
|
287
|
+
searchQuery,
|
|
288
|
+
objectApiName,
|
|
289
|
+
searchParams,
|
|
290
|
+
abortController.signal,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (isCancelled || abortController.signal.aborted) return;
|
|
294
|
+
|
|
295
|
+
const normalizedRecords = keywordSearchResult.records.map((r) => ({
|
|
296
|
+
record: r.record as SearchResultRecordData,
|
|
297
|
+
highlightInfo: r.highlightInfo,
|
|
298
|
+
searchInfo: r.searchInfo,
|
|
299
|
+
}));
|
|
300
|
+
|
|
301
|
+
const nextPageToken: string | null = keywordSearchResult.nextPageToken ?? null;
|
|
302
|
+
const previousPageToken: string | null = keywordSearchResult.previousPageToken ?? null;
|
|
303
|
+
|
|
304
|
+
setResultsCache((prev): typeof prev => ({
|
|
305
|
+
...prev,
|
|
306
|
+
[objectApiName]: {
|
|
307
|
+
results: normalizedRecords,
|
|
308
|
+
query: searchQuery,
|
|
309
|
+
pageToken: searchPageToken,
|
|
310
|
+
pageSize: searchPageSize,
|
|
311
|
+
filtersKey: filtersKey,
|
|
312
|
+
sortBy,
|
|
313
|
+
nextPageToken,
|
|
314
|
+
previousPageToken,
|
|
315
|
+
currentPageToken: keywordSearchResult.currentPageToken,
|
|
316
|
+
},
|
|
317
|
+
}));
|
|
318
|
+
} catch (err) {
|
|
319
|
+
if (isCancelled || (err instanceof Error && err.name === "AbortError")) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
setError((prev) => ({ ...prev, [objectApiName]: "Unable to load search results" }));
|
|
323
|
+
// Cache empty result so we skip refetch on remount (avoid infinite loop on API error)
|
|
324
|
+
setResultsCache((prev) => ({
|
|
325
|
+
...prev,
|
|
326
|
+
[objectApiName]: {
|
|
327
|
+
results: [],
|
|
328
|
+
query: searchQuery,
|
|
329
|
+
pageToken: searchPageToken,
|
|
330
|
+
pageSize: searchPageSize,
|
|
331
|
+
filtersKey: filtersKey,
|
|
332
|
+
sortBy,
|
|
333
|
+
nextPageToken: null,
|
|
334
|
+
previousPageToken: null,
|
|
335
|
+
currentPageToken: searchPageToken,
|
|
336
|
+
},
|
|
337
|
+
}));
|
|
338
|
+
} finally {
|
|
339
|
+
if (!isCancelled) {
|
|
340
|
+
setLoading((prev) => ({ ...prev, [objectApiName]: false }));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
if (searchPageToken === "0") {
|
|
346
|
+
debounceTimeout.current = setTimeout(() => {
|
|
347
|
+
fetchResults();
|
|
348
|
+
}, 300);
|
|
349
|
+
} else {
|
|
350
|
+
fetchResults();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return () => {
|
|
354
|
+
isCancelled = true;
|
|
355
|
+
abortController.abort();
|
|
356
|
+
if (debounceTimeout.current) {
|
|
357
|
+
clearTimeout(debounceTimeout.current);
|
|
358
|
+
debounceTimeout.current = null;
|
|
359
|
+
}
|
|
360
|
+
if (abortControllerRef.current === abortController) {
|
|
361
|
+
abortControllerRef.current = null;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}, [objectApiName, searchQuery, searchPageSize, searchPageToken, filtersKey, sortBy]);
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
results: objectApiName ? resultsCache[objectApiName]?.results || [] : [],
|
|
368
|
+
nextPageToken: objectApiName ? resultsCache[objectApiName]?.nextPageToken || null : null,
|
|
369
|
+
previousPageToken: objectApiName
|
|
370
|
+
? resultsCache[objectApiName]?.previousPageToken || null
|
|
371
|
+
: null,
|
|
372
|
+
currentPageToken: objectApiName ? resultsCache[objectApiName]?.currentPageToken || "0" : "0",
|
|
373
|
+
resultsLoading: objectApiName ? loading[objectApiName] || false : false,
|
|
374
|
+
resultsError: objectApiName ? error[objectApiName] || null : null,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Hook: useObjectFilters
|
|
380
|
+
* Thin wrapper over useObjectListMetadata for backward compatibility.
|
|
381
|
+
*/
|
|
382
|
+
export function useObjectFilters(objectApiName: string | null) {
|
|
383
|
+
const { filters, picklistValues, loading, error } = useObjectListMetadata(objectApiName);
|
|
384
|
+
const filtersData: Record<string, FiltersData> = objectApiName
|
|
385
|
+
? {
|
|
386
|
+
[objectApiName]: {
|
|
387
|
+
filters,
|
|
388
|
+
picklistValues,
|
|
389
|
+
loading,
|
|
390
|
+
error,
|
|
391
|
+
},
|
|
392
|
+
}
|
|
393
|
+
: {};
|
|
394
|
+
return { filtersData };
|
|
395
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from "react";
|
|
2
|
+
import { objectDetailService } from "../api/objectDetailService";
|
|
3
|
+
import type { LayoutResponse } from "../types/recordDetail/recordDetail";
|
|
4
|
+
import type { ObjectInfoResult } from "../types/objectInfo/objectInfo";
|
|
5
|
+
import type { GraphQLRecordNode } from "../api/recordListGraphQLService";
|
|
6
|
+
|
|
7
|
+
export interface UseRecordDetailLayoutReturn {
|
|
8
|
+
layout: LayoutResponse | null;
|
|
9
|
+
record: GraphQLRecordNode | null;
|
|
10
|
+
objectMetadata: ObjectInfoResult | null;
|
|
11
|
+
loading: boolean;
|
|
12
|
+
error: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UseRecordDetailLayoutParams {
|
|
16
|
+
objectApiName: string | null;
|
|
17
|
+
recordId: string | null;
|
|
18
|
+
recordTypeId?: string | null;
|
|
19
|
+
/** When provided, skips the fetch and uses this data (avoids duplicate API calls when parent already fetched). Callers should memoize this (e.g. useMemo) to avoid unnecessary effect runs. */
|
|
20
|
+
initialData?: {
|
|
21
|
+
layout: LayoutResponse;
|
|
22
|
+
record: GraphQLRecordNode;
|
|
23
|
+
objectMetadata: ObjectInfoResult;
|
|
24
|
+
} | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const MAX_CACHE_SIZE = 50;
|
|
28
|
+
/** Cache entries older than this are treated as stale and refetched. */
|
|
29
|
+
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
30
|
+
|
|
31
|
+
type CacheEntry = {
|
|
32
|
+
layout: LayoutResponse;
|
|
33
|
+
record: GraphQLRecordNode;
|
|
34
|
+
objectMetadata: ObjectInfoResult;
|
|
35
|
+
cachedAt: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Detail page data: layout (REST), object metadata (GraphQL), single record (GraphQL).
|
|
40
|
+
*
|
|
41
|
+
* Calls objectDetailService.getRecordDetail once per objectApiName/recordId/recordTypeId.
|
|
42
|
+
* Caches result in memory (TTL 5min, max 50 entries). Used by DetailPage and UiApiDetailForm.
|
|
43
|
+
*
|
|
44
|
+
* @param objectApiName - Object API name.
|
|
45
|
+
* @param recordId - Record Id.
|
|
46
|
+
* @param recordTypeId - Optional record type (default master).
|
|
47
|
+
* @returns { layout, record, objectMetadata, loading, error }.
|
|
48
|
+
*/
|
|
49
|
+
export function useRecordDetailLayout({
|
|
50
|
+
objectApiName,
|
|
51
|
+
recordId,
|
|
52
|
+
recordTypeId = null,
|
|
53
|
+
initialData = null,
|
|
54
|
+
}: UseRecordDetailLayoutParams): UseRecordDetailLayoutReturn {
|
|
55
|
+
const [layout, setLayout] = useState<LayoutResponse | null>(initialData?.layout ?? null);
|
|
56
|
+
const [record, setRecord] = useState<GraphQLRecordNode | null>(initialData?.record ?? null);
|
|
57
|
+
const [objectMetadata, setObjectMetadata] = useState<ObjectInfoResult | null>(
|
|
58
|
+
initialData?.objectMetadata ?? null,
|
|
59
|
+
);
|
|
60
|
+
const [loading, setLoading] = useState(!initialData);
|
|
61
|
+
const [error, setError] = useState<string | null>(null);
|
|
62
|
+
|
|
63
|
+
const cacheKey =
|
|
64
|
+
objectApiName && recordId ? `${objectApiName}:${recordId}:${recordTypeId ?? "default"}` : null;
|
|
65
|
+
const cacheRef = useRef<Map<string, CacheEntry>>(new Map());
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!objectApiName || !recordId) {
|
|
69
|
+
setError("Invalid object or record ID");
|
|
70
|
+
setLoading(false);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Skip fetch when parent already provided data (avoids duplicate API calls)
|
|
75
|
+
if (
|
|
76
|
+
initialData?.layout != null &&
|
|
77
|
+
initialData?.record != null &&
|
|
78
|
+
initialData?.objectMetadata != null
|
|
79
|
+
) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const cached = cacheRef.current.get(cacheKey!);
|
|
84
|
+
const now = Date.now();
|
|
85
|
+
if (cached && now - cached.cachedAt < CACHE_TTL_MS) {
|
|
86
|
+
setLayout(cached.layout);
|
|
87
|
+
setRecord(cached.record);
|
|
88
|
+
setObjectMetadata(cached.objectMetadata);
|
|
89
|
+
setLoading(false);
|
|
90
|
+
setError(null);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let isCancelled = false;
|
|
95
|
+
const abortController = new AbortController();
|
|
96
|
+
|
|
97
|
+
const fetchDetail = async () => {
|
|
98
|
+
setLoading(true);
|
|
99
|
+
setError(null);
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const {
|
|
103
|
+
layout: layoutData,
|
|
104
|
+
record: recordData,
|
|
105
|
+
objectMetadata: objectMetadataData,
|
|
106
|
+
} = await objectDetailService.getRecordDetail(
|
|
107
|
+
objectApiName,
|
|
108
|
+
recordId,
|
|
109
|
+
recordTypeId ?? undefined,
|
|
110
|
+
abortController.signal,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (isCancelled) return;
|
|
114
|
+
|
|
115
|
+
const cache = cacheRef.current;
|
|
116
|
+
if (cache.size >= MAX_CACHE_SIZE) {
|
|
117
|
+
const firstKey = cache.keys().next().value;
|
|
118
|
+
if (firstKey != null) cache.delete(firstKey);
|
|
119
|
+
}
|
|
120
|
+
cache.set(cacheKey!, {
|
|
121
|
+
layout: layoutData,
|
|
122
|
+
record: recordData,
|
|
123
|
+
objectMetadata: objectMetadataData,
|
|
124
|
+
cachedAt: Date.now(),
|
|
125
|
+
});
|
|
126
|
+
setLayout(layoutData);
|
|
127
|
+
setRecord(recordData);
|
|
128
|
+
setObjectMetadata(objectMetadataData);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (isCancelled || (err instanceof Error && err.name === "AbortError")) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
setError("Failed to load record details");
|
|
134
|
+
} finally {
|
|
135
|
+
if (!isCancelled) {
|
|
136
|
+
setLoading(false);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
fetchDetail();
|
|
142
|
+
|
|
143
|
+
return () => {
|
|
144
|
+
isCancelled = true;
|
|
145
|
+
abortController.abort();
|
|
146
|
+
};
|
|
147
|
+
}, [objectApiName, recordId, recordTypeId, cacheKey, initialData]);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
layout,
|
|
151
|
+
record,
|
|
152
|
+
objectMetadata,
|
|
153
|
+
loading,
|
|
154
|
+
error,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record list hook: GraphQL records with filter, sort, pagination, search.
|
|
3
|
+
* Use for list/search views; detail view uses useRecordDetailLayout instead.
|
|
4
|
+
*
|
|
5
|
+
* @module hooks/useRecordListGraphQL
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useEffect, useCallback } from "react";
|
|
9
|
+
import { useObjectColumns } from "./useObjectSearchData";
|
|
10
|
+
import {
|
|
11
|
+
getRecordsGraphQL,
|
|
12
|
+
buildOrderByFromSort,
|
|
13
|
+
type RecordListGraphQLResult,
|
|
14
|
+
} from "../api/recordListGraphQLService";
|
|
15
|
+
import type { Column } from "../types/search/searchResults";
|
|
16
|
+
import type { FilterCriteria } from "../types/filters/filters";
|
|
17
|
+
|
|
18
|
+
const EMPTY_FILTERS: FilterCriteria[] = [];
|
|
19
|
+
|
|
20
|
+
export interface UseRecordListGraphQLOptions {
|
|
21
|
+
objectApiName: string;
|
|
22
|
+
first?: number;
|
|
23
|
+
after?: string | null;
|
|
24
|
+
filters?: FilterCriteria[];
|
|
25
|
+
sortBy?: string;
|
|
26
|
+
searchQuery?: string;
|
|
27
|
+
/** When provided, skips useObjectColumns (use from parent e.g. useObjectListMetadata). */
|
|
28
|
+
columns?: Column[];
|
|
29
|
+
columnsLoading?: boolean;
|
|
30
|
+
columnsError?: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface UseRecordListGraphQLReturn {
|
|
34
|
+
data: RecordListGraphQLResult | null;
|
|
35
|
+
edges: Array<{ node?: Record<string, unknown> }>;
|
|
36
|
+
pageInfo: {
|
|
37
|
+
hasNextPage?: boolean;
|
|
38
|
+
hasPreviousPage?: boolean;
|
|
39
|
+
endCursor?: string | null;
|
|
40
|
+
startCursor?: string | null;
|
|
41
|
+
} | null;
|
|
42
|
+
loading: boolean;
|
|
43
|
+
error: string | null;
|
|
44
|
+
columnsLoading: boolean;
|
|
45
|
+
columnsError: string | null;
|
|
46
|
+
refetch: () => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Fetches records via GraphQL for the given object with filter, sort, pagination, and search.
|
|
51
|
+
*/
|
|
52
|
+
export function useRecordListGraphQL(
|
|
53
|
+
options: UseRecordListGraphQLOptions,
|
|
54
|
+
): UseRecordListGraphQLReturn {
|
|
55
|
+
const {
|
|
56
|
+
objectApiName,
|
|
57
|
+
first = 50,
|
|
58
|
+
after = null,
|
|
59
|
+
filters = EMPTY_FILTERS,
|
|
60
|
+
sortBy = "",
|
|
61
|
+
searchQuery = "",
|
|
62
|
+
columns: columnsProp,
|
|
63
|
+
columnsLoading: columnsLoadingProp,
|
|
64
|
+
columnsError: columnsErrorProp,
|
|
65
|
+
} = options;
|
|
66
|
+
|
|
67
|
+
const fromParent = columnsProp !== undefined;
|
|
68
|
+
const fromHook = useObjectColumns(fromParent ? null : objectApiName);
|
|
69
|
+
|
|
70
|
+
const columns = fromParent ? columnsProp : fromHook.columns;
|
|
71
|
+
const columnsLoading = fromParent ? (columnsLoadingProp ?? false) : fromHook.columnsLoading;
|
|
72
|
+
const columnsError = fromParent ? (columnsErrorProp ?? null) : fromHook.columnsError;
|
|
73
|
+
|
|
74
|
+
const [data, setData] = useState<RecordListGraphQLResult | null>(null);
|
|
75
|
+
const [loading, setLoading] = useState(false);
|
|
76
|
+
const [error, setError] = useState<string | null>(null);
|
|
77
|
+
|
|
78
|
+
const fetchRecords = useCallback(() => {
|
|
79
|
+
if (columnsLoading || columnsError || columns.length === 0) return;
|
|
80
|
+
|
|
81
|
+
setLoading(true);
|
|
82
|
+
setError(null);
|
|
83
|
+
const orderBy = buildOrderByFromSort(sortBy);
|
|
84
|
+
|
|
85
|
+
getRecordsGraphQL({
|
|
86
|
+
objectApiName,
|
|
87
|
+
columns,
|
|
88
|
+
first,
|
|
89
|
+
after,
|
|
90
|
+
filters,
|
|
91
|
+
orderBy,
|
|
92
|
+
searchQuery: searchQuery.trim() || undefined,
|
|
93
|
+
})
|
|
94
|
+
.then((result) => {
|
|
95
|
+
setData(result);
|
|
96
|
+
})
|
|
97
|
+
.catch((err) => {
|
|
98
|
+
setError(err instanceof Error ? err.message : "Failed to load records");
|
|
99
|
+
})
|
|
100
|
+
.finally(() => {
|
|
101
|
+
setLoading(false);
|
|
102
|
+
});
|
|
103
|
+
}, [
|
|
104
|
+
objectApiName,
|
|
105
|
+
columns,
|
|
106
|
+
columnsLoading,
|
|
107
|
+
columnsError,
|
|
108
|
+
first,
|
|
109
|
+
after,
|
|
110
|
+
filters,
|
|
111
|
+
sortBy,
|
|
112
|
+
searchQuery,
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (!objectApiName || columnsLoading || columnsError) return;
|
|
117
|
+
if (columns.length === 0 && !columnsLoading) return;
|
|
118
|
+
fetchRecords();
|
|
119
|
+
}, [objectApiName, columns, columnsLoading, columnsError, fetchRecords]);
|
|
120
|
+
|
|
121
|
+
const objectData = data?.uiapi?.query?.[objectApiName];
|
|
122
|
+
const edges = objectData?.edges ?? [];
|
|
123
|
+
const pageInfo = objectData?.pageInfo ?? null;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
data,
|
|
127
|
+
edges,
|
|
128
|
+
pageInfo,
|
|
129
|
+
loading: columnsLoading || loading,
|
|
130
|
+
error: columnsError || error,
|
|
131
|
+
columnsLoading,
|
|
132
|
+
columnsError,
|
|
133
|
+
refetch: fetchRecords,
|
|
134
|
+
};
|
|
135
|
+
}
|