@pagamio/frontend-commons-lib 0.8.206 → 0.8.208

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.
@@ -5,8 +5,13 @@ import React, { useEffect, useState } from 'react';
5
5
  import { useAppSidebarContext } from '../../context';
6
6
  import { useLibTranslations, useTranslation } from '../../translations';
7
7
  const AppSidebarMenu = () => {
8
- const { pages } = useAppSidebarContext();
9
- return (_jsx(Sidebar.Items, { children: pages.map((item) => (_jsx(Sidebar.ItemGroup, { children: _jsx(AppSidebarItem, { ...item }) }, item.label))) }));
8
+ const { pages, groupByItem } = useAppSidebarContext();
9
+ // If groupByItem is true each item gets its own ItemGroup
10
+ if (groupByItem) {
11
+ return (_jsx(Sidebar.Items, { children: pages.map((item) => (_jsx(Sidebar.ItemGroup, { children: _jsx(AppSidebarItem, { ...item }) }, item.label))) }));
12
+ }
13
+ // Default behavior all items in one ItemGroup
14
+ return (_jsx(Sidebar.Items, { children: _jsx(Sidebar.ItemGroup, { children: pages.map((item) => (_jsx(AppSidebarItem, { ...item }, item.label))) }) }));
10
15
  };
11
16
  const AppSidebarItem = ({ href, target, icon, label, items, badge, forceDropdown }) => {
12
17
  const { pathname, linkComponent: Link } = useAppSidebarContext();
@@ -20,6 +20,7 @@ interface AppSidebarPageItem {
20
20
  * @property {React.ElementType} linkComponent - Component used for navigation links
21
21
  * @property {React.ReactNode} sidebarHeader - Optional custom header to render at top of sidebar
22
22
  * @property {React.ReactNode} sidebarFooter - Optional custom footer to render at bottom of sidebar
23
+ * @property {boolean} groupByItem - If true, each sidebar item gets its own ItemGroup. If false, all items share one ItemGroup.
23
24
  */
24
25
  interface AppSidebarContextProps {
25
26
  desktop: {
@@ -37,6 +38,7 @@ interface AppSidebarContextProps {
37
38
  linkComponent: React.ElementType;
38
39
  sidebarHeader?: React.ReactNode;
39
40
  sidebarFooter?: React.ReactNode;
41
+ groupByItem?: boolean;
40
42
  }
41
43
  /**
42
44
  * Props for the AppSidebarProvider component
@@ -48,6 +50,7 @@ interface AppSidebarContextProps {
48
50
  * @property {ReactNode} children - Child components
49
51
  * @property {React.ReactNode} sidebarHeader - Optional custom header to render at top of sidebar
50
52
  * @property {React.ReactNode} sidebarFooter - Optional custom footer to render at bottom of sidebar
53
+ * @property {boolean} groupByItem - If true, each sidebar item gets its own ItemGroup. If false (default), all items share one ItemGroup.
51
54
  */
52
55
  interface AppSidebarProviderProps extends PropsWithChildren {
53
56
  initialCollapsed: boolean;
@@ -56,12 +59,13 @@ interface AppSidebarProviderProps extends PropsWithChildren {
56
59
  linkComponent: React.ElementType;
57
60
  sidebarHeader?: React.ReactNode;
58
61
  sidebarFooter?: React.ReactNode;
62
+ groupByItem?: boolean;
59
63
  }
60
64
  /**
61
65
  * Provider component for sidebar state management
62
66
  * @param {AppSidebarProviderProps} props - Component props
63
67
  */
64
- declare const AppSidebarProvider: ({ initialCollapsed, children, pages, pathname, linkComponent, sidebarHeader, sidebarFooter, }: AppSidebarProviderProps) => import("react/jsx-runtime").JSX.Element;
68
+ declare const AppSidebarProvider: ({ initialCollapsed, children, pages, pathname, linkComponent, sidebarHeader, sidebarFooter, groupByItem, }: AppSidebarProviderProps) => import("react/jsx-runtime").JSX.Element;
65
69
  /**
66
70
  * Hook for accessing sidebar context
67
71
  * @throws {Error} When used outside AppSidebarProvider
@@ -8,7 +8,7 @@ const AppSidebarContext = createContext(null);
8
8
  * Provider component for sidebar state management
9
9
  * @param {AppSidebarProviderProps} props - Component props
10
10
  */
11
- const AppSidebarProvider = ({ initialCollapsed, children, pages, pathname, linkComponent, sidebarHeader, sidebarFooter, }) => {
11
+ const AppSidebarProvider = ({ initialCollapsed, children, pages, pathname, linkComponent, sidebarHeader, sidebarFooter, groupByItem = false, }) => {
12
12
  const [isOpenMobile, setIsOpenMobile] = useState(false);
13
13
  const [isCollapsed, setIsCollapsed] = useState(initialCollapsed);
14
14
  function handleSetCollapsed(value) {
@@ -30,7 +30,8 @@ const AppSidebarProvider = ({ initialCollapsed, children, pages, pathname, linkC
30
30
  linkComponent,
31
31
  sidebarHeader,
32
32
  sidebarFooter,
33
- }), [isCollapsed, isOpenMobile, pages, pathname, linkComponent, sidebarHeader, sidebarFooter]);
33
+ groupByItem,
34
+ }), [isCollapsed, isOpenMobile, pages, pathname, linkComponent, sidebarHeader, sidebarFooter, groupByItem]);
34
35
  return _jsx(AppSidebarContext.Provider, { value: value, children: children });
35
36
  };
36
37
  /**
@@ -27,8 +27,8 @@ export declare const signupFormSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
27
27
  password: string;
28
28
  firstName: string;
29
29
  lastName: string;
30
- confirmPassword: string;
31
30
  emailAddress: string;
31
+ confirmPassword: string;
32
32
  profile: string;
33
33
  role?: string | undefined;
34
34
  }, {
@@ -36,8 +36,8 @@ export declare const signupFormSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
36
36
  password: string;
37
37
  firstName: string;
38
38
  lastName: string;
39
- confirmPassword: string;
40
39
  emailAddress: string;
40
+ confirmPassword: string;
41
41
  profile: string;
42
42
  role?: string | undefined;
43
43
  }>, {
@@ -45,8 +45,8 @@ export declare const signupFormSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
45
45
  password: string;
46
46
  firstName: string;
47
47
  lastName: string;
48
- confirmPassword: string;
49
48
  emailAddress: string;
49
+ confirmPassword: string;
50
50
  profile: string;
51
51
  role?: string | undefined;
52
52
  }, {
@@ -54,8 +54,8 @@ export declare const signupFormSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
54
54
  password: string;
55
55
  firstName: string;
56
56
  lastName: string;
57
- confirmPassword: string;
58
57
  emailAddress: string;
58
+ confirmPassword: string;
59
59
  profile: string;
60
60
  role?: string | undefined;
61
61
  }>, {
@@ -63,8 +63,8 @@ export declare const signupFormSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
63
63
  password: string;
64
64
  firstName: string;
65
65
  lastName: string;
66
- confirmPassword: string;
67
66
  emailAddress: string;
67
+ confirmPassword: string;
68
68
  profile: string;
69
69
  role?: string | undefined;
70
70
  }, {
@@ -72,8 +72,8 @@ export declare const signupFormSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
72
72
  password: string;
73
73
  firstName: string;
74
74
  lastName: string;
75
- confirmPassword: string;
76
75
  emailAddress: string;
76
+ confirmPassword: string;
77
77
  profile: string;
78
78
  role?: string | undefined;
79
79
  }>;
@@ -52,6 +52,10 @@ type ExportDropdownProps<T extends Record<string, any>> = {
52
52
  csvOptions?: CsvExportOptions;
53
53
  exportAll?: boolean;
54
54
  fetchData?: (params?: Record<string, string>) => Promise<T[]>;
55
+ enableEmailExport?: boolean;
56
+ emailExportApiUrl?: string;
57
+ onEmailExportSuccess?: (message: string) => void;
58
+ onEmailExportError?: (error: Error) => void;
55
59
  };
56
- declare const ExportDropdown: <T extends Record<string, any>>({ data, columns, containerClassName, buttonClassName, extraOptions, pdfOptions, xlsxOptions, csvOptions, exportAll, fetchData, }: ExportDropdownProps<T>) => import("react/jsx-runtime").JSX.Element;
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;
57
61
  export default ExportDropdown;
@@ -1,15 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Stack } from '@mantine/core';
3
- import { Checkbox, Label } from 'flowbite-react';
3
+ import { Checkbox, Label, TextInput } from 'flowbite-react';
4
+ import { HiMail } from 'react-icons/hi';
4
5
  import { useEffect, useRef, useState } from 'react';
5
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../components';
6
- import { exportToCsv, exportToPdf, exportToXlsx } from './exportUtils';
7
- const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, extraOptions = [], pdfOptions, xlsxOptions, csvOptions, exportAll = false, fetchData, }) => {
6
+ import { Modal, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../components';
7
+ import { exportToCsv, exportToPdf, exportToXlsx, generateExportAsBase64, sendExportEmail } from './exportUtils';
8
+ const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, extraOptions = [], pdfOptions, xlsxOptions, csvOptions, exportAll = false, fetchData, enableEmailExport = false, emailExportApiUrl, onEmailExportSuccess, onEmailExportError, }) => {
8
9
  const [exportType, setExportType] = useState(null);
9
10
  const [excludedColumns, setExcludedColumns] = useState(['action']);
10
11
  const [isExporting, setIsExporting] = useState(false);
11
12
  const [exportAllData, setExportAllData] = useState(false);
12
13
  const [selectOpen, setSelectOpen] = useState(false);
14
+ const [emailAddress, setEmailAddress] = useState('');
15
+ const [emailError, setEmailError] = useState('');
16
+ const [showEmailModal, setShowEmailModal] = useState(false);
17
+ const [pendingExportData, setPendingExportData] = useState(null);
18
+ const [pendingEmailFormat, setPendingEmailFormat] = useState(null);
13
19
  const popoverRef = useRef(null);
14
20
  useEffect(() => {
15
21
  const handleClickOutside = (event) => {
@@ -26,6 +32,22 @@ const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, ex
26
32
  { value: 'csv', label: 'Export as CSV' },
27
33
  { value: 'xlsx', label: 'Export as XLSX' },
28
34
  { value: 'pdf', label: 'Export as PDF' },
35
+ ...(enableEmailExport
36
+ ? [
37
+ {
38
+ value: 'email-csv',
39
+ label: (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx(HiMail, { className: "w-4 h-4" }), " Email as CSV"] })),
40
+ },
41
+ {
42
+ value: 'email-xlsx',
43
+ label: (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx(HiMail, { className: "w-4 h-4" }), " Email as XLSX"] })),
44
+ },
45
+ {
46
+ value: 'email-pdf',
47
+ label: (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx(HiMail, { className: "w-4 h-4" }), " Email as PDF"] })),
48
+ },
49
+ ]
50
+ : []),
29
51
  ];
30
52
  const handleCheckboxChange = (columnKey, checked) => {
31
53
  setExcludedColumns((prev) => (checked ? [...prev, columnKey] : prev.filter((key) => key !== columnKey)));
@@ -33,42 +55,104 @@ const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, ex
33
55
  const getIncludedColumns = () => {
34
56
  return columns.filter((col) => !excludedColumns.includes(col.accessorKey));
35
57
  };
58
+ const isEmailExport = exportType?.startsWith('email-');
59
+ const emailFormat = isEmailExport ? exportType?.replace('email-', '') : null;
60
+ const validateEmail = (email) => {
61
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
62
+ return emailRegex.test(email);
63
+ };
64
+ const performExport = async (exportData, includedColumns) => {
65
+ if (exportType === 'csv') {
66
+ exportToCsv(exportData, includedColumns, csvOptions);
67
+ }
68
+ else if (exportType === 'xlsx') {
69
+ exportToXlsx(exportData, includedColumns, xlsxOptions);
70
+ }
71
+ else if (exportType === 'pdf') {
72
+ await exportToPdf(exportData, includedColumns, {
73
+ title: pdfOptions?.title || 'Data Export',
74
+ subtitle: pdfOptions?.subtitle,
75
+ colors: pdfOptions?.colors,
76
+ logo: pdfOptions?.logo,
77
+ });
78
+ }
79
+ };
80
+ const handleEmailModalSubmit = async () => {
81
+ console.log('handleEmailModalSubmit called');
82
+ console.log('emailAddress:', emailAddress);
83
+ console.log('pendingExportData:', pendingExportData);
84
+ console.log('pendingEmailFormat:', pendingEmailFormat);
85
+ if (!emailAddress.trim() || !validateEmail(emailAddress)) {
86
+ setEmailError(emailAddress.trim() ? 'Please enter a valid email address' : 'Email address is required');
87
+ return;
88
+ }
89
+ if (!pendingExportData || !pendingEmailFormat) {
90
+ console.log('Missing pendingExportData or pendingEmailFormat');
91
+ return;
92
+ }
93
+ setEmailError('');
94
+ setIsExporting(true);
95
+ try {
96
+ console.log('Generating export as base64...');
97
+ const base64File = await generateExportAsBase64(pendingExportData.data, pendingExportData.columns, pendingEmailFormat, { pdfOptions, xlsxOptions, csvOptions });
98
+ console.log('Base64 file generated, length:', base64File.length);
99
+ console.log('Sending email to:', emailAddress);
100
+ const result = await sendExportEmail(emailAddress, base64File, emailExportApiUrl);
101
+ console.log('Email sent successfully:', result);
102
+ onEmailExportSuccess?.(result || 'Export sent successfully to your email');
103
+ setEmailAddress('');
104
+ setShowEmailModal(false);
105
+ setPendingExportData(null);
106
+ setPendingEmailFormat(null);
107
+ }
108
+ catch (error) {
109
+ console.error('Email export failed:', error);
110
+ onEmailExportError?.(error instanceof Error ? error : new Error('Failed to send email'));
111
+ setEmailError('Failed to send email. Please try again.');
112
+ }
113
+ finally {
114
+ setIsExporting(false);
115
+ }
116
+ };
117
+ const handleEmailModalClose = () => {
118
+ setShowEmailModal(false);
119
+ setEmailAddress('');
120
+ setEmailError('');
121
+ setPendingExportData(null);
122
+ setPendingEmailFormat(null);
123
+ setIsExporting(false);
124
+ };
36
125
  const handleContinue = async () => {
37
126
  const includedColumns = getIncludedColumns();
38
127
  setIsExporting(true);
39
128
  try {
40
- let exportData = data;
41
- // If exportAll is checked and fetchData function is provided, fetch all data
42
- if (exportAllData && fetchData) {
43
- exportData = await fetchData({ exportAll: 'true' });
44
- }
45
- if (exportType === 'csv') {
46
- exportToCsv(exportData, includedColumns, csvOptions);
47
- }
48
- else if (exportType === 'xlsx') {
49
- exportToXlsx(exportData, includedColumns, xlsxOptions);
129
+ const exportData = exportAllData && fetchData ? await fetchData({ exportAll: 'true' }) : data;
130
+ if (isEmailExport) {
131
+ // Store data, format and show email modal
132
+ setPendingExportData({ data: exportData, columns: includedColumns });
133
+ setPendingEmailFormat(emailFormat); // Store the format before closing popover
134
+ setShowEmailModal(true);
135
+ setExportType(null); // Close the popover
50
136
  }
51
- else if (exportType === 'pdf') {
52
- // Pass PDF options with enhanced configuration
53
- await exportToPdf(exportData, includedColumns, {
54
- title: pdfOptions?.title || 'Data Export',
55
- subtitle: pdfOptions?.subtitle,
56
- colors: pdfOptions?.colors,
57
- logo: pdfOptions?.logo,
58
- });
137
+ else {
138
+ await performExport(exportData, includedColumns);
139
+ setExportType(null);
59
140
  }
60
141
  }
61
142
  catch (error) {
62
143
  console.error('Export failed:', error);
144
+ if (isEmailExport) {
145
+ onEmailExportError?.(error instanceof Error ? error : new Error('Export failed'));
146
+ }
63
147
  }
64
148
  finally {
65
149
  setIsExporting(false);
66
- setExportType(null);
67
150
  }
68
151
  };
69
152
  const handleExportChange = (type) => {
70
153
  if (!type || type === 'none') {
71
154
  setExportType(null);
155
+ setEmailError('');
72
156
  return;
73
157
  }
74
158
  // Check if type is an extraOption
@@ -89,6 +173,23 @@ const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, ex
89
173
  }) }), _jsx("button", { className: `w-full mt-4 px-4 py-2 rounded-md text-white flex items-center justify-center
90
174
  ${getIncludedColumns().length === 0 || isExporting
91
175
  ? 'bg-gray-300 cursor-not-allowed'
92
- : 'bg-primary-500 hover:bg-primary-600'}`, 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" })] }), "Exporting..."] })) : ('Continue') })] }))] }));
176
+ : 'bg-primary-500 hover:bg-primary-600'}`, 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-500" }), _jsx("span", { children: "Send Export via Email" })] }), size: "md", primaryButton: {
177
+ label: 'Send Email',
178
+ onClick: () => {
179
+ handleEmailModalSubmit().catch((error) => {
180
+ console.error('Error in handleEmailModalSubmit:', error);
181
+ });
182
+ },
183
+ loading: isExporting,
184
+ disabled: isExporting || !emailAddress.trim(),
185
+ }, secondaryButton: {
186
+ label: 'Cancel',
187
+ onClick: handleEmailModalClose,
188
+ disabled: isExporting,
189
+ variant: 'outline',
190
+ }, children: _jsxs("div", { className: "space-y-4", children: [_jsx("p", { className: "text-sm text-gray-600", children: "Enter your email address to receive the export file as an attachment." }), _jsxs("div", { children: [_jsxs(Label, { htmlFor: "modal-export-email", className: "mb-2 block", children: ["Email Address ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(TextInput, { id: "modal-export-email", type: "email", placeholder: "your.email@example.com", value: emailAddress, onChange: (e) => {
191
+ setEmailAddress(e.target.value);
192
+ setEmailError('');
193
+ }, color: emailError ? 'failure' : undefined, helperText: emailError || undefined, disabled: isExporting, autoFocus: true })] }), pendingEmailFormat && (_jsx("div", { className: "p-3 bg-blue-50 rounded-md border border-blue-200", children: _jsxs("p", { className: "text-xs text-blue-800", children: [_jsx("strong", { children: "Export Format:" }), " ", pendingEmailFormat.toUpperCase()] }) }))] }) })] }));
93
194
  };
94
195
  export default ExportDropdown;
@@ -1,3 +1,3 @@
1
1
  import { type TableToolbarProps } from './types';
2
- declare const TableToolbar: <T extends Record<string, any>>({ searchable, searchQuery, onSearch, filters, appliedFilters, exportable, columns, data, onTableFilter, addButton, addText, searctInputPlaceHolder, onAdd, handleSearchTable, handleApplyFilters, onClearFilters, showClearFilters, showApplyFilterButton, isNarrow, extraExportOptions, pdfExportOptions, xlsxExportOptions, csvExportOptions, exportAll, fetchData, }: TableToolbarProps<T>) => import("react/jsx-runtime").JSX.Element;
2
+ declare const TableToolbar: <T extends Record<string, any>>({ searchable, searchQuery, onSearch, filters, appliedFilters, exportable, columns, data, onTableFilter, addButton, addText, searctInputPlaceHolder, onAdd, handleSearchTable, handleApplyFilters, onClearFilters, showClearFilters, showApplyFilterButton, isNarrow, extraExportOptions, pdfExportOptions, xlsxExportOptions, csvExportOptions, exportAll, fetchData, enableEmailExport, emailExportApiUrl, onEmailExportSuccess, onEmailExportError, }: TableToolbarProps<T>) => import("react/jsx-runtime").JSX.Element;
3
3
  export default TableToolbar;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { HiPlusSm } from 'react-icons/hi';
3
3
  import { FilterComponent, IconButton } from '../../components';
4
4
  import ExportDropdown from './ExportButton';
5
- const TableToolbar = ({ searchable, searchQuery, onSearch, filters = [], appliedFilters, exportable, columns, data, onTableFilter = () => { }, addButton, addText, searctInputPlaceHolder, onAdd = () => { }, handleSearchTable, handleApplyFilters, onClearFilters = () => { }, showClearFilters = false, showApplyFilterButton = true, isNarrow, extraExportOptions = [], pdfExportOptions, xlsxExportOptions, csvExportOptions, exportAll, fetchData, }) => {
5
+ const TableToolbar = ({ searchable, searchQuery, onSearch, filters = [], appliedFilters, exportable, columns, data, onTableFilter = () => { }, addButton, addText, searctInputPlaceHolder, onAdd = () => { }, handleSearchTable, handleApplyFilters, onClearFilters = () => { }, showClearFilters = false, showApplyFilterButton = true, isNarrow, extraExportOptions = [], pdfExportOptions, xlsxExportOptions, csvExportOptions, exportAll, fetchData, enableEmailExport, emailExportApiUrl, onEmailExportSuccess, onEmailExportError, }) => {
6
6
  const handleFilter = (columnKey, value) => {
7
7
  onTableFilter(columnKey, value ?? '');
8
8
  };
@@ -19,6 +19,6 @@ const TableToolbar = ({ searchable, searchQuery, onSearch, filters = [], applied
19
19
  else {
20
20
  handleFilter(name, value);
21
21
  }
22
- }, handleApplyFilters: handleApplyFilters ?? handleSearchTable, resetFilters: onClearFilters, showSearch: searchable, searchQuery: searchQuery, onSearch: onSearch, isNarrow: isNarrow, children: _jsxs("div", { className: isNarrow ? 'flex flex-col items-stretch space-y-2 pt-5' : 'flex items-center flex-wrap gap-2', children: [exportable && (_jsx("div", { className: isNarrow ? 'w-full' : 'flex-grow', children: _jsx(ExportDropdown, { containerClassName: "w-full", columns: columns, data: data, buttonClassName: "h-10", extraOptions: extraExportOptions, pdfOptions: pdfExportOptions, xlsxOptions: xlsxExportOptions, csvOptions: csvExportOptions, exportAll: exportAll, fetchData: fetchData }) })), addButton && (_jsx("div", { className: isNarrow ? 'w-full' : 'flex-grow', children: typeof addButton === 'boolean' ? (_jsx(IconButton, { onClick: onAdd, icon: HiPlusSm, label: "add-new-entry", className: "items-center w-full text-nowrap", children: addText ?? 'Add New Entry' })) : (addButton) }))] }) }));
22
+ }, handleApplyFilters: handleApplyFilters ?? handleSearchTable, resetFilters: onClearFilters, showSearch: searchable, searchQuery: searchQuery, onSearch: onSearch, isNarrow: isNarrow, children: _jsxs("div", { className: isNarrow ? 'flex flex-col items-stretch space-y-2 pt-5' : 'flex items-center flex-wrap gap-2', children: [exportable && (_jsx("div", { className: isNarrow ? 'w-full' : 'flex-grow', children: _jsx(ExportDropdown, { containerClassName: "w-full", columns: columns, data: data, buttonClassName: "h-10", extraOptions: extraExportOptions, pdfOptions: pdfExportOptions, xlsxOptions: xlsxExportOptions, csvOptions: csvExportOptions, exportAll: exportAll, fetchData: fetchData, enableEmailExport: enableEmailExport, emailExportApiUrl: emailExportApiUrl, onEmailExportSuccess: onEmailExportSuccess, onEmailExportError: onEmailExportError }) })), addButton && (_jsx("div", { className: isNarrow ? 'w-full' : 'flex-grow', children: typeof addButton === 'boolean' ? (_jsx(IconButton, { onClick: onAdd, icon: HiPlusSm, label: "add-new-entry", className: "items-center w-full text-nowrap", children: addText ?? 'Add New Entry' })) : (addButton) }))] }) }));
23
23
  };
24
24
  export default TableToolbar;
@@ -39,4 +39,19 @@ interface CsvExportOptions {
39
39
  export declare const exportToCsv: <T extends Record<string, any>>(data: T[], columns: MRT_ColumnDef<T>[], options?: CsvExportOptions) => void;
40
40
  export declare const exportToXlsx: <T extends Record<string, any>>(data: T[], columns: MRT_ColumnDef<T>[], options?: XlsxExportOptions) => void;
41
41
  export declare const exportToPdf: <T extends Record<string, any>>(data: T[], columns: MRT_ColumnDef<T>[], options?: PdfExportOptions) => Promise<void>;
42
+ /**
43
+ * Sends an export file to the user via email
44
+ * @param email - The recipient email address
45
+ * @param file - Base64 encoded file string
46
+ * @param baseUrl - The API base URL (optional, defaults to /api/v1)
47
+ */
48
+ export declare const sendExportEmail: (email: string, file: string, baseUrl?: string) => Promise<string>;
49
+ /**
50
+ * Generates export file as base64 string for email sending
51
+ */
52
+ export declare const generateExportAsBase64: <T extends Record<string, any>>(data: T[], columns: MRT_ColumnDef<T>[], format: "csv" | "xlsx" | "pdf", options?: {
53
+ pdfOptions?: PdfExportOptions;
54
+ xlsxOptions?: XlsxExportOptions;
55
+ csvOptions?: CsvExportOptions;
56
+ }) => Promise<string>;
42
57
  export {};
@@ -780,3 +780,68 @@ export const exportToPdf = async (data, columns, options = {}) => {
780
780
  const filename = `${title.toLowerCase().replace(/\s+/g, '_')}_${timestamp}.pdf`;
781
781
  doc.save(filename);
782
782
  };
783
+ /**
784
+ * Sends an export file to the user via email
785
+ * @param email - The recipient email address
786
+ * @param file - Base64 encoded file string
787
+ * @param baseUrl - The API base URL (optional, defaults to /api/v1)
788
+ */
789
+ export const sendExportEmail = async (email, file, baseUrl = '/api/v1') => {
790
+ const response = await fetch(`${baseUrl}/exports/send-email?email=${encodeURIComponent(email)}`, {
791
+ method: 'POST',
792
+ headers: {
793
+ 'Content-Type': 'application/json',
794
+ },
795
+ body: JSON.stringify({ file }),
796
+ });
797
+ if (!response.ok) {
798
+ throw new Error(`Failed to send email: ${response.statusText}`);
799
+ }
800
+ return response.text();
801
+ };
802
+ /**
803
+ * Generates export file as base64 string for email sending
804
+ */
805
+ export const generateExportAsBase64 = async (data, columns, format, options) => {
806
+ if (format === 'csv') {
807
+ const visibleColumns = columns.filter((col) => col.accessorKey !== 'action');
808
+ const headers = visibleColumns.map((col) => String(col.header ?? col.accessorKey));
809
+ const rows = data.map((row) => visibleColumns.map((col) => {
810
+ const key = col.accessorKey;
811
+ const value = row[key];
812
+ return value !== null && value !== undefined ? String(value) : '';
813
+ }));
814
+ const csvContent = Papa.unparse({ fields: headers, data: rows });
815
+ return btoa(csvContent);
816
+ }
817
+ if (format === 'xlsx') {
818
+ const visibleColumns = columns.filter((col) => col.accessorKey !== 'action');
819
+ const headers = visibleColumns.map((col) => String(col.header ?? col.accessorKey));
820
+ const rows = data.map((row) => visibleColumns.map((col) => {
821
+ const key = col.accessorKey;
822
+ return row[key] ?? '';
823
+ }));
824
+ const worksheet = XLSX.utils.aoa_to_sheet([headers, ...rows]);
825
+ const workbook = XLSX.utils.book_new();
826
+ XLSX.utils.book_append_sheet(workbook, worksheet, options?.xlsxOptions?.sheetName || 'Data');
827
+ const xlsxData = XLSX.write(workbook, { bookType: 'xlsx', type: 'base64' });
828
+ return xlsxData;
829
+ }
830
+ if (format === 'pdf') {
831
+ const doc = new jsPDF();
832
+ const visibleColumns = columns.filter((col) => col.accessorKey !== 'action');
833
+ const headers = visibleColumns.map((col) => String(col.header ?? col.accessorKey));
834
+ const rows = data.map((row) => visibleColumns.map((col) => {
835
+ const key = col.accessorKey;
836
+ const value = row[key];
837
+ return value !== null && value !== undefined ? String(value) : '';
838
+ }));
839
+ doc.autoTable({
840
+ head: [headers],
841
+ body: rows,
842
+ startY: 20,
843
+ });
844
+ return doc.output('datauristring').split(',')[1];
845
+ }
846
+ throw new Error(`Unsupported format: ${format}`);
847
+ };
@@ -1,5 +1,5 @@
1
1
  import { type PagamioMantineTableProps } from './types';
2
2
  declare const PagamioTable: <T extends {
3
3
  id: string | number;
4
- }>({ columns, data, isLoading, pagination, sorting, filtering, search, exportable, addButton, addText, onAdd, onClearFilters, handleSearchTable, showClearFilters, showApplyFilterButton, searctInputPlaceHolder, expandable, extraExportOptions, pdfExportOptions, xlsxExportOptions, csvExportOptions, renderExpandableComponent, onRowClick, rowClassName, ...props }: PagamioMantineTableProps<T>) => import("react/jsx-runtime").JSX.Element;
4
+ }>({ columns, data, isLoading, pagination, sorting, filtering, search, exportable, addButton, addText, onAdd, onClearFilters, handleSearchTable, showClearFilters, showApplyFilterButton, searctInputPlaceHolder, expandable, extraExportOptions, pdfExportOptions, xlsxExportOptions, csvExportOptions, renderExpandableComponent, onRowClick, rowClassName, enableEmailExport, emailExportApiUrl, onEmailExportSuccess, onEmailExportError, ...props }: PagamioMantineTableProps<T>) => import("react/jsx-runtime").JSX.Element;
5
5
  export default PagamioTable;
@@ -2,10 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMediaQueries } from '../../shared';
3
3
  import CoreTable from './MantineCoreTable';
4
4
  import TableToolbar from './TableToolbar';
5
- const PagamioTable = ({ columns, data, isLoading, pagination, sorting, filtering, search, exportable = false, addButton = false, addText, onAdd, onClearFilters, handleSearchTable, showClearFilters = false, showApplyFilterButton = true, searctInputPlaceHolder, expandable = false, extraExportOptions, pdfExportOptions, xlsxExportOptions, csvExportOptions, renderExpandableComponent, onRowClick, rowClassName, ...props }) => {
5
+ const PagamioTable = ({ columns, data, isLoading, pagination, sorting, filtering, search, exportable = false, addButton = false, addText, onAdd, onClearFilters, handleSearchTable, showClearFilters = false, showApplyFilterButton = true, searctInputPlaceHolder, expandable = false, extraExportOptions, pdfExportOptions, xlsxExportOptions, csvExportOptions, renderExpandableComponent, onRowClick, rowClassName, enableEmailExport, emailExportApiUrl, onEmailExportSuccess, onEmailExportError, ...props }) => {
6
6
  const hasPagination = pagination?.enabled;
7
7
  const { isSm, isXs } = useMediaQueries();
8
8
  const isNarrow = isXs || isSm;
9
- return (_jsxs("div", { className: "mt-2", children: [_jsx(TableToolbar, { searchable: search?.enabled ?? false, searchQuery: search?.searchQuery ?? '', onSearch: search?.onSearch ?? (() => { }), filters: filtering?.filters ?? [], appliedFilters: filtering?.appliedFilters ?? {}, onTableFilter: filtering?.onTableFilter ?? (() => { }), exportable: exportable, addButton: addButton, addText: addText, onAdd: onAdd ?? (() => { }), handleSearchTable: handleSearchTable, onClearFilters: onClearFilters, showClearFilters: showClearFilters, showApplyFilterButton: showApplyFilterButton, searctInputPlaceHolder: searctInputPlaceHolder, data: data, columns: columns, isNarrow: isNarrow, extraExportOptions: extraExportOptions, pdfExportOptions: pdfExportOptions, xlsxExportOptions: xlsxExportOptions, csvExportOptions: csvExportOptions, exportAll: props.exportAll, fetchData: props.fetchData }), _jsx("div", { className: "border border-gray-200 rounded-md overflow-hidden", children: _jsx(CoreTable, { columns: columns, data: data, isLoading: isLoading, onSort: sorting?.onSort, sortConfig: sorting?.sortConfig ?? null, multiSorting: sorting?.multiSorting || false, sortable: props.sortable, setSortBy: sorting?.setSortBy, setSortDir: sorting?.setSortDir, enablePagination: hasPagination, pagination: pagination, expandable: expandable, onRowClick: onRowClick, rowClassName: rowClassName, renderExpandableComponent: renderExpandableComponent, ...props }) })] }));
9
+ return (_jsxs("div", { className: "mt-2", children: [_jsx(TableToolbar, { searchable: search?.enabled ?? false, searchQuery: search?.searchQuery ?? '', onSearch: search?.onSearch ?? (() => { }), filters: filtering?.filters ?? [], appliedFilters: filtering?.appliedFilters ?? {}, onTableFilter: filtering?.onTableFilter ?? (() => { }), exportable: exportable, addButton: addButton, addText: addText, onAdd: onAdd ?? (() => { }), handleSearchTable: handleSearchTable, onClearFilters: onClearFilters, showClearFilters: showClearFilters, showApplyFilterButton: showApplyFilterButton, searctInputPlaceHolder: searctInputPlaceHolder, data: data, columns: columns, isNarrow: isNarrow, extraExportOptions: extraExportOptions, pdfExportOptions: pdfExportOptions, xlsxExportOptions: xlsxExportOptions, csvExportOptions: csvExportOptions, exportAll: props.exportAll, fetchData: props.fetchData, enableEmailExport: enableEmailExport, emailExportApiUrl: emailExportApiUrl, onEmailExportSuccess: onEmailExportSuccess, onEmailExportError: onEmailExportError }), _jsx("div", { className: "border border-gray-200 rounded-md overflow-hidden", children: _jsx(CoreTable, { columns: columns, data: data, isLoading: isLoading, onSort: sorting?.onSort, sortConfig: sorting?.sortConfig ?? null, multiSorting: sorting?.multiSorting || false, sortable: props.sortable, setSortBy: sorting?.setSortBy, setSortDir: sorting?.setSortDir, enablePagination: hasPagination, pagination: pagination, expandable: expandable, onRowClick: onRowClick, rowClassName: rowClassName, renderExpandableComponent: renderExpandableComponent, ...props }) })] }));
10
10
  };
11
11
  export default PagamioTable;
@@ -114,6 +114,10 @@ export type PagamioMantineTableProps<T extends Record<string, any>> = {
114
114
  row: MRT_Row<T>;
115
115
  table: MRT_TableInstance<T>;
116
116
  }) => React.ReactNode;
117
+ enableEmailExport?: boolean;
118
+ emailExportApiUrl?: string;
119
+ onEmailExportSuccess?: (message: string) => void;
120
+ onEmailExportError?: (error: Error) => void;
117
121
  };
118
122
  export type TableToolbarProps<T extends Record<string, any>> = {
119
123
  searchable: boolean;
@@ -141,6 +145,10 @@ export type TableToolbarProps<T extends Record<string, any>> = {
141
145
  csvExportOptions?: CsvExportOptions;
142
146
  exportAll?: boolean;
143
147
  fetchData?: (params?: Record<string, string>) => Promise<T[]>;
148
+ enableEmailExport?: boolean;
149
+ emailExportApiUrl?: string;
150
+ onEmailExportSuccess?: (message: string) => void;
151
+ onEmailExportError?: (error: Error) => void;
144
152
  };
145
153
  export interface BaseEntity {
146
154
  id: number | string;
@@ -1,4 +1,5 @@
1
1
  export { default } from './data-table/index';
2
2
  export * from './data-table/types';
3
3
  export * from './data-table/pdfExportUtils';
4
+ export { sendExportEmail, generateExportAsBase64 } from './data-table/exportUtils';
4
5
  export * from './utils';
@@ -1,4 +1,5 @@
1
1
  export { default } from './data-table/index';
2
2
  export * from './data-table/types';
3
3
  export * from './data-table/pdfExportUtils';
4
+ export { sendExportEmail, generateExportAsBase64 } from './data-table/exportUtils';
4
5
  export * from './utils';
package/lib/styles.css CHANGED
@@ -2211,6 +2211,10 @@ input[type="range"]::-ms-fill-lower {
2211
2211
  .border-dashed {
2212
2212
  border-style: dashed;
2213
2213
  }
2214
+ .border-blue-200 {
2215
+ --tw-border-opacity: 1;
2216
+ border-color: rgb(195 221 253 / var(--tw-border-opacity, 1));
2217
+ }
2214
2218
  .border-blue-500 {
2215
2219
  --tw-border-opacity: 1;
2216
2220
  border-color: rgb(63 131 248 / var(--tw-border-opacity, 1));
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.206",
4
+ "version": "0.8.208",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "provenance": false