@pagamio/frontend-commons-lib 0.8.355 → 0.8.357
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.
|
@@ -51,7 +51,7 @@ type ExportDropdownProps<T extends Record<string, any>> = {
|
|
|
51
51
|
xlsxOptions?: XlsxExportOptions;
|
|
52
52
|
csvOptions?: CsvExportOptions;
|
|
53
53
|
exportAll?: boolean;
|
|
54
|
-
fetchData?: (params?: Record<string, string
|
|
54
|
+
fetchData?: (params?: Record<string, string>, onProgress?: (fetched: number, total: number) => void) => Promise<T[]>;
|
|
55
55
|
enableEmailExport?: boolean;
|
|
56
56
|
emailExportApiUrl?: string;
|
|
57
57
|
onEmailExportSuccess?: (message: string) => void;
|
|
@@ -9,6 +9,7 @@ const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, ex
|
|
|
9
9
|
const [exportType, setExportType] = useState(null);
|
|
10
10
|
const [excludedColumns, setExcludedColumns] = useState(['action']);
|
|
11
11
|
const [isExporting, setIsExporting] = useState(false);
|
|
12
|
+
const [exportProgress, setExportProgress] = useState(null);
|
|
12
13
|
const [exportAllData, setExportAllData] = useState(false);
|
|
13
14
|
const [selectOpen, setSelectOpen] = useState(false);
|
|
14
15
|
const [emailAddress, setEmailAddress] = useState('');
|
|
@@ -116,8 +117,11 @@ const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, ex
|
|
|
116
117
|
const handleContinue = async () => {
|
|
117
118
|
const includedColumns = getIncludedColumns();
|
|
118
119
|
setIsExporting(true);
|
|
120
|
+
setExportProgress(null);
|
|
119
121
|
try {
|
|
120
|
-
const exportData = exportAllData && fetchData
|
|
122
|
+
const exportData = exportAllData && fetchData
|
|
123
|
+
? await fetchData({ exportAll: 'true' }, (fetched, total) => setExportProgress({ fetched, total }))
|
|
124
|
+
: data;
|
|
121
125
|
if (isEmailExport) {
|
|
122
126
|
// Store data, format and show email modal
|
|
123
127
|
setPendingExportData({ data: exportData, columns: includedColumns });
|
|
@@ -138,6 +142,7 @@ const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, ex
|
|
|
138
142
|
}
|
|
139
143
|
finally {
|
|
140
144
|
setIsExporting(false);
|
|
145
|
+
setExportProgress(null);
|
|
141
146
|
}
|
|
142
147
|
};
|
|
143
148
|
const handleExportChange = (type) => {
|
|
@@ -164,7 +169,11 @@ const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, ex
|
|
|
164
169
|
}) }), _jsx(Button, { variant: "primary", className: `w-full mt-4 px-4 py-2 rounded-md text-white flex items-center justify-center
|
|
165
170
|
${getIncludedColumns().length === 0 || isExporting
|
|
166
171
|
? 'bg-gray-300 cursor-not-allowed'
|
|
167
|
-
: 'bg-primary hover:bg-primary/90'}`, onClick: handleContinue, disabled: getIncludedColumns().length === 0 || isExporting, children: isExporting ? (_jsxs(_Fragment, { children: [_jsxs("svg", { className: "animate-spin -ml-1 mr-2 h-4 w-4 text-white", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }),
|
|
172
|
+
: 'bg-primary hover:bg-primary/90'}`, onClick: handleContinue, disabled: getIncludedColumns().length === 0 || isExporting, children: isExporting ? (_jsxs(_Fragment, { children: [_jsxs("svg", { className: "animate-spin -ml-1 mr-2 h-4 w-4 text-white", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }), exportProgress && exportProgress.total > exportProgress.fetched
|
|
173
|
+
? `Fetching ${exportProgress.fetched}/${exportProgress.total}...`
|
|
174
|
+
: isEmailExport
|
|
175
|
+
? 'Preparing...'
|
|
176
|
+
: 'Exporting...'] })) : ('Continue') })] })), _jsx(Modal, { isOpen: showEmailModal, onClose: handleEmailModalClose, title: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(HiMail, { className: "w-5 h-5 text-primary" }), _jsx("span", { children: "Send Export via Email" })] }), size: "md", primaryButton: {
|
|
168
177
|
label: 'Send Email',
|
|
169
178
|
onClick: () => {
|
|
170
179
|
handleEmailModalSubmit().catch((error) => {
|
|
@@ -82,7 +82,14 @@ export interface ExportConfig<T extends Record<string, any>> {
|
|
|
82
82
|
xlsx?: XlsxExportOptions;
|
|
83
83
|
csv?: CsvExportOptions;
|
|
84
84
|
extraOptions?: ExtraOption[];
|
|
85
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Fetch the full dataset for "Export all data" (not just the current page).
|
|
87
|
+
* `onProgress(fetched, total)` is invoked after each page so the UI can show
|
|
88
|
+
* progress on long, multi-request exports. `usePagamioTable` auto-wires this
|
|
89
|
+
* for server-paginated tables; a consumer may override (e.g. a dedicated
|
|
90
|
+
* export endpoint).
|
|
91
|
+
*/
|
|
92
|
+
fetchAllData?: (params?: Record<string, string>, onProgress?: (fetched: number, total: number) => void) => Promise<T[]>;
|
|
86
93
|
email?: {
|
|
87
94
|
apiUrl: string;
|
|
88
95
|
onSuccess?: (message: string) => void;
|
|
@@ -138,6 +138,12 @@ function buildQueryParams(config, pagination, sorting, filtering) {
|
|
|
138
138
|
}
|
|
139
139
|
return params;
|
|
140
140
|
}
|
|
141
|
+
// Backend caps `limit` at 200 (PaginationDto `@Max(200)`, anti-DoS). Export-all
|
|
142
|
+
// pages through at this max so the whole dataset reaches the client in the
|
|
143
|
+
// fewest round-trips the contract allows. Driven through the hook's own param
|
|
144
|
+
// builder — never a hardcoded `limit` in a consumer `queryFn` (see backend
|
|
145
|
+
// pagination convention).
|
|
146
|
+
const MAX_EXPORT_PAGE_SIZE = 200;
|
|
141
147
|
function clientFilter(items, filters) {
|
|
142
148
|
return items.filter((item) => Object.entries(filters).every(([key, value]) => key === 'search' || !value || value === 'all' || item[key]?.toString() === value));
|
|
143
149
|
}
|
|
@@ -264,6 +270,52 @@ export const usePagamioTable = ({ queryKey, queryFn, enabled: queryEnabled = tru
|
|
|
264
270
|
queryClient.invalidateQueries({ queryKey });
|
|
265
271
|
}
|
|
266
272
|
}, [isServerMode, queryKey, queryClient]);
|
|
273
|
+
// ── Export-all: fetch every server row, not just the current page ──
|
|
274
|
+
// Pages through the same `queryFn` at the backend's max `limit`, preserving
|
|
275
|
+
// the user's current sort/filters/search so the export matches what they see.
|
|
276
|
+
// Returns the full flattened dataset for the client-side export utils.
|
|
277
|
+
const fetchAllPages = useCallback(async (_params, onProgress) => {
|
|
278
|
+
if (!isServerMode || !queryFn) {
|
|
279
|
+
// Client-mode tables already hold the whole filtered/sorted set.
|
|
280
|
+
onProgress?.(processedData.length, processedData.length);
|
|
281
|
+
return processedData;
|
|
282
|
+
}
|
|
283
|
+
const buildPageParams = (page) => buildQueryParams(queryParamConfig, { mode: paginationConfig.mode, page, size: MAX_EXPORT_PAGE_SIZE }, { mode: sortingConfig.mode, sortConfig }, { mode: filteringConfig.mode, searchMode, filters: appliedFilters });
|
|
284
|
+
// Always start from the first page (index 0) regardless of where the user
|
|
285
|
+
// is paged to. The first response also gives us the true total, so we
|
|
286
|
+
// derive the page count from it rather than the possibly-stale live view.
|
|
287
|
+
const firstRaw = await queryFn(buildPageParams(0));
|
|
288
|
+
const first = processResponse(firstRaw, responseMapping);
|
|
289
|
+
const all = [...first.data];
|
|
290
|
+
const total = first.total || serverTotal;
|
|
291
|
+
const totalPages = Math.max(1, Math.ceil(total / MAX_EXPORT_PAGE_SIZE));
|
|
292
|
+
onProgress?.(all.length, total);
|
|
293
|
+
// Pages are zero/one-based per the consumer's `queryParamConfig.transform`;
|
|
294
|
+
// `buildQueryParams` already applies that transform, so we just feed it the
|
|
295
|
+
// hook's internal zero-based index and let the config bump it.
|
|
296
|
+
for (let page = 1; page < totalPages; page++) {
|
|
297
|
+
const raw = await queryFn(buildPageParams(page));
|
|
298
|
+
const { data: pageData } = processResponse(raw, responseMapping);
|
|
299
|
+
if (pageData.length === 0)
|
|
300
|
+
break; // defensive: stop if a page comes back empty
|
|
301
|
+
all.push(...pageData);
|
|
302
|
+
onProgress?.(all.length, total);
|
|
303
|
+
}
|
|
304
|
+
return all;
|
|
305
|
+
}, [
|
|
306
|
+
isServerMode,
|
|
307
|
+
queryFn,
|
|
308
|
+
processedData,
|
|
309
|
+
queryParamConfig,
|
|
310
|
+
paginationConfig.mode,
|
|
311
|
+
sortingConfig.mode,
|
|
312
|
+
sortConfig,
|
|
313
|
+
filteringConfig.mode,
|
|
314
|
+
searchMode,
|
|
315
|
+
appliedFilters,
|
|
316
|
+
responseMapping,
|
|
317
|
+
serverTotal,
|
|
318
|
+
]);
|
|
267
319
|
// ── Search handler (for <input> onChange) ────────────────────────
|
|
268
320
|
const handleSearchChange = useCallback((e) => {
|
|
269
321
|
setSearchQuery(e.target.value);
|
|
@@ -307,6 +359,18 @@ export const usePagamioTable = ({ queryKey, queryFn, enabled: queryEnabled = tru
|
|
|
307
359
|
...toolbar,
|
|
308
360
|
onClearFilters: clearFilters,
|
|
309
361
|
showClearFilters: toolbar.showClearFilters ?? true,
|
|
362
|
+
// Auto-wire export-all for server-paginated tables. Providing
|
|
363
|
+
// `fetchAllData` makes PagamioTable surface the "Export all data"
|
|
364
|
+
// checkbox; checking it pages through the full dataset. A consumer
|
|
365
|
+
// can still override with its own `fetchAllData` (e.g. a dedicated
|
|
366
|
+
// export endpoint).
|
|
367
|
+
...(toolbar.export?.enabled &&
|
|
368
|
+
isServerMode && {
|
|
369
|
+
export: {
|
|
370
|
+
...toolbar.export,
|
|
371
|
+
fetchAllData: toolbar.export.fetchAllData ?? fetchAllPages,
|
|
372
|
+
},
|
|
373
|
+
}),
|
|
310
374
|
},
|
|
311
375
|
}),
|
|
312
376
|
...(onRowClick && { onRowClick }),
|
|
@@ -336,6 +400,8 @@ export const usePagamioTable = ({ queryKey, queryFn, enabled: queryEnabled = tru
|
|
|
336
400
|
searchPlaceholder,
|
|
337
401
|
toolbar,
|
|
338
402
|
clearFilters,
|
|
403
|
+
isServerMode,
|
|
404
|
+
fetchAllPages,
|
|
339
405
|
onRowClick,
|
|
340
406
|
rowClassName,
|
|
341
407
|
expandable,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagamio/frontend-commons-lib",
|
|
3
3
|
"description": "Pagamio library for Frontend reusable components like the form engine and table container",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.357",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"provenance": false
|