From ad0af0e3325dc43e1e11c0d7b81ee78b75053009 Mon Sep 17 00:00:00 2001 From: Raja Oktafrianto Date: Fri, 6 Jun 2025 15:22:54 +0700 Subject: [PATCH] update --- .../blocks/ShowDetailDialog.tsx | 480 +++++++++--------- 1 file changed, 231 insertions(+), 249 deletions(-) diff --git a/src/pages/wallet/wallet-statement/blocks/ShowDetailDialog.tsx b/src/pages/wallet/wallet-statement/blocks/ShowDetailDialog.tsx index 1a00a99..06c6943 100644 --- a/src/pages/wallet/wallet-statement/blocks/ShowDetailDialog.tsx +++ b/src/pages/wallet/wallet-statement/blocks/ShowDetailDialog.tsx @@ -16,14 +16,9 @@ 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); -} +import { toAbsoluteUrl } from '@/utils'; interface ContextProps { doExportData: (sorting: any, filter: any) => Promise; @@ -233,15 +228,6 @@ 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(); @@ -250,9 +236,11 @@ const ShowDetailWalletDialog = () => { const wb = XLSX.utils.book_new(); const ws = XLSX.utils.json_to_sheet([]); - // Space untuk header image + // Membuat space untuk header image (sekitar 10 baris) const imageSpaceRows = 10; + const companyHeader = [ + // Baris kosong untuk space gambar header ...Array(imageSpaceRows).fill(['']), [''], ['Wallet Statement Details'], @@ -263,8 +251,17 @@ 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' ] ]; @@ -278,12 +275,21 @@ 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; - // Apply styles + // Style untuk title "Wallet Statement Details" const titleRowIndex = imageSpaceRows + 1; const titleCell = XLSX.utils.encode_cell({ r: titleRowIndex, c: 0 }); if (ws[titleCell]) { @@ -293,19 +299,33 @@ const ShowDetailWalletDialog = () => { }; } - // Merge cells + // 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 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 column headers + // Style untuk 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 }); @@ -324,14 +344,14 @@ const ShowDetailWalletDialog = () => { } } - // Style data rows + // Style untuk 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' }, + alignment: { horizontal: 'left' }, // Semua kolom rata kiri border: { top: { style: 'thin', color: { rgb: 'D9D9D9' } }, bottom: { style: 'thin', color: { rgb: 'D9D9D9' } }, @@ -343,165 +363,123 @@ const ShowDetailWalletDialog = () => { } } - // Try ExcelJS with better error handling - let useExcelJS = false; try { - // 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'); + // Import ExcelJS untuk menambahkan gambar + const ExcelJS = await import('exceljs'); - // 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; + // Buat workbook baru dengan ExcelJS + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('Wallet Statement'); - // 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 - }; - } + // 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 + }; } } } } + } - // Set column widths - colWidths.forEach((width, index) => { - worksheet.getColumn(index + 1).width = width.wch; + // 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); }); + } - // 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); + // Tambahkan gambar header + try { + const response = await fetch( + toAbsoluteUrl('/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 } }); } - - // 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); + } catch (imageError) { + console.warn('Could not load header image, continuing without it:', imageError); } - } catch (excelJSError) { - console.warn('ExcelJS failed, falling back to XLSX:', excelJSError); - useExcelJS = false; - } - // Fallback to XLSX if ExcelJS failed - if (!useExcelJS) { + // 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); + 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); } - - toast.success('Excel export completed successfully'); - } catch (error: any) { + } catch (error) { console.error('Error exporting to Excel:', error); - toast.error('Failed to export Excel: ' + error.message); + toast.error('Failed to export Excel'); } }; 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); @@ -511,84 +489,92 @@ const ShowDetailWalletDialog = () => { format: 'a4' }); - // Try to add header image dengan multiple paths - const imagePaths = [ - '/media/avatars/KopDetailWalletStatement.png', - './media/avatars/KopDetailWalletStatement.png', - ]; + const headerImageUrl = toAbsoluteUrl('/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, @@ -615,19 +601,15 @@ const ShowDetailWalletDialog = () => { } } }); - } catch (autoTableError) { - console.error('AutoTable error:', autoTableError); - toast.error('Failed to generate PDF table'); - return; - } - const fileName = `wallet_statement_${selectedWallet?.ID}_${format(new Date(), 'yyyyMMdd_HHmmss')}.pdf`; - pdf.save(fileName); - - toast.success('PDF export completed successfully'); - } catch (error: any) { + 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 (error) { console.error('Error exporting to PDF:', error); - toast.error('Failed to export PDF: ' + error.message); + toast.error('Failed to export PDF'); } }; @@ -713,4 +695,4 @@ const ShowDetailWalletDialog = () => { ); }; -export default ShowDetailWalletDialog; \ No newline at end of file +export default ShowDetailWalletDialog;