import React, { createContext, useCallback, useEffect, useState } from 'react'; import { Dialog, DialogBody, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { useCallApi } from '@/hooks'; import { apiConfig } from '@/config/api.config'; import { DataGridColumnHeader, DataGridProvider } from '@/components'; import { useManageStatementContext } from '../hooks/useManageWalletStatementContext'; import { toast, Toaster } from 'sonner'; 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'; import { toAbsoluteUrl } from '@/utils'; interface ContextProps { doExportData: (sorting: any, filter: any) => Promise; doExportExcel: () => Promise; doExportPDF: () => Promise; } const initialProps: ContextProps = { doExportData: async () => ({ data: [], totalCount: 0 }), doExportExcel: async () => {}, doExportPDF: async () => {} }; export const showDetailWalletContext = createContext(initialProps); const ShowDetailWalletDialog = () => { const { GetData, GetExportData } = useCallApi(); const { showDetailDialog, setShowDetailDialog, selectedWallet } = useManageStatementContext(); const [filters, setFilters] = useState({}); const [transaction, setTransaction] = useState([]); const categoryMap: Record = { T: 'TRANSFER', P: 'PURCHASE', W: 'WITHDRAW', U: 'TOP UP', R: 'RETURN', N: 'TOP UP PARTNER', E: 'REWARD', L: 'PURCHASE LOJA', B: 'TOP UP P24', A: 'TRANSFER AGENT', M: 'WITHDRAWAL AGENT', O: 'TOP UP AGENT', S: 'TRANSFER P24', I: 'WIJTDRAW MERCHANT', D: 'DONATION', F: 'FEE', V: 'REVERSAL', C: 'CASHBACK CASH', H: 'CASHBACK POINT' }; const columns = [ { accessorKey: 'transaction_code', header: ({ column }: any) => ( ), enableSorting: false, meta: { headerClassName: 'min-w-[120px]' } }, { accessorKey: 'msisdn_reff', header: ({ column }: any) => , enableSorting: false, meta: { headerClassName: 'min-w-[120px]' } }, { accessorKey: 'transaction_type.name', header: ({ column }: any) => ( ), enableSorting: false, meta: { headerClassName: 'min-w-[180px]' } }, { accessorKey: 'type', header: ({ column }: any) => , enableSorting: false, meta: { headerClassName: 'min-w-[80px]' } }, { accessorKey: 'pre_amount', header: ({ column }: any) => , enableSorting: false, cell: (info: any) => info.getValue()?.toFixed(2), meta: { headerClassName: 'min-w-[100px]' } }, { accessorKey: 'amount', header: ({ column }: any) => , enableSorting: false, cell: (info: any) => info.getValue()?.toFixed(2), meta: { headerClassName: 'min-w-[100px]' } }, { accessorKey: 'post_amount', header: ({ column }: any) => , enableSorting: false, cell: (info: any) => info.getValue()?.toFixed(2), meta: { headerClassName: 'min-w-[100px]' } }, { accessorFn: (row: any) => categoryMap[row.category] || '_', accessorKey: 'category', header: ({ column }: any) => , enableSorting: false, enableHiding: false, meta: { headerClassName: 'w-[250px]' } }, { accessorKey: 'date', header: ({ column }: any) => , enableSorting: false, cell: (info: any) => { const val = info.getValue(); if (!val) return '-'; return new Date(val).toLocaleString('id-ID', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }); }, meta: { headerClassName: 'min-w-[150px]' } }, { accessorKey: 'purpose', header: ({ column }: any) => , enableSorting: false, cell: (info: any) => info.getValue() || '-', meta: { headerClassName: 'min-w-[200px]' } }, { accessorKey: 'notes', header: ({ column }: any) => , enableSorting: false, cell: (info: any) => info.getValue() || '-', meta: { headerClassName: 'min-w-[200px]' } } ]; const getTransactionLists = useCallback( async (page: number, limit: number, sorting: any, _columnFilters: any) => { try { const response = await GetData( `${apiConfig.service_wallet}/dashboard/balance/list-balance-detail/${selectedWallet?.ID}`, { limit, page: page + 1, with_deleted: false, order_field: sorting?.[0]?.id ?? 'created_at', order_direction: sorting?.[0]?.desc ? 'ASC' : 'DESC', filter: JSON.stringify(filters) } ); if (!response || !response.data) { console.warn('No data received:', response); return { data: [], totalCount: 0 }; } setTransaction(response.data.list); return { data: response.data.list, totalCount: response.data.total_count }; } catch (error) { console.error('Error fetching transaction', error); return { data: [], totalCount: 0 }; } }, [GetData, filters, selectedWallet] ); const getAllTransactionData = async () => { try { const response = await GetData( `${apiConfig.service_wallet}/dashboard/balance/list-balance-detail/${selectedWallet?.ID}`, { limit: 10000, page: 1, with_deleted: false, order_field: 'created_at', order_direction: 'DESC', filter: JSON.stringify(filters) } ); return response?.data?.list || []; } catch (error) { console.error('Error fetching all transaction data', error); return []; } }; const formatTransactionData = (data: any[]) => { return data.map((item) => ({ 'Transaction Code': item.transaction_code || '-', 'MSISDN Reffer': item.msisdn_reff || '-', 'Transaction Type': item.transaction_type?.name || '-', Type: item.type || '-', 'Pre Amount': item.pre_amount?.toFixed(2) || '0.00', Amount: item.amount?.toFixed(2) || '0.00', 'Post Amount': item.post_amount?.toFixed(2) || '0.00', Category: categoryMap[item.category] || '-', Date: item.date ? new Date(item.date).toLocaleString('id-ID', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }) : '-', Purpose: item.purpose || '-', Notes: item.notes || '-' })); }; const doExportExcel = async () => { try { const allData = await getAllTransactionData(); const formattedData = formatTransactionData(allData); const wb = XLSX.utils.book_new(); const ws = XLSX.utils.json_to_sheet([]); // 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'], [`Export Date: ${format(new Date(), 'dd MMM yyyy HH:mm:ss')}`], [`Wallet ID: ${selectedWallet?.ID || '-'}`], [''] ]; const columnHeaders = [ [ 'Transaction Code', 'MSISDN Reffer', 'Transaction Type', 'Type', 'Pre Amount', 'Amount', 'Post Amount', 'Category', 'Date', 'Purpose', 'Notes' ] ]; const excelData = [ ...companyHeader, ...columnHeaders, ...formattedData.map((item) => Object.values(item)) ]; XLSX.utils.sheet_add_aoa(ws, excelData); // 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 } ]; ws['!cols'] = colWidths; // Style untuk title "Wallet Statement Details" const titleRowIndex = imageSpaceRows + 1; const titleCell = XLSX.utils.encode_cell({ r: titleRowIndex, c: 0 }); if (ws[titleCell]) { ws[titleCell].s = { font: { bold: true, sz: 16 }, alignment: { horizontal: 'center' } }; } // 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 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 }); if (ws[cellRef]) { ws[cellRef].s = { font: { bold: true, color: { rgb: 'FFFFFF' } }, fill: { fgColor: { rgb: '4472C4' } }, alignment: { horizontal: 'center' }, border: { top: { style: 'thin', color: { rgb: '000000' } }, bottom: { style: 'thin', color: { rgb: '000000' } }, left: { style: 'thin', color: { rgb: '000000' } }, right: { style: 'thin', color: { rgb: '000000' } } } }; } } // 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' }, // Semua kolom rata kiri border: { top: { style: 'thin', color: { rgb: 'D9D9D9' } }, bottom: { style: 'thin', color: { rgb: 'D9D9D9' } }, left: { style: 'thin', color: { rgb: 'D9D9D9' } }, right: { style: 'thin', color: { rgb: 'D9D9D9' } } } }; } } } try { // Import ExcelJS untuk menambahkan gambar const ExcelJS = await import('exceljs'); // Buat workbook baru dengan ExcelJS const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet('Wallet Statement'); // 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; }); // 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 } }); } } catch (imageError) { console.warn('Could not load header image, continuing without it:', imageError); } // 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); } } catch (error) { console.error('Error exporting to Excel:', error); toast.error('Failed to export Excel'); } }; const doExportPDF = async () => { try { const allData = await getAllTransactionData(); const formattedData = formatTransactionData(allData); const pdf = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' }); const headerImageUrl = toAbsoluteUrl('/media/avatars/KopDetailWalletStatement.png'); 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, startY: currentY, styles: { fontSize: 7, cellPadding: 2, overflow: 'linebreak', halign: 'center' }, headStyles: { fillColor: [68, 114, 196], textColor: [255, 255, 255], fontStyle: 'bold', halign: 'center' }, columnStyles, tableWidth: 'wrap', margin: { top: currentY, left: leftMargin }, didDrawCell: (data: any) => { if (data.section === 'body') { pdf.setDrawColor(200, 200, 200); pdf.setLineWidth(0.1); } } }); 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'); } }; const doExportData = async (sorting: any, filter: any) => { const user = localStorage.getItem( `${import.meta.env.VITE_APP_NAME}-auth-v${import.meta.env.VITE_APP_VERSION}` ); const parsedUser = user ? JSON.parse(user) : null; sorting = sorting.length === 0 ? [{ id: 'created_at', desc: false }] : sorting; filter = filter && filter.length > 0 ? filter : []; let dataFilter: any = {}; if (filter && filter.length > 0) { filter.forEach((f: any) => { if (f.id === 'username') { dataFilter['username'] = { like: f.value }; } else if (f.id === 'created_at') { dataFilter['created_at'] = f.value; } }); } if (!dataFilter['created_at']) { const defaultFrom = new Date(new Date().setMonth(new Date().getMonth() - 1)); const defaultTo = new Date(); dataFilter['created_at'] = { from: `${format(defaultFrom, 'yyyy-MM-dd')} 00:00:00`, to: `${format(defaultTo, 'yyyy-MM-dd')} 23:59:59` }; } let param = { order_field: sorting[0].id || 'created_at', order_direction: sorting[0].desc ? 'DESC' : 'ASC', with_deleted: false, filter: JSON.stringify(dataFilter), token: parsedUser?.access_token }; let url = `${apiConfig.service_wallet}/dashboard/balance/list-balance-download/${selectedWallet?.ID}`; GetExportData(url, param, 'user_activity_export_'); }; return ( Details Walletsss Statementssss
getTransactionLists(pageIndex, pageSize, sorting, columnFilters) } />
); }; export default ShowDetailWalletDialog;