diff --git a/src/pages/wallet/wallet-statement/blocks/ShowDetailDialog.tsx b/src/pages/wallet/wallet-statement/blocks/ShowDetailDialog.tsx index 49dc5de..1a00a99 100644 --- a/src/pages/wallet/wallet-statement/blocks/ShowDetailDialog.tsx +++ b/src/pages/wallet/wallet-statement/blocks/ShowDetailDialog.tsx @@ -16,8 +16,14 @@ import { format } from 'date-fns'; import FilterSection from './FilterSection'; import * as XLSX from 'xlsx'; import jsPDF from 'jspdf'; -import autoTable from 'jspdf-autotable'; import ExcelJS from 'exceljs'; +let autoTable: any; +try { + // Method 1: Default import + autoTable = require('jspdf-autotable').default; +} catch (e1) { + console.error('Failed to import jspdf-autotable:', e1); +} interface ContextProps { doExportData: (sorting: any, filter: any) => Promise; @@ -227,6 +233,15 @@ const ShowDetailWalletDialog = () => { })); }; + const isImageAccessible = async (url: any) => { + try { + const response = await fetch(url, { method: 'HEAD' }); + return response.ok; + } catch { + return false; + } + }; + const doExportExcel = async () => { try { const allData = await getAllTransactionData(); @@ -235,11 +250,9 @@ const ShowDetailWalletDialog = () => { const wb = XLSX.utils.book_new(); const ws = XLSX.utils.json_to_sheet([]); - // Membuat space untuk header image (sekitar 10 baris) + // Space untuk header image const imageSpaceRows = 10; - const companyHeader = [ - // Baris kosong untuk space gambar header ...Array(imageSpaceRows).fill(['']), [''], ['Wallet Statement Details'], @@ -250,17 +263,8 @@ const ShowDetailWalletDialog = () => { const columnHeaders = [ [ - 'Transaction Code', - 'MSISDN Reffer', - 'Transaction Type', - 'Type', - 'Pre Amount', - 'Amount', - 'Post Amount', - 'Category', - 'Date', - 'Purpose', - 'Notes' + 'Transaction Code', 'MSISDN Reffer', 'Transaction Type', 'Type', + 'Pre Amount', 'Amount', 'Post Amount', 'Category', 'Date', 'Purpose', 'Notes' ] ]; @@ -274,21 +278,12 @@ const ShowDetailWalletDialog = () => { // Set column widths const colWidths = [ - { wch: 20 }, - { wch: 15 }, - { wch: 35 }, - { wch: 10 }, - { wch: 12 }, - { wch: 12 }, - { wch: 12 }, - { wch: 15 }, - { wch: 20 }, - { wch: 25 }, - { wch: 25 } + { wch: 20 }, { wch: 15 }, { wch: 35 }, { wch: 10 }, { wch: 12 }, + { wch: 12 }, { wch: 12 }, { wch: 15 }, { wch: 20 }, { wch: 25 }, { wch: 25 } ]; ws['!cols'] = colWidths; - // Style untuk title "Wallet Statement Details" + // Apply styles const titleRowIndex = imageSpaceRows + 1; const titleCell = XLSX.utils.encode_cell({ r: titleRowIndex, c: 0 }); if (ws[titleCell]) { @@ -298,33 +293,19 @@ const ShowDetailWalletDialog = () => { }; } - // Style untuk export date dan wallet ID - for (let i = titleRowIndex + 1; i < titleRowIndex + 3; i++) { - const cellRef = XLSX.utils.encode_cell({ r: i, c: 0 }); - if (ws[cellRef]) { - ws[cellRef].s = { - font: { sz: 11 }, - alignment: { horizontal: 'left' } - }; - } - } - - // Merge cells untuk header area + // Merge cells if (!ws['!merges']) ws['!merges'] = []; - - // Merge untuk space gambar header for (let i = 0; i < imageSpaceRows; i++) { const range = XLSX.utils.encode_range({ s: { r: i, c: 0 }, e: { r: i, c: 10 } }); ws['!merges'].push(XLSX.utils.decode_range(range)); } - // Merge untuk title dan info for (let i = titleRowIndex; i < titleRowIndex + 4; i++) { const range = XLSX.utils.encode_range({ s: { r: i, c: 0 }, e: { r: i, c: 10 } }); ws['!merges'].push(XLSX.utils.decode_range(range)); } - // Style untuk column headers + // Style column headers const headerRowIndex = titleRowIndex + 4; for (let col = 0; col < columnHeaders[0].length; col++) { const cellRef = XLSX.utils.encode_cell({ r: headerRowIndex, c: col }); @@ -343,14 +324,14 @@ const ShowDetailWalletDialog = () => { } } - // Style untuk data rows - const dataStartRow = headerRowIndex + 1; + // Style data rows + const dataStartRow = headerRowIndex + 1; for (let row = dataStartRow; row < dataStartRow + formattedData.length; row++) { for (let col = 0; col < columnHeaders[0].length; col++) { const cellRef = XLSX.utils.encode_cell({ r: row, c: col }); if (ws[cellRef]) { ws[cellRef].s = { - alignment: { horizontal: 'left' }, // Semua kolom rata kiri + alignment: { horizontal: 'left' }, border: { top: { style: 'thin', color: { rgb: 'D9D9D9' } }, bottom: { style: 'thin', color: { rgb: 'D9D9D9' } }, @@ -362,121 +343,165 @@ const ShowDetailWalletDialog = () => { } } + // Try ExcelJS with better error handling + let useExcelJS = false; try { - // Import ExcelJS untuk menambahkan gambar - const ExcelJS = await import('exceljs'); + // Check if ExcelJS is available + const ExcelJSModule = await import('exceljs'); + const ExcelJS = ExcelJSModule.default || ExcelJSModule; + + if (ExcelJS && ExcelJS.Workbook) { + useExcelJS = true; + + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('Wallet Statement'); - // Buat workbook baru dengan ExcelJS - const workbook = new ExcelJS.Workbook(); - const worksheet = workbook.addWorksheet('Wallet Statement'); + // Transfer data from XLSX to ExcelJS + const range = XLSX.utils.decode_range(ws['!ref'] || 'A1:K1'); + for (let row = range.s.r; row <= range.e.r; row++) { + for (let col = range.s.c; col <= range.e.c; col++) { + const cellAddress = XLSX.utils.encode_cell({ r: row, c: col }); + const cell = ws[cellAddress]; + if (cell && cell.v !== undefined) { + const excelCell = worksheet.getCell(row + 1, col + 1); + excelCell.value = cell.v; - // Tambahkan data dari XLSX worksheet ke ExcelJS worksheet - const range = XLSX.utils.decode_range(ws['!ref'] || 'A1:K1'); - - for (let row = range.s.r; row <= range.e.r; row++) { - for (let col = range.s.c; col <= range.e.c; col++) { - const cellAddress = XLSX.utils.encode_cell({ r: row, c: col }); - const cell = ws[cellAddress]; - if (cell && cell.v !== undefined) { - const excelCell = worksheet.getCell(row + 1, col + 1); - excelCell.value = cell.v; - - // Apply styles - if (cell.s) { - if (cell.s.font) { - excelCell.font = { - bold: cell.s.font.bold || false, - size: cell.s.font.sz || 11, - color: cell.s.font.color ? { argb: cell.s.font.color.rgb } : undefined - }; - } - if (cell.s.fill) { - excelCell.fill = { - type: 'pattern', - pattern: 'solid', - fgColor: { argb: cell.s.fill.fgColor.rgb } - }; - } - if (cell.s.alignment) { - excelCell.alignment = { - horizontal: cell.s.alignment.horizontal || 'left' - }; - } - if (cell.s.border) { - excelCell.border = { - top: cell.s.border.top ? { style: 'thin' } : undefined, - bottom: cell.s.border.bottom ? { style: 'thin' } : undefined, - left: cell.s.border.left ? { style: 'thin' } : undefined, - right: cell.s.border.right ? { style: 'thin' } : undefined - }; + // Apply styles + if (cell.s) { + if (cell.s.font) { + excelCell.font = { + bold: cell.s.font.bold || false, + size: cell.s.font.sz || 11, + color: cell.s.font.color ? { argb: cell.s.font.color.rgb } : undefined + }; + } + if (cell.s.fill) { + excelCell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: cell.s.fill.fgColor.rgb } + }; + } + if (cell.s.alignment) { + excelCell.alignment = { + horizontal: cell.s.alignment.horizontal || 'left' + }; + } + if (cell.s.border) { + excelCell.border = { + top: cell.s.border.top ? { style: 'thin' } : undefined, + bottom: cell.s.border.bottom ? { style: 'thin' } : undefined, + left: cell.s.border.left ? { style: 'thin' } : undefined, + right: cell.s.border.right ? { style: 'thin' } : undefined + }; + } } } } } - } - // Set column widths - colWidths.forEach((width, index) => { - worksheet.getColumn(index + 1).width = width.wch; - }); - - // Merge cells - if (ws['!merges']) { - ws['!merges'].forEach((merge) => { - worksheet.mergeCells(merge.s.r + 1, merge.s.c + 1, merge.e.r + 1, merge.e.c + 1); + // Set column widths + colWidths.forEach((width, index) => { + worksheet.getColumn(index + 1).width = width.wch; }); - } - // Tambahkan gambar header - try { - const response = await fetch('/media/avatars/KopDetailWalletStatement.png'); - if (response.ok) { - const imageBuffer = await response.arrayBuffer(); - - const imageId = workbook.addImage({ - buffer: imageBuffer, - extension: 'png' - }); - - // Posisikan gambar di area header - worksheet.addImage(imageId, { - tl: { col: 1.7, row: 1 }, - ext: { width: 1100, height: 180 } + // Merge cells + if (ws['!merges']) { + ws['!merges'].forEach((merge) => { + worksheet.mergeCells(merge.s.r + 1, merge.s.c + 1, merge.e.r + 1, merge.e.c + 1); }); } - } catch (imageError) { - console.warn('Could not load header image, continuing without it:', imageError); + + // Try to add header image dengan multiple fallback paths + const imagePaths = [ + '/media/avatars/KopDetailWalletStatement.png', + './media/avatars/KopDetailWalletStatement.png', + ]; + + let imageAdded = false; + for (const imagePath of imagePaths) { + try { + if (await isImageAccessible(imagePath)) { + const response = await fetch(imagePath); + if (response.ok) { + const imageBuffer = await response.arrayBuffer(); + const imageId = workbook.addImage({ + buffer: imageBuffer, + extension: 'png' + }); + + worksheet.addImage(imageId, { + tl: { col: 1.7, row: 1 }, + ext: { width: 1100, height: 180 } + }); + imageAdded = true; + break; + } + } + } catch (imageError) { + console.warn(`Failed to load image from ${imagePath}:`, imageError); + continue; + } + } + + if (!imageAdded) { + console.warn('No header image could be loaded from any path'); + } + + // Export file + const fileName = `wallet_statement_${selectedWallet?.ID}_${format(new Date(), 'yyyyMMdd_HHmmss')}.xlsx`; + const buffer = await workbook.xlsx.writeBuffer(); + + const blob = new Blob([buffer], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = fileName; + link.click(); + window.URL.revokeObjectURL(url); } - - // Export file - const fileName = `wallet_statement_${selectedWallet?.ID}_${format(new Date(), 'yyyyMMdd_HHmmss')}.xlsx`; - const buffer = await workbook.xlsx.writeBuffer(); - - // Download file - const blob = new Blob([buffer], { - type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - }); - const url = window.URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.download = fileName; - link.click(); - window.URL.revokeObjectURL(url); } catch (excelJSError) { - console.warn('ExcelJS not available, falling back to XLSX without image:', excelJSError); + console.warn('ExcelJS failed, falling back to XLSX:', excelJSError); + useExcelJS = false; + } + // Fallback to XLSX if ExcelJS failed + if (!useExcelJS) { XLSX.utils.book_append_sheet(wb, ws, 'Wallet Statement'); const fileName = `wallet_statement_${selectedWallet?.ID}_${format(new Date(), 'yyyyMMdd_HHmmss')}.xlsx`; XLSX.writeFile(wb, fileName); } - } catch (error) { + + toast.success('Excel export completed successfully'); + } catch (error: any) { console.error('Error exporting to Excel:', error); - toast.error('Failed to export Excel'); + toast.error('Failed to export Excel: ' + error.message); } }; const doExportPDF = async () => { try { + // Check if autoTable is available + if (!autoTable) { + // Try to load autoTable dynamically + try { + const autoTableModule = await import('jspdf-autotable'); + autoTable = autoTableModule.default || autoTableModule.autoTable || autoTableModule; + } catch (importError) { + console.error('Failed to import jspdf-autotable:', importError); + toast.error('PDF export library not available'); + return; + } + } + + if (typeof autoTable !== 'function') { + console.error('autoTable is not a function, type:', typeof autoTable); + toast.error('PDF export function not properly loaded'); + return; + } + const allData = await getAllTransactionData(); const formattedData = formatTransactionData(allData); @@ -486,92 +511,84 @@ const ShowDetailWalletDialog = () => { format: 'a4' }); - const headerImageUrl = '/media/avatars/KopDetailWalletStatement.png'; + // Try to add header image dengan multiple paths + const imagePaths = [ + '/media/avatars/KopDetailWalletStatement.png', + './media/avatars/KopDetailWalletStatement.png', + ]; + let imageAdded = false; + for (const imagePath of imagePaths) { + try { + if (await isImageAccessible(imagePath)) { + const pageWidth = pdf.internal.pageSize.getWidth(); + const imageWidth = 250; + const imageHeight = 40; + const xPosition = (pageWidth - imageWidth) / 2; + + pdf.addImage(imagePath, 'PNG', xPosition, 10, imageWidth, imageHeight); + imageAdded = true; + break; + } + } catch (imageError) { + console.warn(`Failed to load image from ${imagePath}:`, imageError); + continue; + } + } + + const pageWidth = pdf.internal.pageSize.getWidth(); + let currentY = imageAdded ? 55 : 20; + + // Add header line + pdf.setDrawColor(0, 0, 0); + pdf.setLineWidth(0.5); + pdf.line(20, currentY, pageWidth - 20, currentY); + currentY += 10; + + // Add title + pdf.setFontSize(16); + pdf.setFont('helvetica', 'bold'); + pdf.text('Wallet Statement Details', pageWidth / 2, currentY, { align: 'center' }); + currentY += 10; + + // Add metadata + pdf.setFontSize(10); + pdf.setFont('helvetica', 'normal'); + pdf.text(`Export Date: ${format(new Date(), 'dd MMM yyyy HH:mm:ss')}`, 20, currentY); + pdf.text(`Wallet ID: ${selectedWallet?.ID || '-'}`, 20, currentY + 6); + currentY += 20; + + // Prepare table data + const tableHeaders = [ + 'Transaction Code', 'MSISDN Reffer', 'Transaction Type', 'Type', + 'Pre Amount', 'Amount', 'Post Amount', 'Category', 'Date', 'Purpose', 'Notes' + ]; + + const tableData = formattedData.map((item) => [ + item['Transaction Code'], item['MSISDN Reffer'], item['Transaction Type'], item['Type'], + item['Pre Amount'], item['Amount'], item['Post Amount'], item['Category'], + item['Date'], item['Purpose'], item['Notes'] + ]); + + const columnStyles = { + 0: { cellWidth: 25, halign: 'left' }, + 1: { cellWidth: 20, halign: 'left' }, + 2: { cellWidth: 25, halign: 'left' }, + 3: { cellWidth: 15, halign: 'center' }, + 4: { cellWidth: 18, halign: 'right' }, + 5: { cellWidth: 18, halign: 'right' }, + 6: { cellWidth: 18, halign: 'right' }, + 7: { cellWidth: 20, halign: 'left' }, + 8: { cellWidth: 30, halign: 'left' }, + 9: { cellWidth: 25, halign: 'left' }, + 10: { cellWidth: 25, halign: 'left' } + }; + + const totalTableWidth = Object.values(columnStyles).reduce((sum, col) => sum + col.cellWidth, 0); + const leftMargin = (pageWidth - totalTableWidth) / 2; + + // Use autoTable with proper error handling try { - const pageWidth = pdf.internal.pageSize.getWidth(); - const imageWidth = 250; - const imageHeight = 40; - const xPosition = (pageWidth - imageWidth) / 2; - - pdf.addImage(headerImageUrl, 'PNG', xPosition, 10, imageWidth, imageHeight); - - let currentY = 55; - - pdf.setDrawColor(0, 0, 0); - pdf.setLineWidth(0.5); - pdf.line(20, currentY, pageWidth - 20, currentY); - - currentY += 10; - - pdf.setFontSize(16); - pdf.setFont('helvetica', 'bold'); - pdf.text('Wallet Statement Details', pageWidth / 2, currentY, { align: 'center' }); - - currentY += 10; - - pdf.setFontSize(10); - pdf.setFont('helvetica', 'normal'); - pdf.text(`Export Date: ${format(new Date(), 'dd MMM yyyy HH:mm:ss')}`, 20, currentY); - pdf.text(`Wallet ID: ${selectedWallet?.ID || '-'}`, 20, currentY + 6); - - currentY += 20; - - const tableHeaders = [ - 'Transaction Code', - 'MSISDN Reffer', - 'Transaction Type', - 'Type', - 'Pre Amount', - 'Amount', - 'Post Amount', - 'Category', - 'Date', - 'Purpose', - 'Notes' - ]; - - const tableData = formattedData.map((item) => [ - item['Transaction Code'], - item['MSISDN Reffer'], - item['Transaction Type'], - item['Type'], - item['Pre Amount'], - item['Amount'], - item['Post Amount'], - item['Category'], - item['Date'], - item['Purpose'], - item['Notes'] - ]); - - const calculateLeftMargin = ( - columnStyles: Record, - pageWidth: number - ): number => { - const totalTableWidth = Object.values(columnStyles).reduce( - (sum, col) => sum + col.cellWidth, - 0 - ); - - return (pageWidth - totalTableWidth) / 2; - }; - const columnStyles = { - 0: { cellWidth: 25, halign: 'left' as const }, - 1: { cellWidth: 20, halign: 'left' as const }, - 2: { cellWidth: 25, halign: 'left' as const }, - 3: { cellWidth: 15, halign: 'center' as const }, - 4: { cellWidth: 18, halign: 'right' as const }, - 5: { cellWidth: 18, halign: 'right' as const }, - 6: { cellWidth: 18, halign: 'right' as const }, - 7: { cellWidth: 20, halign: 'left' as const }, - 8: { cellWidth: 30, halign: 'left' as const }, - 9: { cellWidth: 25, halign: 'left' as const }, - 10: { cellWidth: 25, halign: 'left' as const } - }; - - const leftMargin = calculateLeftMargin(columnStyles, pageWidth); - autoTable(pdf, { head: [tableHeaders], body: tableData, @@ -598,15 +615,19 @@ const ShowDetailWalletDialog = () => { } } }); - - const fileName = `wallet_statement_${selectedWallet?.ID}_${format(new Date(), 'yyyyMMdd_HHmmss')}.pdf`; - pdf.save(fileName); - } catch (imageError) { - console.error('Error loading header image:', imageError); + } catch (autoTableError) { + console.error('AutoTable error:', autoTableError); + toast.error('Failed to generate PDF table'); + return; } - } catch (error) { + + const fileName = `wallet_statement_${selectedWallet?.ID}_${format(new Date(), 'yyyyMMdd_HHmmss')}.pdf`; + pdf.save(fileName); + + toast.success('PDF export completed successfully'); + } catch (error: any) { console.error('Error exporting to PDF:', error); - toast.error('Failed to export PDF'); + toast.error('Failed to export PDF: ' + error.message); } };