@pagamio/frontend-commons-lib 0.8.357 → 0.8.358

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.
@@ -52,10 +52,17 @@ type ExportDropdownProps<T extends Record<string, any>> = {
52
52
  csvOptions?: CsvExportOptions;
53
53
  exportAll?: boolean;
54
54
  fetchData?: (params?: Record<string, string>, onProgress?: (fetched: number, total: number) => void) => Promise<T[]>;
55
+ asyncExport?: {
56
+ threshold: number;
57
+ totalRows: number;
58
+ requestExport: (format: 'csv' | 'xlsx' | 'pdf') => Promise<void>;
59
+ onQueued?: (message: string) => void;
60
+ onError?: (error: Error) => void;
61
+ };
55
62
  enableEmailExport?: boolean;
56
63
  emailExportApiUrl?: string;
57
64
  onEmailExportSuccess?: (message: string) => void;
58
65
  onEmailExportError?: (error: Error) => void;
59
66
  };
60
- declare const ExportDropdown: <T extends Record<string, any>>({ data, columns, containerClassName, buttonClassName, extraOptions, pdfOptions, xlsxOptions, csvOptions, exportAll, fetchData, enableEmailExport, emailExportApiUrl, onEmailExportSuccess, onEmailExportError, }: ExportDropdownProps<T>) => import("react/jsx-runtime").JSX.Element;
67
+ declare const ExportDropdown: <T extends Record<string, any>>({ data, columns, containerClassName, buttonClassName, extraOptions, pdfOptions, xlsxOptions, csvOptions, exportAll, fetchData, asyncExport, enableEmailExport, emailExportApiUrl, onEmailExportSuccess, onEmailExportError, }: ExportDropdownProps<T>) => import("react/jsx-runtime").JSX.Element;
61
68
  export default ExportDropdown;
@@ -5,7 +5,7 @@ import { HiMail } from 'react-icons/hi';
5
5
  import { useEffect, useRef, useState } from 'react';
6
6
  import { Button, Modal, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../components';
7
7
  import { exportToCsv, exportToPdf, exportToXlsx, generateExportAsFile, sendExportEmail } from './exportUtils';
8
- const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, extraOptions = [], pdfOptions, xlsxOptions, csvOptions, exportAll = false, fetchData, enableEmailExport = false, emailExportApiUrl, onEmailExportSuccess, onEmailExportError, }) => {
8
+ const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, extraOptions = [], pdfOptions, xlsxOptions, csvOptions, exportAll = false, fetchData, asyncExport, enableEmailExport = false, emailExportApiUrl, onEmailExportSuccess, onEmailExportError, }) => {
9
9
  const [exportType, setExportType] = useState(null);
10
10
  const [excludedColumns, setExcludedColumns] = useState(['action']);
11
11
  const [isExporting, setIsExporting] = useState(false);
@@ -114,11 +114,28 @@ const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, ex
114
114
  setPendingEmailFormat(null);
115
115
  setIsExporting(false);
116
116
  };
117
+ // When exporting all and the dataset is too large to pull into the browser,
118
+ // hand off to the async backend job instead of paging through it client-side.
119
+ const shouldQueueAsync = exportAllData && !!asyncExport && asyncExport.totalRows > asyncExport.threshold;
117
120
  const handleContinue = async () => {
118
121
  const includedColumns = getIncludedColumns();
119
122
  setIsExporting(true);
120
123
  setExportProgress(null);
121
124
  try {
125
+ if (shouldQueueAsync && asyncExport) {
126
+ // Async path: server generates the file off the request path and
127
+ // delivers it via export history / email. Nothing to render here.
128
+ const format = (isEmailExport ? emailFormat : exportType);
129
+ try {
130
+ await asyncExport.requestExport(format);
131
+ asyncExport.onQueued?.('Your export is being prepared. It will appear in export history when ready.');
132
+ }
133
+ catch (err) {
134
+ asyncExport.onError?.(err instanceof Error ? err : new Error('Export request failed'));
135
+ }
136
+ setExportType(null);
137
+ return;
138
+ }
122
139
  const exportData = exportAllData && fetchData
123
140
  ? await fetchData({ exportAll: 'true' }, (fetched, total) => setExportProgress({ fetched, total }))
124
141
  : data;
@@ -162,7 +179,9 @@ const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, ex
162
179
  };
163
180
  // Show popover when exportType is set and Select is closed
164
181
  const popoverOpened = exportType !== null && !selectOpen;
165
- return (_jsxs("div", { className: `w-40 ${containerClassName ?? ''}`, children: [_jsxs(Select, { value: exportType ?? 'none', open: selectOpen, onOpenChange: setSelectOpen, onValueChange: (value) => handleExportChange(value === 'none' ? null : value), children: [_jsx(SelectTrigger, { className: `w-full ${buttonClassName ?? ''}`, children: _jsx(SelectValue, { placeholder: "Export Data" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "none", children: _jsx("span", { className: "text-muted-foreground", children: "Export Data As" }) }, "reset"), exportOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))), extraOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value)))] })] }, "export-dropdown"), popoverOpened && (_jsxs("div", { ref: popoverRef, className: "mt-2 bg-background rounded-md shadow-lg p-4 z-50 absolute w-45", children: [_jsx("div", { className: "text-sm text-muted-foreground pb-3", children: "Uncheck to exclude column from export" }), _jsxs("div", { className: "mb-4 p-3 bg-muted rounded-md", children: [_jsxs("div", { className: "flex items-center gap-x-2", children: [_jsx(Checkbox, { id: "export-all-data", checked: exportAllData, onChange: (event) => setExportAllData(event.currentTarget.checked), className: "checked:!bg-primary checked:!border-primary" }), _jsx(Label, { htmlFor: "export-all-data", className: "text-sm font-medium", children: "Export all data" })] }), _jsx("p", { className: "text-xs text-muted-foreground mt-1 ml-6", children: "This will fetch all records for the table" })] }), _jsx(Stack, { children: columns.map((col) => {
182
+ return (_jsxs("div", { className: `w-40 ${containerClassName ?? ''}`, children: [_jsxs(Select, { value: exportType ?? 'none', open: selectOpen, onOpenChange: setSelectOpen, onValueChange: (value) => handleExportChange(value === 'none' ? null : value), children: [_jsx(SelectTrigger, { className: `w-full ${buttonClassName ?? ''}`, children: _jsx(SelectValue, { placeholder: "Export Data" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "none", children: _jsx("span", { className: "text-muted-foreground", children: "Export Data As" }) }, "reset"), exportOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))), extraOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value)))] })] }, "export-dropdown"), popoverOpened && (_jsxs("div", { ref: popoverRef, className: "mt-2 bg-background rounded-md shadow-lg p-4 z-50 absolute w-45", children: [_jsx("div", { className: "text-sm text-muted-foreground pb-3", children: "Uncheck to exclude column from export" }), _jsxs("div", { className: "mb-4 p-3 bg-muted rounded-md", children: [_jsxs("div", { className: "flex items-center gap-x-2", children: [_jsx(Checkbox, { id: "export-all-data", checked: exportAllData, onChange: (event) => setExportAllData(event.currentTarget.checked), className: "checked:!bg-primary checked:!border-primary" }), _jsx(Label, { htmlFor: "export-all-data", className: "text-sm font-medium", children: "Export all data" })] }), _jsx("p", { className: "text-xs text-muted-foreground mt-1 ml-6", children: shouldQueueAsync
183
+ ? 'This dataset is large — it will be prepared in the background and appear in export history.'
184
+ : 'This will fetch all records for the table' })] }), _jsx(Stack, { children: columns.map((col) => {
166
185
  if (col.accessorKey !== 'action') {
167
186
  return (_jsxs("div", { className: "flex items-center gap-x-2", children: [_jsx(Checkbox, { id: col.header, checked: !excludedColumns.includes(col.accessorKey), onChange: (event) => handleCheckboxChange(col.accessorKey, !event.currentTarget.checked), className: "checked:!bg-primary checked:!border-primary" }), _jsx(Label, { htmlFor: "rememberMe", children: col.header })] }, col.accessorKey));
168
187
  }
@@ -97,7 +97,7 @@ function CustomToolbar({ filters, appliedFilters, onFilterChange, onApply, onCle
97
97
  else {
98
98
  handleFilter(name, value);
99
99
  }
100
- }, handleApplyFilters: onApply, resetFilters: onClearFilters ?? (() => { }), showSearch: searchEnabled, searchQuery: searchQuery, onSearch: onSearch, isNarrow: isNarrow, children: _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [exportConfig?.enabled && (_jsx(ExportDropdown, { containerClassName: isNarrow ? 'w-full' : undefined, columns: columns, data: data, buttonClassName: "h-10", extraOptions: exportConfig.extraOptions, pdfOptions: exportConfig.pdf, xlsxOptions: exportConfig.xlsx, csvOptions: exportConfig.csv, exportAll: !!exportConfig.fetchAllData, fetchData: exportConfig.fetchAllData, enableEmailExport: !!exportConfig.email, emailExportApiUrl: exportConfig.email?.apiUrl, onEmailExportSuccess: exportConfig.email?.onSuccess, onEmailExportError: exportConfig.email?.onError })), addButton &&
100
+ }, handleApplyFilters: onApply, resetFilters: onClearFilters ?? (() => { }), showSearch: searchEnabled, searchQuery: searchQuery, onSearch: onSearch, isNarrow: isNarrow, children: _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [exportConfig?.enabled && (_jsx(ExportDropdown, { containerClassName: isNarrow ? 'w-full' : undefined, columns: columns, data: data, buttonClassName: "h-10", extraOptions: exportConfig.extraOptions, pdfOptions: exportConfig.pdf, xlsxOptions: exportConfig.xlsx, csvOptions: exportConfig.csv, exportAll: !!exportConfig.fetchAllData, fetchData: exportConfig.fetchAllData, asyncExport: exportConfig.asyncExport, enableEmailExport: !!exportConfig.email, emailExportApiUrl: exportConfig.email?.apiUrl, onEmailExportSuccess: exportConfig.email?.onSuccess, onEmailExportError: exportConfig.email?.onError })), addButton &&
101
101
  (typeof addButton === 'boolean' ? (_jsx(IconButton, { onClick: onAdd ?? (() => { }), icon: HiPlusSm, label: "add-new-entry", className: cn('items-center text-nowrap', isNarrow ? 'w-full' : undefined), children: addText ?? 'Add New Entry' })) : (addButton))] }) }));
102
102
  }
103
103
  // ─── PagamioTable Component ──────────────────────────────────────────
@@ -95,6 +95,29 @@ export interface ExportConfig<T extends Record<string, any>> {
95
95
  onSuccess?: (message: string) => void;
96
96
  onError?: (error: Error) => void;
97
97
  };
98
+ /**
99
+ * Async (queued) export for datasets too large to page through and render in
100
+ * the browser. When "Export all data" is checked and `totalRows > threshold`,
101
+ * the export button calls `requestExport(format)` instead of fetching the
102
+ * whole set client-side — the backend generates the file off the request path
103
+ * and delivers it via export history / email.
104
+ *
105
+ * `usePagamioTable` auto-wires `totalRows` from the table's row count; the
106
+ * consumer supplies `threshold` and `requestExport` (which POSTs to
107
+ * `/exports` with its `resourceKey` + current filters).
108
+ */
109
+ asyncExport?: {
110
+ /** Row count above which export is queued instead of run client-side. */
111
+ threshold: number;
112
+ /** Current total server rows (auto-wired by the hook). */
113
+ totalRows: number;
114
+ /** Queue a server-side export of the full dataset in the chosen format. */
115
+ requestExport: (format: 'csv' | 'xlsx' | 'pdf') => Promise<void>;
116
+ /** Called after a successful queue request (e.g. to toast the user). */
117
+ onQueued?: (message: string) => void;
118
+ /** Called if the queue request fails. */
119
+ onError?: (error: Error) => void;
120
+ };
98
121
  }
99
122
  export interface ToolbarConfig<T extends Record<string, any>> {
100
123
  filters?: BaseFilter[];
@@ -369,6 +369,14 @@ export const usePagamioTable = ({ queryKey, queryFn, enabled: queryEnabled = tru
369
369
  export: {
370
370
  ...toolbar.export,
371
371
  fetchAllData: toolbar.export.fetchAllData ?? fetchAllPages,
372
+ // Keep the async-export row count in sync with the live total so
373
+ // the consumer only has to supply `threshold` + `requestExport`.
374
+ ...(toolbar.export.asyncExport && {
375
+ asyncExport: {
376
+ ...toolbar.export.asyncExport,
377
+ totalRows: totalItems,
378
+ },
379
+ }),
372
380
  },
373
381
  }),
374
382
  },
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.357",
4
+ "version": "0.8.358",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "provenance": false