diff --git a/src/services/export/pdf/PDFExportSalesService.ts b/src/services/export/pdf/PDFExportSalesService.ts index c9f15e7..b7a86be 100644 --- a/src/services/export/pdf/PDFExportSalesService.ts +++ b/src/services/export/pdf/PDFExportSalesService.ts @@ -61,15 +61,15 @@ export class PDFExportSalesService { yPos = this.addReportTitle(pdf, salesData.profitLoss, yPos, pageWidth, marginLeft, marginRight) // Section 1: Ringkasan - checkPageBreak(60) + checkPageBreak(50) yPos = this.addRingkasanSection(pdf, salesData.profitLoss, yPos, pageWidth, marginLeft, marginRight, checkPageBreak) // Section 2: Invoice Summary - checkPageBreak(50) + checkPageBreak(40) yPos = this.addInvoiceSection(pdf, salesData.profitLoss, yPos, pageWidth, marginLeft, marginRight, checkPageBreak) // Section 3: Payment Methods - checkPageBreak(120) + checkPageBreak(80) yPos = this.addPaymentMethodsSection( pdf, salesData.paymentAnalytics, @@ -81,7 +81,7 @@ export class PDFExportSalesService { ) // Section 4: Category Summary - checkPageBreak(120) + checkPageBreak(80) yPos = this.addCategorySection( pdf, salesData.categoryAnalytics, @@ -93,7 +93,7 @@ export class PDFExportSalesService { ) // Section 5: Product Summary - checkPageBreak(150) + checkPageBreak(100) yPos = this.addProductSection( pdf, salesData.productAnalytics, @@ -142,7 +142,7 @@ export class PDFExportSalesService { } /** - * Add Ringkasan section + * Add Ringkasan section - SAMAKAN DENGAN PAYMENT METHODS STYLE */ private static addRingkasanSection( pdf: any, @@ -155,12 +155,12 @@ export class PDFExportSalesService { ): number { let yPos = startY - // Section title - pdf.setFontSize(16) + // Section title - SAMA SEPERTI PAYMENT METHODS + pdf.setFontSize(14) pdf.setFont('helvetica', 'bold') pdf.setTextColor(102, 45, 145) pdf.text('Ringkasan', marginLeft, yPos) - yPos += 10 + yPos += 12 // Reset text color pdf.setTextColor(0, 0, 0) @@ -192,11 +192,11 @@ export class PDFExportSalesService { yPos += 8 }) - return yPos + 10 + return yPos + 20 } /** - * Add Invoice section + * Add Invoice section - SAMAKAN DENGAN PAYMENT METHODS STYLE */ private static addInvoiceSection( pdf: any, @@ -209,12 +209,12 @@ export class PDFExportSalesService { ): number { let yPos = startY - // Section title - pdf.setFontSize(16) + // Section title - SAMA SEPERTI PAYMENT METHODS + pdf.setFontSize(14) pdf.setFont('helvetica', 'bold') pdf.setTextColor(102, 45, 145) pdf.text('Invoice', marginLeft, yPos) - yPos += 10 + yPos += 12 // Reset formatting pdf.setTextColor(0, 0, 0) @@ -242,11 +242,11 @@ export class PDFExportSalesService { yPos += 8 }) - return yPos + 15 + return yPos + 20 } /** - * Add Payment Methods section + * Add Payment Methods section - ORIGINAL STYLE (3 jam lu bikin ini!) */ private static addPaymentMethodsSection( pdf: any, @@ -363,11 +363,11 @@ export class PDFExportSalesService { align: 'right' }) - return yPos + 25 + return yPos + 20 } /** - * Add Category section + * Add Category section - SAMAKAN DENGAN PAYMENT METHODS STYLE */ private static addCategorySection( pdf: any, @@ -380,12 +380,12 @@ export class PDFExportSalesService { ): number { let yPos = startY - // Section title - pdf.setFontSize(16) + // Section title - SAMA SEPERTI PAYMENT METHODS + pdf.setFontSize(14) pdf.setFont('helvetica', 'bold') pdf.setTextColor(102, 45, 145) pdf.text('Ringkasan Kategori', marginLeft, yPos) - yPos += 15 + yPos += 12 // Reset formatting pdf.setTextColor(0, 0, 0) @@ -395,26 +395,26 @@ export class PDFExportSalesService { const colWidths = [50, 30, 25, 35] // Name, Products, Qty, Revenue let currentX = marginLeft - // Table header + // Table header - SAMA SEPERTI PAYMENT METHODS pdf.setFillColor(240, 240, 240) - pdf.rect(marginLeft, yPos - 3, tableWidth, 12, 'F') + pdf.rect(marginLeft, yPos, tableWidth, 10, 'F') pdf.setFont('helvetica', 'bold') - pdf.setFontSize(10) + pdf.setFontSize(9) const headers = ['Nama', 'Total Produk', 'Qty', 'Pendapatan'] currentX = marginLeft headers.forEach((header, index) => { if (index === 0) { - pdf.text(header, currentX + 2, yPos + 3) + pdf.text(header, currentX + 2, yPos + 6) } else { - pdf.text(header, currentX + colWidths[index] - 2, yPos + 3, { align: 'right' }) + pdf.text(header, currentX + colWidths[index] / 2, yPos + 6, { align: 'center' }) } currentX += colWidths[index] }) - yPos += 15 + yPos += 12 // Calculate summaries const categorySummary = { @@ -423,67 +423,67 @@ export class PDFExportSalesService { totalQuantity: categoryAnalytics.data?.reduce((sum, item) => sum + (item?.total_quantity || 0), 0) || 0 } - // Table rows + // Table rows - SAMA SEPERTI PAYMENT METHODS pdf.setFont('helvetica', 'normal') pdf.setFontSize(9) categoryAnalytics.data?.forEach((category, index) => { - if (checkPageBreak(12)) yPos = 20 - - // Row background (alternating) - if (index % 2 === 1) { - pdf.setFillColor(250, 250, 250) - pdf.rect(marginLeft, yPos - 3, tableWidth, 10, 'F') - } + if (checkPageBreak(10)) yPos = 20 currentX = marginLeft + pdf.setFont('helvetica', 'normal') + pdf.setFontSize(9) + pdf.setTextColor(0, 0, 0) // Category name - pdf.text(category.category_name, currentX + 2, yPos + 2) + pdf.text(category.category_name, currentX + 2, yPos + 5) currentX += colWidths[0] // Product count - pdf.text(category.product_count.toString(), currentX + colWidths[1] - 2, yPos + 2, { align: 'right' }) + pdf.text(category.product_count.toString(), currentX + colWidths[1] / 2, yPos + 5, { align: 'center' }) currentX += colWidths[1] // Quantity - pdf.text(category.total_quantity.toString(), currentX + colWidths[2] - 2, yPos + 2, { align: 'right' }) + pdf.text(category.total_quantity.toString(), currentX + colWidths[2] / 2, yPos + 5, { align: 'center' }) currentX += colWidths[2] // Revenue - pdf.text(this.formatCurrency(category.total_revenue), currentX + colWidths[3] - 2, yPos + 2, { align: 'right' }) + pdf.text(this.formatCurrency(category.total_revenue), currentX + colWidths[3] - 2, yPos + 5, { align: 'right' }) + + // Draw bottom border line - SAMA SEPERTI PAYMENT METHODS + pdf.setDrawColor(230, 230, 230) + pdf.setLineWidth(0.3) + pdf.line(marginLeft, yPos + 8, marginLeft + tableWidth, yPos + 8) yPos += 10 }) - // Table footer (Total) - if (checkPageBreak(12)) yPos = 20 - - pdf.setFillColor(220, 220, 220) - pdf.rect(marginLeft, yPos - 3, tableWidth, 12, 'F') + // Table footer (Total) - SAMA SEPERTI PAYMENT METHODS + pdf.setFillColor(245, 245, 245) // Lighter gray + pdf.rect(marginLeft, yPos, tableWidth, 10, 'F') pdf.setFont('helvetica', 'bold') - pdf.setFontSize(10) + pdf.setFontSize(9) currentX = marginLeft - pdf.text('TOTAL', currentX + 2, yPos + 3) + pdf.text('TOTAL', currentX + 2, yPos + 6) currentX += colWidths[0] - pdf.text(categorySummary.productCount.toString(), currentX + colWidths[1] - 2, yPos + 3, { align: 'right' }) + pdf.text(categorySummary.productCount.toString(), currentX + colWidths[1] / 2, yPos + 6, { align: 'center' }) currentX += colWidths[1] - pdf.text(categorySummary.totalQuantity.toString(), currentX + colWidths[2] - 2, yPos + 3, { align: 'right' }) + pdf.text(categorySummary.totalQuantity.toString(), currentX + colWidths[2] / 2, yPos + 6, { align: 'center' }) currentX += colWidths[2] - pdf.text(this.formatCurrency(categorySummary.totalRevenue), currentX + colWidths[3] - 2, yPos + 3, { + pdf.text(this.formatCurrency(categorySummary.totalRevenue), currentX + colWidths[3] - 2, yPos + 6, { align: 'right' }) - return yPos + 25 + return yPos + 20 } /** - * Add Product section with category grouping + * Add Product section - SAMAKAN DENGAN PAYMENT METHODS STYLE */ private static addProductSection( pdf: any, @@ -494,14 +494,14 @@ export class PDFExportSalesService { marginRight: number, checkPageBreak: (space: number) => boolean ): number { - let yPos = startY + let yPos = startY // Hapus extra spacing, biar sama dengan section lain - // Section title - pdf.setFontSize(16) + // Section title - SAMA SEPERTI PAYMENT METHODS + pdf.setFontSize(14) pdf.setFont('helvetica', 'bold') pdf.setTextColor(102, 45, 145) pdf.text('Ringkasan Item', marginLeft, yPos) - yPos += 15 + yPos += 12 // Reset formatting pdf.setTextColor(0, 0, 0) @@ -511,26 +511,26 @@ export class PDFExportSalesService { const colWidths = [60, 20, 20, 30, 30] // Product, Qty, Order, Revenue, Average let currentX = marginLeft - // Table header + // Table header - SAMA SEPERTI PAYMENT METHODS pdf.setFillColor(240, 240, 240) - pdf.rect(marginLeft, yPos - 3, tableWidth, 12, 'F') + pdf.rect(marginLeft, yPos, tableWidth, 10, 'F') pdf.setFont('helvetica', 'bold') - pdf.setFontSize(10) + pdf.setFontSize(9) const headers = ['Produk', 'Qty', 'Order', 'Pendapatan', 'Rata Rata'] currentX = marginLeft headers.forEach((header, index) => { if (index === 0) { - pdf.text(header, currentX + 2, yPos + 3) + pdf.text(header, currentX + 2, yPos + 6) } else { - pdf.text(header, currentX + colWidths[index] - 2, yPos + 3, { align: 'right' }) + pdf.text(header, currentX + colWidths[index] / 2, yPos + 6, { align: 'center' }) } currentX += colWidths[index] }) - yPos += 15 + yPos += 12 // Group products by category const groupedProducts = @@ -553,6 +553,8 @@ export class PDFExportSalesService { totalOrders: productAnalytics.data?.reduce((sum, item) => sum + (item?.order_count || 0), 0) || 0 } + // Table rows - SAMA SEPERTI PAYMENT METHODS + pdf.setFont('helvetica', 'normal') pdf.setFontSize(9) // Render grouped products @@ -562,105 +564,108 @@ export class PDFExportSalesService { const categoryProducts = groupedProducts[categoryName] // Check page break for category header - if (checkPageBreak(15)) yPos = 20 + if (checkPageBreak(10)) yPos = 20 - // Category header - pdf.setFillColor(230, 230, 230) - pdf.rect(marginLeft, yPos - 3, tableWidth, 12, 'F') + // Category header - SAMA STYLE SEPERTI PAYMENT METHODS TAPI WARNA LEBIH SOFT + pdf.setFillColor(248, 248, 248) // Warna lebih soft + pdf.rect(marginLeft, yPos, tableWidth, 10, 'F') pdf.setFont('helvetica', 'bold') + pdf.setFontSize(9) pdf.setTextColor(102, 45, 145) - pdf.text(categoryName.toUpperCase(), marginLeft + 2, yPos + 3) + pdf.text(categoryName.toUpperCase(), marginLeft + 2, yPos + 6) pdf.setTextColor(0, 0, 0) - yPos += 12 + yPos += 10 // Kurangi jarak, langsung ke 10px seperti row normal // Category products categoryProducts.forEach((item, index) => { if (checkPageBreak(10)) yPos = 20 - // Row background (alternating within category) - if (index % 2 === 1) { - pdf.setFillColor(250, 250, 250) - pdf.rect(marginLeft, yPos - 2, tableWidth, 8, 'F') - } - - pdf.setFont('helvetica', 'normal') currentX = marginLeft + pdf.setFont('helvetica', 'normal') + pdf.setFontSize(9) + pdf.setTextColor(0, 0, 0) // Product name (indented and truncated if needed) const productName = item.product_name.length > 45 ? item.product_name.substring(0, 42) + '...' : item.product_name - pdf.text(productName, currentX + 5, yPos + 2) // Indented for products + pdf.text(` ${productName}`, currentX + 2, yPos + 5) // Indented for products currentX += colWidths[0] // Quantity - pdf.text(item.quantity_sold.toString(), currentX + colWidths[1] - 2, yPos + 2, { align: 'right' }) + pdf.text(item.quantity_sold.toString(), currentX + colWidths[1] / 2, yPos + 5, { align: 'center' }) currentX += colWidths[1] // Order count - pdf.text((item.order_count || 0).toString(), currentX + colWidths[2] - 2, yPos + 2, { align: 'right' }) + pdf.text((item.order_count || 0).toString(), currentX + colWidths[2] / 2, yPos + 5, { align: 'center' }) currentX += colWidths[2] // Revenue - pdf.text(this.formatCurrency(item.revenue), currentX + colWidths[3] - 2, yPos + 2, { align: 'right' }) + pdf.text(this.formatCurrency(item.revenue), currentX + colWidths[3] - 2, yPos + 5, { align: 'right' }) currentX += colWidths[3] // Average price - pdf.text(this.formatCurrency(item.average_price), currentX + colWidths[4] - 2, yPos + 2, { align: 'right' }) + pdf.text(this.formatCurrency(item.average_price), currentX + colWidths[4] - 2, yPos + 5, { align: 'right' }) - yPos += 8 + // Draw bottom border line - SAMA SEPERTI PAYMENT METHODS + pdf.setDrawColor(230, 230, 230) + pdf.setLineWidth(0.3) + pdf.line(marginLeft, yPos + 8, marginLeft + tableWidth, yPos + 8) + + yPos += 10 }) - // Category subtotal + // Category subtotal - WARNA LEBIH SOFT if (checkPageBreak(10)) yPos = 20 const categoryTotalQty = categoryProducts.reduce((sum, item) => sum + (item.quantity_sold || 0), 0) const categoryTotalOrders = categoryProducts.reduce((sum, item) => sum + (item.order_count || 0), 0) const categoryTotalRevenue = categoryProducts.reduce((sum, item) => sum + (item.revenue || 0), 0) - pdf.setFillColor(200, 200, 200) - pdf.rect(marginLeft, yPos - 2, tableWidth, 10, 'F') + pdf.setFillColor(240, 240, 240) // Sama dengan table header, lebih soft + pdf.rect(marginLeft, yPos, tableWidth, 10, 'F') pdf.setFont('helvetica', 'bold') + pdf.setFontSize(9) currentX = marginLeft - pdf.text(`Subtotal ${categoryName}`, currentX + 5, yPos + 3) + pdf.text(`Subtotal ${categoryName}`, currentX + 2, yPos + 6) currentX += colWidths[0] - pdf.text(categoryTotalQty.toString(), currentX + colWidths[1] - 2, yPos + 3, { align: 'right' }) + pdf.text(categoryTotalQty.toString(), currentX + colWidths[1] / 2, yPos + 6, { align: 'center' }) currentX += colWidths[1] - pdf.text(categoryTotalOrders.toString(), currentX + colWidths[2] - 2, yPos + 3, { align: 'right' }) + pdf.text(categoryTotalOrders.toString(), currentX + colWidths[2] / 2, yPos + 6, { align: 'center' }) currentX += colWidths[2] - pdf.text(this.formatCurrency(categoryTotalRevenue), currentX + colWidths[3] - 2, yPos + 3, { align: 'right' }) + pdf.text(this.formatCurrency(categoryTotalRevenue), currentX + colWidths[3] - 2, yPos + 6, { align: 'right' }) - yPos += 15 + yPos += 10 // Kurangi spacing dari 15 ke 10 }) - // Grand total - if (checkPageBreak(12)) yPos = 20 + // Grand total - SAMA SEPERTI PAYMENT METHODS FOOTER + if (checkPageBreak(10)) yPos = 20 - pdf.setFillColor(180, 180, 180) - pdf.rect(marginLeft, yPos - 3, tableWidth, 12, 'F') + pdf.setFillColor(245, 245, 245) // Lighter gray - SAMA SEPERTI PAYMENT METHODS + pdf.rect(marginLeft, yPos, tableWidth, 10, 'F') pdf.setFont('helvetica', 'bold') - pdf.setFontSize(10) + pdf.setFontSize(9) currentX = marginLeft - pdf.text('TOTAL KESELURUHAN', currentX + 2, yPos + 3) + pdf.text('TOTAL KESELURUHAN', currentX + 2, yPos + 6) currentX += colWidths[0] - pdf.text(productSummary.totalQuantitySold.toString(), currentX + colWidths[1] - 2, yPos + 3, { align: 'right' }) + pdf.text(productSummary.totalQuantitySold.toString(), currentX + colWidths[1] / 2, yPos + 6, { align: 'center' }) currentX += colWidths[1] - pdf.text(productSummary.totalOrders.toString(), currentX + colWidths[2] - 2, yPos + 3, { align: 'right' }) + pdf.text(productSummary.totalOrders.toString(), currentX + colWidths[2] / 2, yPos + 6, { align: 'center' }) currentX += colWidths[2] - pdf.text(this.formatCurrency(productSummary.totalRevenue), currentX + colWidths[3] - 2, yPos + 3, { + pdf.text(this.formatCurrency(productSummary.totalRevenue), currentX + colWidths[3] - 2, yPos + 6, { align: 'right' }) - return yPos + 20 + return yPos + 25 } /**