@orchestrator-ui/orchestrator-ui-components 8.5.0 → 8.6.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/.turbo/turbo-build.log +10 -14
- package/.turbo/turbo-lint.log +2 -4
- package/.turbo/turbo-test.log +7 -9
- package/CHANGELOG.md +6 -0
- package/dist/index.d.ts +84 -74
- package/dist/index.js +2568 -2252
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/WfoAutoExpandableTextArea/WfoAutoExpandableTextArea.tsx +54 -0
- package/src/components/WfoAutoExpandableTextArea/index.ts +1 -0
- package/src/components/WfoAutoExpandableTextArea/styles.ts +18 -0
- package/src/components/WfoPageTemplate/paths.ts +1 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoAddGroupAction.tsx +1 -1
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoCombinatorSelector.tsx +3 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFieldSelector.tsx +58 -4
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoFilterBuilder.tsx +37 -28
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoInlineCombinator.tsx +17 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRemoveRuleAction.tsx +3 -3
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoRuleGroup.tsx +6 -3
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoSearchFieldWithActions.tsx +56 -0
- package/src/components/WfoTable/WfoStructuredSearchTable/WfoStructuredSearchTable.tsx +84 -81
- package/src/components/WfoTable/WfoStructuredSearchTable/styles.ts +52 -16
- package/src/components/WfoTable/WfoStructuredSearchTable/utils.ts +45 -0
- package/src/components/WfoTable/WfoTableSettingsModal/WfoTableSettingsModal.tsx +9 -0
- package/src/components/index.ts +1 -0
- package/src/configuration/version.ts +1 -1
- package/src/messages/en-GB.json +1 -0
- package/src/messages/nl-NL.json +1 -0
- package/src/pages/WfoSearchPocPage.tsx +151 -25
- package/src/types/search.ts +3 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { ReactNode, useEffect, useMemo, useState } from 'react';
|
|
1
|
+
import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import type { RuleGroupType } from 'react-querybuilder';
|
|
3
3
|
import { formatQuery } from 'react-querybuilder/formatQuery';
|
|
4
4
|
import { parseCEL } from 'react-querybuilder/parseCEL';
|
|
@@ -32,16 +32,22 @@ import {
|
|
|
32
32
|
WfoTableColumnConfig,
|
|
33
33
|
subscriptionListTabs,
|
|
34
34
|
} from '@/components';
|
|
35
|
+
import { parseCelToRuleGroup } from '@/components/WfoTable/WfoStructuredSearchTable/utils';
|
|
35
36
|
import { ColumnType, WfoTableProps } from '@/components/WfoTable/WfoTable';
|
|
36
37
|
import { useStoredTableConfig } from '@/hooks';
|
|
37
|
-
import { SearchPayload, useSearchQuery } from '@/rtk';
|
|
38
|
-
import {
|
|
39
|
-
|
|
38
|
+
import { SearchPayload, useLazySearchQuery, useSearchQuery } from '@/rtk';
|
|
39
|
+
import {
|
|
40
|
+
EntityKind,
|
|
41
|
+
FieldToOperatorMap,
|
|
42
|
+
PaginatedSearchResults,
|
|
43
|
+
ResultColumToPropertyMap,
|
|
44
|
+
RetrieverType,
|
|
45
|
+
SortOrder,
|
|
46
|
+
} from '@/types';
|
|
47
|
+
import { getCsvFileNameWithDate, initiateCsvFileDownload, parseDateToLocaleDateTimeString } from '@/utils';
|
|
40
48
|
|
|
41
49
|
const SEARCH_TABLE_LOCAL_STORAGE_KEY = 'SEARCH_TABLE_LOCAL_STORAGE_KEY';
|
|
42
50
|
|
|
43
|
-
type ResultColumToPropertyMap<T> = Map<string, keyof T>;
|
|
44
|
-
|
|
45
51
|
const getKeyByValueFromMap = <T,>(resultColumToPropertyMap: ResultColumToPropertyMap<T>, field: keyof T) => {
|
|
46
52
|
return [...resultColumToPropertyMap.entries()].find(([, v]) => v === field)?.[0] || '';
|
|
47
53
|
};
|
|
@@ -116,6 +122,21 @@ const resultColumToPropertyMap: ResultColumToPropertyMap<SubscriptionListItem> =
|
|
|
116
122
|
['subscription.metadata', 'metadata'],
|
|
117
123
|
]);
|
|
118
124
|
|
|
125
|
+
/* These options will be added as the first options in the field dropdown in the FieldSelector */
|
|
126
|
+
const prefilledFieldOptions: FieldToOperatorMap = new Map([
|
|
127
|
+
['subscription.subscription_id', ['eq', 'neq', 'like']],
|
|
128
|
+
['subscription.description', ['eq', 'neq', 'like']],
|
|
129
|
+
['subscription.status', ['eq', 'neq', 'like']],
|
|
130
|
+
['subscription.insync', ['eq', 'neq']],
|
|
131
|
+
['subscription.product.name', ['eq', 'neq', 'like']],
|
|
132
|
+
['subscription.product.tag', ['eq', 'neq', 'like']],
|
|
133
|
+
['subscription.customer_name', ['eq', 'neq', 'like']],
|
|
134
|
+
['subscription.customer_abbreviation', ['eq', 'neq', 'like']],
|
|
135
|
+
['subscription.start_date', ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'between']],
|
|
136
|
+
['subscription.end_date', ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'between']],
|
|
137
|
+
['subscription.note', ['eq', 'neq', 'like']],
|
|
138
|
+
]);
|
|
139
|
+
|
|
119
140
|
export const WfoSearchPocPage = () => {
|
|
120
141
|
const t = useTranslations('subscriptions.index');
|
|
121
142
|
const [activeTab, setActiveTab] = useQueryParam('activeTab', withDefault(StringParam, WfoSubscriptionListTab.ACTIVE));
|
|
@@ -125,26 +146,60 @@ export const WfoSearchPocPage = () => {
|
|
|
125
146
|
|
|
126
147
|
// Part of the search endpoint payload that is passed in the q parameter
|
|
127
148
|
const [queryText, setQueryText] = useState<string>('');
|
|
128
|
-
|
|
149
|
+
// The committed query and filter live in the URL so a link reproduces the search and browser
|
|
150
|
+
// back/forward re-runs it. The filter is stored as a CEL string and parsed back to a rule group.
|
|
151
|
+
const [committedSearchQuery, setCommittedSearchQuery] = useQueryParam('queryString', withDefault(StringParam, ''));
|
|
152
|
+
const [committedFilterString, setCommittedFilterString] = useQueryParam('filterString', withDefault(StringParam, ''));
|
|
153
|
+
// Track the last value this page committed, so the URL->state sync effects below only rebuild the
|
|
154
|
+
// inputs for external changes (page load, back/forward). Rebuilding on own commits would revert
|
|
155
|
+
// characters typed right after committing (search bar) or visibly restructure builder rules the
|
|
156
|
+
// CEL round trip cannot preserve, such as 'between'. The refs hold the value as the params decode
|
|
157
|
+
// it (absent param -> ''); the setters receive undefined to remove an empty param from the URL.
|
|
158
|
+
const lastSelfCommittedFilter = useRef('');
|
|
159
|
+
const commitFilterString = (celString: string) => {
|
|
160
|
+
lastSelfCommittedFilter.current = celString;
|
|
161
|
+
setCommittedFilterString(celString || undefined);
|
|
162
|
+
};
|
|
163
|
+
const lastSelfCommittedQuery = useRef('');
|
|
164
|
+
const commitSearchQuery = (queryText: string) => {
|
|
165
|
+
lastSelfCommittedQuery.current = queryText;
|
|
166
|
+
setCommittedSearchQuery(queryText || undefined);
|
|
167
|
+
};
|
|
129
168
|
|
|
130
169
|
// String that is displayed in the filter textarea. This is transformed and if valid passed to the search endpoint in the filter parameter
|
|
131
|
-
const [filterString, setFilterString] = useState<string>();
|
|
170
|
+
const [filterString, setFilterString] = useState<string>('');
|
|
132
171
|
const [queryBuilderRuleGroup, setQueryBuilderRuleGroup] = useState<RuleGroupType | undefined>();
|
|
133
|
-
const
|
|
172
|
+
const committedRuleGroup = useMemo<RuleGroupType | undefined>(
|
|
173
|
+
() => parseCelToRuleGroup(committedFilterString),
|
|
174
|
+
[committedFilterString],
|
|
175
|
+
);
|
|
134
176
|
const [isValidFilterString, setIsValidFilterString] = useState<boolean>(true);
|
|
135
177
|
const [tableDefaults, setTableDefaults] = useState<StoredTableConfig<SubscriptionListItem>>();
|
|
136
178
|
const [pageSize, setPageSize] = useState<number>(tableDefaults?.selectedPageSize || DEFAULT_PAGE_SIZE);
|
|
137
179
|
const [limit, setLimit] = useState<number>(pageSize);
|
|
138
|
-
const [
|
|
180
|
+
const [pageCursor, setPageCursor] = useState<{ cursor: string; searchKey: string } | undefined>(undefined);
|
|
139
181
|
const [dataSorting, setDataSorting] = useState<WfoDataSorting<SubscriptionListItem>>({
|
|
140
182
|
field: 'subscriptionId',
|
|
141
183
|
sortOrder: SortOrder.DESC,
|
|
142
184
|
});
|
|
143
185
|
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
186
|
+
// A next-page cursor is only valid for the committed search that produced it. Binding it to a key
|
|
187
|
+
// of that search covers commits that bypass the handlers below, e.g. browser back/forward changing
|
|
188
|
+
// the queryString/filterString/activeTab URL params: the key no longer matches, so the stale
|
|
189
|
+
// cursor is not sent along with the new search.
|
|
190
|
+
const committedSearchKey = JSON.stringify([
|
|
191
|
+
committedSearchQuery,
|
|
192
|
+
committedFilterString,
|
|
193
|
+
selectedTab,
|
|
194
|
+
retrieverType,
|
|
195
|
+
dataSorting,
|
|
196
|
+
]);
|
|
197
|
+
const cursor = pageCursor?.searchKey === committedSearchKey ? pageCursor.cursor : undefined;
|
|
198
|
+
|
|
199
|
+
// The search payload is derived entirely from the committed search state, which lives in the URL
|
|
200
|
+
// (queryString, filterString and activeTab params). Changing any of these values — whether via the
|
|
201
|
+
// UI or a browser back/forward that updates the URL — produces a new payload and RTK Query re-runs
|
|
202
|
+
// the search automatically. No imperative trigger or effect (and no eslint-disable) is needed.
|
|
148
203
|
// Cursor is stripped from the RTK cache key, so a cursor change appends to the accumulated
|
|
149
204
|
// result set instead of creating a new cache entry — see search endpoint's merge() logic.
|
|
150
205
|
const searchPayload: SearchPayload = useMemo(() => {
|
|
@@ -167,6 +222,9 @@ export const WfoSearchPocPage = () => {
|
|
|
167
222
|
|
|
168
223
|
const { data, isFetching } = useSearchQuery(searchPayload);
|
|
169
224
|
|
|
225
|
+
const [getSubscriptionListTrigger] = useLazySearchQuery();
|
|
226
|
+
const getSubscriptionListForExport = () => getSubscriptionListTrigger(searchPayload).unwrap();
|
|
227
|
+
|
|
170
228
|
useEffect(() => {
|
|
171
229
|
const storedConfig = getStoredTableConfig();
|
|
172
230
|
if (storedConfig) {
|
|
@@ -188,6 +246,7 @@ export const WfoSearchPocPage = () => {
|
|
|
188
246
|
renderDetails: (value) => value,
|
|
189
247
|
renderTooltip: (value) => value,
|
|
190
248
|
isSortable: true,
|
|
249
|
+
isFilterable: true,
|
|
191
250
|
},
|
|
192
251
|
description: {
|
|
193
252
|
columnType: ColumnType.DATA,
|
|
@@ -195,37 +254,44 @@ export const WfoSearchPocPage = () => {
|
|
|
195
254
|
width: '500px',
|
|
196
255
|
renderData: (value, record) => <Link href={`/subscriptions/${record.subscriptionId}`}>{value}</Link>,
|
|
197
256
|
renderTooltip: (value) => value,
|
|
257
|
+
isFilterable: true,
|
|
198
258
|
},
|
|
199
259
|
status: {
|
|
200
260
|
columnType: ColumnType.DATA,
|
|
201
261
|
label: t('status'),
|
|
202
262
|
width: '120px',
|
|
203
263
|
renderData: (value) => <WfoSubscriptionStatusBadge status={value} />,
|
|
264
|
+
isFilterable: true,
|
|
204
265
|
},
|
|
205
266
|
insync: {
|
|
206
267
|
columnType: ColumnType.DATA,
|
|
207
268
|
label: t('insync'),
|
|
208
269
|
width: '75px',
|
|
209
270
|
renderData: (value) => <WfoInsyncIcon inSync={value} />,
|
|
271
|
+
isFilterable: true,
|
|
210
272
|
},
|
|
211
273
|
productName: {
|
|
212
274
|
columnType: ColumnType.DATA,
|
|
213
275
|
width: '260px',
|
|
214
276
|
label: t('product'),
|
|
277
|
+
isFilterable: true,
|
|
215
278
|
},
|
|
216
279
|
tag: {
|
|
217
280
|
columnType: ColumnType.DATA,
|
|
218
281
|
label: t('tag'),
|
|
219
282
|
width: '100px',
|
|
283
|
+
isFilterable: true,
|
|
220
284
|
},
|
|
221
285
|
customerFullname: {
|
|
222
286
|
columnType: ColumnType.DATA,
|
|
223
287
|
label: t('customerFullname'),
|
|
288
|
+
isFilterable: true,
|
|
224
289
|
},
|
|
225
290
|
customerShortcode: {
|
|
226
291
|
columnType: ColumnType.DATA,
|
|
227
292
|
label: t('customerShortcode'),
|
|
228
293
|
width: '150px',
|
|
294
|
+
isFilterable: true,
|
|
229
295
|
},
|
|
230
296
|
startDate: {
|
|
231
297
|
columnType: ColumnType.DATA,
|
|
@@ -235,6 +301,7 @@ export const WfoSearchPocPage = () => {
|
|
|
235
301
|
renderDetails: parseDateToLocaleDateTimeString,
|
|
236
302
|
clipboardText: parseDateToLocaleDateTimeString,
|
|
237
303
|
renderTooltip: (cellValue) => cellValue?.toString(),
|
|
304
|
+
isFilterable: true,
|
|
238
305
|
},
|
|
239
306
|
endDate: {
|
|
240
307
|
columnType: ColumnType.DATA,
|
|
@@ -244,6 +311,7 @@ export const WfoSearchPocPage = () => {
|
|
|
244
311
|
renderDetails: parseDateToLocaleDateTimeString,
|
|
245
312
|
clipboardText: parseDateToLocaleDateTimeString,
|
|
246
313
|
renderTooltip: (cellValue) => cellValue?.toString(),
|
|
314
|
+
isFilterable: true,
|
|
247
315
|
},
|
|
248
316
|
note: {
|
|
249
317
|
columnType: ColumnType.DATA,
|
|
@@ -260,6 +328,7 @@ export const WfoSearchPocPage = () => {
|
|
|
260
328
|
/>
|
|
261
329
|
);
|
|
262
330
|
},
|
|
331
|
+
isFilterable: true,
|
|
263
332
|
},
|
|
264
333
|
metadata: {
|
|
265
334
|
columnType: ColumnType.DATA,
|
|
@@ -268,12 +337,28 @@ export const WfoSearchPocPage = () => {
|
|
|
268
337
|
renderData: (value) => <WfoInlineJson data={value} />,
|
|
269
338
|
renderDetails: (value) => value && <WfoJsonCodeBlock data={value} isBasicStyle />,
|
|
270
339
|
renderTooltip: (value) => value && <WfoJsonCodeBlock data={value} isBasicStyle={false} />,
|
|
340
|
+
isFilterable: true,
|
|
271
341
|
},
|
|
272
342
|
};
|
|
273
343
|
|
|
274
344
|
const handleApplyFilter = (searchParams?: SearchParams) => {
|
|
275
|
-
|
|
276
|
-
|
|
345
|
+
const ruleGroupParam = searchParams?.ruleGroup;
|
|
346
|
+
// Use an explicitly passed rule group when provided (e.g. a column-header search), a cleared filter
|
|
347
|
+
// when `false`, and otherwise the current query builder state (the "Apply filter" button).
|
|
348
|
+
const effectiveRuleGroup = ruleGroupParam === false ? undefined : (ruleGroupParam ?? queryBuilderRuleGroup);
|
|
349
|
+
// '' (no rule group, or only placeholder rules) commits an empty filter, clearing the URL param.
|
|
350
|
+
const celQuery =
|
|
351
|
+
effectiveRuleGroup ? formatQuery(effectiveRuleGroup, { format: 'cel', fallbackExpression: '' }) : '';
|
|
352
|
+
// A non-empty CEL string must survive the round trip through the URL: formatQuery escapes double
|
|
353
|
+
// quotes in values but parseCEL has no escape support, so such a filter would silently be dropped
|
|
354
|
+
// after committing. Refuse the commit and flag the filter instead, keeping the URL and the search
|
|
355
|
+
// results consistent.
|
|
356
|
+
if (celQuery && !parseCelToRuleGroup(celQuery)) {
|
|
357
|
+
setIsValidFilterString(false);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
commitFilterString(celQuery);
|
|
361
|
+
setPageCursor(undefined);
|
|
277
362
|
};
|
|
278
363
|
|
|
279
364
|
const onChangeQueryText = (queryText: string) => {
|
|
@@ -282,22 +367,22 @@ export const WfoSearchPocPage = () => {
|
|
|
282
367
|
|
|
283
368
|
const onSearchQueryText = (queryText: string) => {
|
|
284
369
|
setQueryText(queryText);
|
|
285
|
-
|
|
286
|
-
|
|
370
|
+
commitSearchQuery(queryText);
|
|
371
|
+
setPageCursor(undefined);
|
|
287
372
|
};
|
|
288
373
|
|
|
289
374
|
const onUpdateRetrieverType = (retrieverType: RetrieverType) => {
|
|
290
375
|
setRetrieverType(retrieverType);
|
|
291
|
-
|
|
376
|
+
setPageCursor(undefined);
|
|
292
377
|
};
|
|
293
378
|
|
|
294
379
|
const handleChangeTab = (updatedTab: WfoSubscriptionListTab) => {
|
|
295
380
|
setActiveTab(updatedTab);
|
|
296
381
|
setLimit(pageSize);
|
|
297
|
-
|
|
382
|
+
setPageCursor(undefined);
|
|
298
383
|
};
|
|
299
384
|
|
|
300
|
-
const safeCelParse = (celString: string) => {
|
|
385
|
+
const safeCelParse = useCallback((celString: string) => {
|
|
301
386
|
try {
|
|
302
387
|
const ruleGroup = parseCEL(celString);
|
|
303
388
|
if (celString === '') {
|
|
@@ -315,7 +400,32 @@ export const WfoSearchPocPage = () => {
|
|
|
315
400
|
} catch {
|
|
316
401
|
setIsValidFilterString(false);
|
|
317
402
|
}
|
|
318
|
-
};
|
|
403
|
+
}, []);
|
|
404
|
+
|
|
405
|
+
// Populate the search bar and the filter builder from the URL, both on page load and when
|
|
406
|
+
// back/forward navigation changes the committed search. Commits made by this page are skipped —
|
|
407
|
+
// see the lastSelfCommitted refs above.
|
|
408
|
+
useEffect(() => {
|
|
409
|
+
if (committedSearchQuery === lastSelfCommittedQuery.current) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
lastSelfCommittedQuery.current = committedSearchQuery;
|
|
413
|
+
setQueryText(committedSearchQuery);
|
|
414
|
+
}, [committedSearchQuery]);
|
|
415
|
+
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
if (committedFilterString === lastSelfCommittedFilter.current) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
lastSelfCommittedFilter.current = committedFilterString;
|
|
421
|
+
setFilterString(committedFilterString);
|
|
422
|
+
if (committedFilterString) {
|
|
423
|
+
safeCelParse(committedFilterString);
|
|
424
|
+
} else {
|
|
425
|
+
setQueryBuilderRuleGroup(undefined);
|
|
426
|
+
setIsValidFilterString(true);
|
|
427
|
+
}
|
|
428
|
+
}, [committedFilterString, safeCelParse]);
|
|
319
429
|
|
|
320
430
|
const onUpdateQueryBuilder = (ruleGroup: RuleGroupType | false) => {
|
|
321
431
|
if (ruleGroup === false) {
|
|
@@ -349,21 +459,34 @@ export const WfoSearchPocPage = () => {
|
|
|
349
459
|
const hasNextPage = data?.page_info?.has_next_page ?? false;
|
|
350
460
|
const nextPageCursor = data?.page_info?.next_page_cursor ?? undefined;
|
|
351
461
|
|
|
462
|
+
const exportData = async () => {
|
|
463
|
+
const exportResult = await getSubscriptionListForExport();
|
|
464
|
+
const { items: exportItems } = getDataFromResponse<SubscriptionListItem>(
|
|
465
|
+
exportResult,
|
|
466
|
+
resultColumToPropertyMap,
|
|
467
|
+
'subscriptionId',
|
|
468
|
+
);
|
|
469
|
+
if (!exportItems.length) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
initiateCsvFileDownload(exportItems, Object.keys(tableColumnConfig), getCsvFileNameWithDate('Subscriptions'));
|
|
473
|
+
};
|
|
474
|
+
|
|
352
475
|
const onShowMore = () => {
|
|
353
476
|
if (!isFetching && nextPageCursor) {
|
|
354
|
-
|
|
477
|
+
setPageCursor({ cursor: nextPageCursor, searchKey: committedSearchKey });
|
|
355
478
|
}
|
|
356
479
|
};
|
|
357
480
|
|
|
358
481
|
const onUpdateDataSorting = ({ field, sortOrder }: WfoDataSorting<SubscriptionListItem>) => {
|
|
359
482
|
setDataSorting({ field, sortOrder });
|
|
360
483
|
setLimit(pageSize);
|
|
361
|
-
|
|
484
|
+
setPageCursor(undefined);
|
|
362
485
|
};
|
|
363
486
|
|
|
364
487
|
return (
|
|
365
488
|
<>
|
|
366
|
-
<WfoContentHeader title="Subscriptions (
|
|
489
|
+
<WfoContentHeader title="Subscriptions (Beta)" />
|
|
367
490
|
<WfoFilterTabs
|
|
368
491
|
tabs={subscriptionListTabs}
|
|
369
492
|
selectedTab={selectedTab}
|
|
@@ -391,11 +514,14 @@ export const WfoSearchPocPage = () => {
|
|
|
391
514
|
queryText={queryText}
|
|
392
515
|
retrieverType={retrieverType}
|
|
393
516
|
tableColumnConfig={tableColumnConfig}
|
|
517
|
+
getColumnSearchFieldName={(field) => getKeyByValueFromMap(resultColumToPropertyMap, field)}
|
|
394
518
|
pageSize={pageSize}
|
|
395
519
|
onUpdateDataSorting={onUpdateDataSorting}
|
|
396
520
|
setPageSize={setPageSize}
|
|
397
521
|
totalItems={totalItems}
|
|
398
522
|
hasNextPage={hasNextPage}
|
|
523
|
+
prefilledFieldOptions={prefilledFieldOptions}
|
|
524
|
+
onExportData={exportData}
|
|
399
525
|
/>
|
|
400
526
|
</>
|
|
401
527
|
);
|