@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>) => Promise<T[]>;
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 ? await fetchData({ exportAll: 'true' }) : data;
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" })] }), isEmailExport ? 'Preparing...' : '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: {
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
- fetchAllData?: (params?: Record<string, string>) => Promise<T[]>;
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.355",
4
+ "version": "0.8.357",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "provenance": false