@pagamio/frontend-commons-lib 0.8.207 → 0.8.209
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/lib/components/layout/Sidebar.js +7 -2
- package/lib/context/SidebarContext.d.ts +5 -1
- package/lib/context/SidebarContext.js +3 -2
- package/lib/pagamio-table/data-table/ExportButton.js +3 -12
- package/lib/pagamio-table/data-table/exportUtils.d.ts +8 -6
- package/lib/pagamio-table/data-table/exportUtils.js +48 -14
- package/lib/pagamio-table/index.d.ts +1 -1
- package/lib/pagamio-table/index.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
/**
|
|
@@ -4,7 +4,7 @@ import { Checkbox, Label, TextInput } from 'flowbite-react';
|
|
|
4
4
|
import { HiMail } from 'react-icons/hi';
|
|
5
5
|
import { useEffect, useRef, useState } from 'react';
|
|
6
6
|
import { Modal, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../components';
|
|
7
|
-
import { exportToCsv, exportToPdf, exportToXlsx,
|
|
7
|
+
import { exportToCsv, exportToPdf, exportToXlsx, generateExportAsFile, sendExportEmail } from './exportUtils';
|
|
8
8
|
const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, extraOptions = [], pdfOptions, xlsxOptions, csvOptions, exportAll = false, fetchData, enableEmailExport = false, emailExportApiUrl, onEmailExportSuccess, onEmailExportError, }) => {
|
|
9
9
|
const [exportType, setExportType] = useState(null);
|
|
10
10
|
const [excludedColumns, setExcludedColumns] = useState(['action']);
|
|
@@ -78,27 +78,18 @@ const ExportDropdown = ({ data, columns, containerClassName, buttonClassName, ex
|
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
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
81
|
if (!emailAddress.trim() || !validateEmail(emailAddress)) {
|
|
86
82
|
setEmailError(emailAddress.trim() ? 'Please enter a valid email address' : 'Email address is required');
|
|
87
83
|
return;
|
|
88
84
|
}
|
|
89
85
|
if (!pendingExportData || !pendingEmailFormat) {
|
|
90
|
-
console.log('Missing pendingExportData or pendingEmailFormat');
|
|
91
86
|
return;
|
|
92
87
|
}
|
|
93
88
|
setEmailError('');
|
|
94
89
|
setIsExporting(true);
|
|
95
90
|
try {
|
|
96
|
-
|
|
97
|
-
const
|
|
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);
|
|
91
|
+
const exportFile = await generateExportAsFile(pendingExportData.data, pendingExportData.columns, pendingEmailFormat, { pdfOptions, xlsxOptions, csvOptions });
|
|
92
|
+
const result = await sendExportEmail(emailAddress, exportFile, emailExportApiUrl);
|
|
102
93
|
onEmailExportSuccess?.(result || 'Export sent successfully to your email');
|
|
103
94
|
setEmailAddress('');
|
|
104
95
|
setShowEmailModal(false);
|
|
@@ -40,18 +40,20 @@ export declare const exportToCsv: <T extends Record<string, any>>(data: T[], col
|
|
|
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
42
|
/**
|
|
43
|
-
* Sends an export file to the user via email
|
|
43
|
+
* Sends an export file to the user via email using multipart/form-data
|
|
44
|
+
* Automatically includes Authorization header from cookies if available
|
|
44
45
|
* @param email - The recipient email address
|
|
45
|
-
* @param file -
|
|
46
|
+
* @param file - The File object to send
|
|
46
47
|
* @param baseUrl - The API base URL (optional, defaults to /api/v1)
|
|
48
|
+
* @param additionalHeaders - Optional additional headers to merge with auth headers
|
|
47
49
|
*/
|
|
48
|
-
export declare const sendExportEmail: (email: string, file:
|
|
50
|
+
export declare const sendExportEmail: (email: string, file: File, baseUrl?: string, additionalHeaders?: HeadersInit) => Promise<string>;
|
|
49
51
|
/**
|
|
50
|
-
* Generates export
|
|
52
|
+
* Generates export as a File object for email sending
|
|
51
53
|
*/
|
|
52
|
-
export declare const
|
|
54
|
+
export declare const generateExportAsFile: <T extends Record<string, any>>(data: T[], columns: MRT_ColumnDef<T>[], format: "csv" | "xlsx" | "pdf", options?: {
|
|
53
55
|
pdfOptions?: PdfExportOptions;
|
|
54
56
|
xlsxOptions?: XlsxExportOptions;
|
|
55
57
|
csvOptions?: CsvExportOptions;
|
|
56
|
-
}) => Promise<
|
|
58
|
+
}) => Promise<File>;
|
|
57
59
|
export {};
|
|
@@ -781,28 +781,55 @@ export const exportToPdf = async (data, columns, options = {}) => {
|
|
|
781
781
|
doc.save(filename);
|
|
782
782
|
};
|
|
783
783
|
/**
|
|
784
|
-
*
|
|
784
|
+
* Helper to get authentication headers from browser cookies
|
|
785
|
+
* This reads the accessToken cookie that's set by TokenManager
|
|
786
|
+
*/
|
|
787
|
+
const getAuthHeaders = () => {
|
|
788
|
+
if (typeof document === 'undefined')
|
|
789
|
+
return {};
|
|
790
|
+
// Read accessToken from cookies (matches TokenManager cookie name)
|
|
791
|
+
const cookies = document.cookie.split(';');
|
|
792
|
+
const accessTokenCookie = cookies.find((cookie) => cookie.trim().startsWith('accessToken='));
|
|
793
|
+
if (!accessTokenCookie)
|
|
794
|
+
return {};
|
|
795
|
+
const token = accessTokenCookie.split('=')[1];
|
|
796
|
+
return {
|
|
797
|
+
Authorization: `Bearer ${token}`,
|
|
798
|
+
};
|
|
799
|
+
};
|
|
800
|
+
/**
|
|
801
|
+
* Sends an export file to the user via email using multipart/form-data
|
|
802
|
+
* Automatically includes Authorization header from cookies if available
|
|
785
803
|
* @param email - The recipient email address
|
|
786
|
-
* @param file -
|
|
804
|
+
* @param file - The File object to send
|
|
787
805
|
* @param baseUrl - The API base URL (optional, defaults to /api/v1)
|
|
806
|
+
* @param additionalHeaders - Optional additional headers to merge with auth headers
|
|
788
807
|
*/
|
|
789
|
-
export const sendExportEmail = async (email, file, baseUrl = '/api/v1') => {
|
|
808
|
+
export const sendExportEmail = async (email, file, baseUrl = '/api/v1', additionalHeaders) => {
|
|
809
|
+
const formData = new FormData();
|
|
810
|
+
formData.append('file', file);
|
|
811
|
+
// Build headers: start with auth, then merge additional headers if provided
|
|
812
|
+
const authHeaders = getAuthHeaders();
|
|
813
|
+
const headers = additionalHeaders ? { ...authHeaders, ...additionalHeaders } : authHeaders;
|
|
814
|
+
// Note: Do NOT set Content-Type here - browser will set it automatically with boundary for multipart/form-data
|
|
790
815
|
const response = await fetch(`${baseUrl}/exports/send-email?email=${encodeURIComponent(email)}`, {
|
|
791
816
|
method: 'POST',
|
|
792
|
-
headers
|
|
793
|
-
|
|
794
|
-
},
|
|
795
|
-
body: JSON.stringify({ file }),
|
|
817
|
+
headers,
|
|
818
|
+
body: formData,
|
|
796
819
|
});
|
|
797
820
|
if (!response.ok) {
|
|
798
|
-
|
|
821
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
822
|
+
throw new Error(`Failed to send email: ${errorText}`);
|
|
799
823
|
}
|
|
800
824
|
return response.text();
|
|
801
825
|
};
|
|
802
826
|
/**
|
|
803
|
-
* Generates export
|
|
827
|
+
* Generates export as a File object for email sending
|
|
804
828
|
*/
|
|
805
|
-
export const
|
|
829
|
+
export const generateExportAsFile = async (data, columns, format, options) => {
|
|
830
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
831
|
+
const title = options?.pdfOptions?.title || options?.xlsxOptions?.title || 'export';
|
|
832
|
+
const baseFilename = `${title.toLowerCase().replaceAll(/\s+/g, '_')}_${timestamp}`;
|
|
806
833
|
if (format === 'csv') {
|
|
807
834
|
const visibleColumns = columns.filter((col) => col.accessorKey !== 'action');
|
|
808
835
|
const headers = visibleColumns.map((col) => String(col.header ?? col.accessorKey));
|
|
@@ -812,7 +839,8 @@ export const generateExportAsBase64 = async (data, columns, format, options) =>
|
|
|
812
839
|
return value !== null && value !== undefined ? String(value) : '';
|
|
813
840
|
}));
|
|
814
841
|
const csvContent = Papa.unparse({ fields: headers, data: rows });
|
|
815
|
-
|
|
842
|
+
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
843
|
+
return new File([blob], `${baseFilename}.csv`, { type: 'text/csv' });
|
|
816
844
|
}
|
|
817
845
|
if (format === 'xlsx') {
|
|
818
846
|
const visibleColumns = columns.filter((col) => col.accessorKey !== 'action');
|
|
@@ -824,8 +852,13 @@ export const generateExportAsBase64 = async (data, columns, format, options) =>
|
|
|
824
852
|
const worksheet = XLSX.utils.aoa_to_sheet([headers, ...rows]);
|
|
825
853
|
const workbook = XLSX.utils.book_new();
|
|
826
854
|
XLSX.utils.book_append_sheet(workbook, worksheet, options?.xlsxOptions?.sheetName || 'Data');
|
|
827
|
-
const
|
|
828
|
-
|
|
855
|
+
const xlsxBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
|
|
856
|
+
const blob = new Blob([xlsxBuffer], {
|
|
857
|
+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
858
|
+
});
|
|
859
|
+
return new File([blob], `${baseFilename}.xlsx`, {
|
|
860
|
+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
861
|
+
});
|
|
829
862
|
}
|
|
830
863
|
if (format === 'pdf') {
|
|
831
864
|
const doc = new jsPDF();
|
|
@@ -841,7 +874,8 @@ export const generateExportAsBase64 = async (data, columns, format, options) =>
|
|
|
841
874
|
body: rows,
|
|
842
875
|
startY: 20,
|
|
843
876
|
});
|
|
844
|
-
|
|
877
|
+
const pdfBlob = doc.output('blob');
|
|
878
|
+
return new File([pdfBlob], `${baseFilename}.pdf`, { type: 'application/pdf' });
|
|
845
879
|
}
|
|
846
880
|
throw new Error(`Unsupported format: ${format}`);
|
|
847
881
|
};
|
|
@@ -1,5 +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,
|
|
4
|
+
export { sendExportEmail, generateExportAsFile } from './data-table/exportUtils';
|
|
5
5
|
export * from './utils';
|
|
@@ -1,5 +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,
|
|
4
|
+
export { sendExportEmail, generateExportAsFile } from './data-table/exportUtils';
|
|
5
5
|
export * from './utils';
|
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.209",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"provenance": false
|