From 52879b58fe3603164549d27e6e2375ed55919ea9 Mon Sep 17 00:00:00 2001 From: efrilm Date: Fri, 26 Sep 2025 12:56:15 +0700 Subject: [PATCH] Excel Report Payment Method --- .../export/excel/ExcelExportPaymentService.ts | 251 ++++++++++++++++++ .../ReportPaymentMethodContent.tsx | 24 +- 2 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 src/services/export/excel/ExcelExportPaymentService.ts diff --git a/src/services/export/excel/ExcelExportPaymentService.ts b/src/services/export/excel/ExcelExportPaymentService.ts new file mode 100644 index 0000000..ee7d719 --- /dev/null +++ b/src/services/export/excel/ExcelExportPaymentService.ts @@ -0,0 +1,251 @@ +import type { PaymentReport } from '@/types/services/analytic' + +export class ExcelExportPaymentService { + /** + * Export Payment Method Report to Excel + */ + static async exportPaymentMethodToExcel(paymentData: PaymentReport, filename?: string) { + try { + // Dynamic import untuk xlsx library + const XLSX = await import('xlsx') + + // Prepare data untuk Excel + const worksheetData: any[][] = [] + + // Header dengan report info (baris 1-2) + worksheetData.push(['LAPORAN METODE PEMBAYARAN']) // Row 0 - Main title + worksheetData.push([`Periode: ${paymentData.date_from.split('T')[0]} - ${paymentData.date_to.split('T')[0]}`]) // Row 1 - Period + worksheetData.push([]) // Empty row + + // Add Summary Section + worksheetData.push(['RINGKASAN PERIODE']) // Section header + worksheetData.push([]) // Empty row + + const summaryData = [ + ['Total Amount:', `Rp ${paymentData.summary.total_amount.toLocaleString('id-ID')}`], + ['Total Orders:', paymentData.summary.total_orders.toString()], + ['Total Payments:', paymentData.summary.total_payments.toString()], + ['Average Order Value:', `Rp ${paymentData.summary.average_order_value.toLocaleString('id-ID')}`] + ] + + summaryData.forEach(row => { + worksheetData.push([row[0], row[1]]) // Only 2 columns needed + }) + + worksheetData.push([]) // Empty row + worksheetData.push([]) // Empty row + + // Payment Method Details Section Header + worksheetData.push(['RINCIAN METODE PEMBAYARAN']) // Section header + worksheetData.push([]) // Empty row + + // Header row untuk tabel payment method data + const headerRow = ['No', 'Metode Pembayaran', 'Tipe', 'Jumlah Order', 'Total Amount', 'Persentase'] + worksheetData.push(headerRow) + + // Add payment method data rows + paymentData.data.forEach((payment, index) => { + const rowData = [ + index + 1, // No + payment.payment_method_name, + payment.payment_method_type.toUpperCase(), + payment.order_count, + payment.total_amount, // Store as number for Excel formatting + `${(payment.percentage ?? 0).toFixed(1)}%` + ] + worksheetData.push(rowData) + }) + + // Add total row + const totalRow = ['TOTAL', '', '', paymentData.summary.total_orders, paymentData.summary.total_amount, '100.0%'] + worksheetData.push(totalRow) + + // Create workbook dan worksheet + const workbook = XLSX.utils.book_new() + const worksheet = XLSX.utils.aoa_to_sheet(worksheetData) + + // Apply basic formatting + this.applyBasicFormatting(worksheet, worksheetData.length, XLSX) + + // Add worksheet ke workbook + XLSX.utils.book_append_sheet(workbook, worksheet, 'Metode Pembayaran') + + // Generate filename + const exportFilename = filename || this.generateFilename('Metode_Pembayaran') + + // Download file + XLSX.writeFile(workbook, exportFilename) + + return { success: true, filename: exportFilename } + } catch (error) { + console.error('Error exporting to Excel:', error) + return { success: false, error: 'Failed to export Excel file' } + } + } + + /** + * Apply basic formatting (SheetJS compatible) + */ + private static applyBasicFormatting(worksheet: any, totalRows: number, XLSX: any) { + // Set column widths + const colWidths = [ + { wch: 8 }, // No + { wch: 25 }, // Metode Pembayaran + { wch: 12 }, // Tipe + { wch: 15 }, // Jumlah Order + { wch: 20 }, // Total Amount + { wch: 12 } // Persentase + ] + worksheet['!cols'] = colWidths + + // Set row heights for better spacing + worksheet['!rows'] = [ + { hpt: 30 }, // Title row + { hpt: 25 }, // Period row + { hpt: 15 }, // Empty row + { hpt: 25 }, // Summary header + { hpt: 15 } // Empty row + ] + + // Merge cells untuk headers + const merges = [ + { s: { r: 0, c: 0 }, e: { r: 0, c: 5 } }, // Title (span across all columns) + { s: { r: 1, c: 0 }, e: { r: 1, c: 5 } }, // Period (span across all columns) + { s: { r: 3, c: 0 }, e: { r: 3, c: 5 } } // Summary header (span across all columns) + ] + + // Find and add merge for payment method details header + for (let i = 0; i < totalRows; i++) { + const cell = worksheet[XLSX.utils.encode_cell({ r: i, c: 0 })] + if (cell && cell.v === 'RINCIAN METODE PEMBAYARAN') { + merges.push({ s: { r: i, c: 0 }, e: { r: i, c: 5 } }) // Span across all columns + break + } + } + + worksheet['!merges'] = merges + + // Apply number formatting untuk currency cells + this.applyNumberFormatting(worksheet, totalRows, XLSX) + } + + /** + * Apply number formatting for currency and styling + */ + private static applyNumberFormatting(worksheet: any, totalRows: number, XLSX: any) { + // Find table data start (after header row) + let dataStartRow = -1 + for (let i = 0; i < totalRows; i++) { + const cell = worksheet[XLSX.utils.encode_cell({ r: i, c: 0 })] + if (cell && cell.v === 'No') { + dataStartRow = i + 1 + break + } + } + + if (dataStartRow === -1) return + + // Count actual data rows (excluding total row) + const dataRowsCount = totalRows - dataStartRow - 1 // -1 for total row + + // Apply currency formatting to Total Amount column (column 4 - index 4) + for (let row = dataStartRow; row <= dataStartRow + dataRowsCount; row++) { + // Include total row + const cellAddress = XLSX.utils.encode_cell({ r: row, c: 4 }) // Total Amount column + const cell = worksheet[cellAddress] + + if (cell && typeof cell.v === 'number') { + // Apply Indonesian currency format + cell.z = '#,##0' + cell.t = 'n' + } + } + + // Apply styling to header row + const headerRowIndex = dataStartRow - 1 + for (let col = 0; col < 6; col++) { + const cellAddress = XLSX.utils.encode_cell({ r: headerRowIndex, c: col }) + const cell = worksheet[cellAddress] + + if (cell) { + // Apply bold formatting (basic approach for SheetJS) + cell.s = { + font: { bold: true }, + fill: { fgColor: { rgb: 'F3F4F6' } }, // Light gray background + border: { + bottom: { style: 'medium', color: { rgb: '000000' } } + } + } + } + } + + // Apply styling to total row + const totalRowIndex = dataStartRow + dataRowsCount + for (let col = 0; col < 6; col++) { + const cellAddress = XLSX.utils.encode_cell({ r: totalRowIndex, c: col }) + const cell = worksheet[cellAddress] + + if (cell) { + // Apply bold formatting for total row + cell.s = { + font: { bold: true }, + border: { + top: { style: 'medium', color: { rgb: '000000' } } + } + } + } + } + } + + /** + * Generate filename with timestamp + */ + private static generateFilename(prefix: string): string { + const now = new Date() + const year = now.getFullYear() + const month = (now.getMonth() + 1).toString().padStart(2, '0') + const day = now.getDate().toString().padStart(2, '0') + const hour = now.getHours().toString().padStart(2, '0') + const minute = now.getMinutes().toString().padStart(2, '0') + + return `${prefix}_${year}_${month}_${day}_${hour}${minute}.xlsx` + } + + /** + * Export Payment Method data with custom configuration + */ + static async exportCustomPaymentData( + data: any[][], + sheetName: string = 'Payment Method', + filename?: string, + options?: { + colWidths?: { wch: number }[] + merges?: { s: { r: number; c: number }; e: { r: number; c: number } }[] + } + ) { + try { + const XLSX = await import('xlsx') + + const workbook = XLSX.utils.book_new() + const worksheet = XLSX.utils.aoa_to_sheet(data) + + // Apply options + if (options?.colWidths) { + worksheet['!cols'] = options.colWidths + } + if (options?.merges) { + worksheet['!merges'] = options.merges + } + + XLSX.utils.book_append_sheet(workbook, worksheet, sheetName) + + const exportFilename = filename || this.generateFilename('Payment_Method_Export') + XLSX.writeFile(workbook, exportFilename) + + return { success: true, filename: exportFilename } + } catch (error) { + console.error('Error exporting to Excel:', error) + return { success: false, error: 'Failed to export Excel file' } + } + } +} diff --git a/src/views/apps/report/financial/payment-method-report/ReportPaymentMethodContent.tsx b/src/views/apps/report/financial/payment-method-report/ReportPaymentMethodContent.tsx index 22e6fe9..eaec83c 100644 --- a/src/views/apps/report/financial/payment-method-report/ReportPaymentMethodContent.tsx +++ b/src/views/apps/report/financial/payment-method-report/ReportPaymentMethodContent.tsx @@ -2,6 +2,7 @@ import DateRangePicker from '@/components/RangeDatePicker' import { ReportItemHeader, ReportItemSubheader } from '@/components/report/ReportItem' +import { ExcelExportPaymentService } from '@/services/export/excel/ExcelExportPaymentService' import { usePaymentAnalytics } from '@/services/queries/analytics' import { formatCurrency, formatDateDDMMYYYY } from '@/utils/transform' import { Button, Card, CardContent } from '@mui/material' @@ -16,6 +17,27 @@ const ReportPaymentMethodContent = () => { date_to: formatDateDDMMYYYY(endDate!) }) + const handleExportExcel = async () => { + if (!paymentAnalytics) { + console.warn('No data available for export') + return + } + + try { + const result = await ExcelExportPaymentService.exportPaymentMethodToExcel(paymentAnalytics) + + if (result.success) { + console.log(`File exported successfully: ${result.filename}`) + // Optional: Show success message to user + } else { + console.error('Export failed:', result.error) + // Optional: Show error message to user + } + } catch (error) { + console.error('Export error:', error) + } + } + return (
@@ -25,7 +47,7 @@ const ReportPaymentMethodContent = () => { variant='tonal' startIcon={} className='max-sm:is-full' - // onClick={handleExportPDF} + onClick={handleExportExcel} > Ekspor