'use client' import { useProductSalesAnalytics, useProfitLossAnalytics, useSalesAnalytics, usePaymentAnalytics, useCategoryAnalytics } from '@/services/queries/analytics' import { useOutletById } from '@/services/queries/outlets' import { formatCurrency, formatDate, formatDateDDMMYYYY, formatDatetime } from '@/utils/transform' import ReportGeneratorComponent from '@/views/dashboards/daily-report/report-generator' import ReportHeader from '@/views/dashboards/daily-report/report-header' import React, { useEffect, useRef, useState } from 'react' const DailyPOSReport = () => { const reportRef = useRef(null) const [now, setNow] = useState(new Date()) const [selectedDate, setSelectedDate] = useState(new Date()) const [dateRange, setDateRange] = useState({ startDate: new Date(), endDate: new Date() }) const [filterType, setFilterType] = useState<'single' | 'range'>('single') // 'single' or 'range' const [isGeneratingPDF, setIsGeneratingPDF] = useState(false) // Use selectedDate for single date filter, or dateRange for range filter const getDateParams = () => { if (filterType === 'single') { return { date_from: formatDateDDMMYYYY(selectedDate), date_to: formatDateDDMMYYYY(selectedDate) } } else { return { date_from: formatDateDDMMYYYY(dateRange.startDate), date_to: formatDateDDMMYYYY(dateRange.endDate) } } } const dateParams = getDateParams() const { data: outlet } = useOutletById() const { data: sales } = useSalesAnalytics(dateParams) const { data: profitLoss } = useProfitLossAnalytics(dateParams) const { data: products } = useProductSalesAnalytics(dateParams) const { data: paymentAnalytics } = usePaymentAnalytics(dateParams) const { data: category } = useCategoryAnalytics(dateParams) const productSummary = { totalQuantitySold: products?.data?.reduce((sum, item) => sum + (item?.quantity_sold || 0), 0) || 0, totalRevenue: products?.data?.reduce((sum, item) => sum + (item?.revenue || 0), 0) || 0, totalOrders: products?.data?.reduce((sum, item) => sum + (item?.order_count || 0), 0) || 0 } // Calculate profit loss product summary const profitLossProductSummary = { totalRevenue: profitLoss?.product_data?.reduce((sum, item) => sum + (item?.revenue || 0), 0) || 0, totalCost: profitLoss?.product_data?.reduce((sum, item) => sum + (item?.cost || 0), 0) || 0, totalGrossProfit: profitLoss?.product_data?.reduce((sum, item) => sum + (item?.gross_profit || 0), 0) || 0, totalQuantity: profitLoss?.product_data?.reduce((sum, item) => sum + (item?.quantity_sold || 0), 0) || 0 } const categorySummary = { totalRevenue: category?.data?.reduce((sum, item) => sum + (item?.total_revenue || 0), 0) || 0, orderCount: category?.data?.reduce((sum, item) => sum + (item?.order_count || 0), 0) || 0, productCount: category?.data?.reduce((sum, item) => sum + (item?.product_count || 0), 0) || 0, totalQuantity: category?.data?.reduce((sum, item) => sum + (item?.total_quantity || 0), 0) || 0 } useEffect(() => { setNow(new Date()) }, []) // Format date for input field (YYYY-MM-DD) const formatDateForInput = (date: Date) => { return date.toISOString().split('T')[0] } // Get display text for the report period const getReportPeriodText = () => { if (filterType === 'single') { return `${formatDateDDMMYYYY(selectedDate)} - ${formatDateDDMMYYYY(selectedDate)}` } else { return `${formatDateDDMMYYYY(dateRange.startDate)} - ${formatDateDDMMYYYY(dateRange.endDate)}` } } // Get report title based on filter type const getReportTitle = () => { if (filterType === 'single') { return 'Laporan Transaksi' } else { const daysDiff = Math.ceil((dateRange.endDate.getTime() - dateRange.startDate.getTime()) / (1000 * 3600 * 24)) + 1 // return `Laporan Transaksi ${daysDiff} Hari` return `Laporan Transaksi` } } const handleGeneratePDF = async () => { const reportElement = reportRef.current if (!reportElement) { alert('Report element tidak ditemukan') return } // Set loading state setIsGeneratingPDF(true) try { // Import jsPDF dan html2canvas const jsPDF = (await import('jspdf')).default const html2canvas = (await import('html2canvas')).default // Pastikan element terlihat penuh const originalOverflow = reportElement.style.overflow reportElement.style.overflow = 'visible' // Wait untuk memastikan rendering selesai await new Promise(resolve => setTimeout(resolve, 300)) console.log('Starting PDF generation...') // Update loading message console.log('Capturing content...') // Capture canvas dengan setting yang optimal const canvas = await html2canvas(reportElement, { scale: 1.5, useCORS: true, allowTaint: true, backgroundColor: '#ffffff', logging: false, removeContainer: true, imageTimeout: 0, height: reportElement.scrollHeight, width: reportElement.scrollWidth, scrollX: 0, scrollY: 0, // Pastikan capture semua content windowWidth: Math.max(reportElement.scrollWidth, window.innerWidth), windowHeight: Math.max(reportElement.scrollHeight, window.innerHeight) }) console.log('Canvas captured:', canvas.width, 'x', canvas.height) console.log('Generating PDF pages...') // Restore overflow reportElement.style.overflow = originalOverflow // Create PDF const pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4', compress: true }) // A4 dimensions const pdfWidth = 210 const pdfHeight = 297 const margin = 5 // Small margin to prevent cutoff // Calculate scaling const availableWidth = pdfWidth - 2 * margin const availableHeight = pdfHeight - 2 * margin const imgWidth = availableWidth const imgHeight = (canvas.height * availableWidth) / canvas.width console.log('PDF dimensions - Canvas:', canvas.width, 'x', canvas.height) console.log('PDF dimensions - Image:', imgWidth, 'x', imgHeight) console.log('Available height per page:', availableHeight) // Split content across pages let yPosition = margin let sourceY = 0 let pageCount = 1 let remainingHeight = imgHeight while (remainingHeight > 0) { console.log(`Processing page ${pageCount}, remaining height: ${remainingHeight}`) if (pageCount > 1) { pdf.addPage() yPosition = margin } // Calculate how much content fits on this page const heightForThisPage = Math.min(remainingHeight, availableHeight) // Calculate source dimensions for cropping const sourceHeight = (heightForThisPage * canvas.height) / imgHeight // Create temporary canvas for this page portion const tempCanvas = document.createElement('canvas') const tempCtx = tempCanvas.getContext('2d') if (!tempCtx) { throw new Error('Unable to get 2D context from canvas') } tempCanvas.width = canvas.width tempCanvas.height = sourceHeight // Draw the portion we need tempCtx.drawImage( canvas, 0, sourceY, canvas.width, sourceHeight, // Source rectangle 0, 0, canvas.width, sourceHeight // Destination rectangle ) // Convert to image data const pageImageData = tempCanvas.toDataURL('image/jpeg', 0.9) // Add to PDF pdf.addImage(pageImageData, 'JPEG', margin, yPosition, imgWidth, heightForThisPage) // Update for next page sourceY += sourceHeight remainingHeight -= heightForThisPage pageCount++ // Safety check to prevent infinite loop if (pageCount > 20) { console.warn('Too many pages, breaking loop') break } } console.log(`Generated ${pageCount - 1} pages`) console.log('Finalizing PDF...') // Add metadata pdf.setProperties({ title: getReportTitle(), subject: 'Transaction Report', author: 'Apskel POS System', creator: 'Apskel' }) // Generate filename const fileName = filterType === 'single' ? `laporan-transaksi-${formatDateForInput(selectedDate)}.pdf` : `laporan-transaksi-${formatDateForInput(dateRange.startDate)}-to-${formatDateForInput(dateRange.endDate)}.pdf` console.log('Saving PDF:', fileName) // Save PDF pdf.save(fileName) console.log('PDF generated successfully!') } catch (error) { console.error('Error generating PDF:', error) alert(`Terjadi kesalahan saat membuat PDF: ${error}`) } finally { // Reset loading state setIsGeneratingPDF(false) } } const LoadingOverlay = ({ isVisible, message = 'Generating PDF...' }: { isVisible: boolean; message?: string }) => { if (!isVisible) return null return (
{/* Animated Spinner */}
{/* Loading Message */}

{message}

Mohon tunggu, proses ini mungkin membutuhkan beberapa detik...

{/* Progress Steps */}
Capturing report content
Processing pages
Finalizing PDF
) } return (
{/* Control Panel */} {/* Report Template */}
{/* Header */} {/* Performance Summary */}

1. Ringkasan

Total Penjualan (termasuk rasik) {formatCurrency(profitLoss?.summary.total_revenue ?? 0)}
Total Terjual {productSummary.totalQuantitySold}
HPP {formatCurrency(profitLoss?.summary.total_cost ?? 0)} |{' '} {(((profitLoss?.summary.total_cost ?? 0) / (profitLoss?.summary.total_revenue || 1)) * 100).toFixed( 0 )} %
Laba Kotor {formatCurrency(profitLoss?.summary.gross_profit ?? 0)} |{' '} {(profitLoss?.summary.gross_profit_margin ?? 0).toFixed(0)}%
Biaya lain² {formatCurrency(profitLoss?.summary.total_tax ?? 0)} |{' '} {(((profitLoss?.summary.total_tax ?? 0) / (profitLoss?.summary.total_revenue || 1)) * 100).toFixed(0)} %
Laba/Rugi {formatCurrency(profitLoss?.summary.net_profit ?? 0)} |{' '} {(profitLoss?.summary.net_profit_margin ?? 0).toFixed(0)}%
{/* Profit Loss Product Table */}

Laba Rugi Per Produk

{profitLoss?.product_data?.map((item, index) => ( )) || []}
Produk Qty Pendapatan HPP Laba Kotor Margin (%)
{item.product_name} {item.quantity_sold} {formatCurrency(item.revenue)} {formatCurrency(item.cost)} {formatCurrency(item.gross_profit)} {(item.gross_profit_margin ?? 0).toFixed(1)}%
TOTAL {profitLossProductSummary.totalQuantity} {formatCurrency(profitLossProductSummary.totalRevenue)} {formatCurrency(profitLossProductSummary.totalCost)} {formatCurrency(profitLossProductSummary.totalGrossProfit)} {profitLossProductSummary.totalRevenue > 0 ? ( (profitLossProductSummary.totalGrossProfit / profitLossProductSummary.totalRevenue) * 100 ).toFixed(1) : 0} %
{/* Payment Method Summary */}

2. Ringkasan Metode Pembayaran

{paymentAnalytics?.data?.map((payment, index) => ( )) || []}
Metode Pembayaran Tipe Jumlah Order Total Amount Persentase
{payment.payment_method_name} {payment.payment_method_type.toUpperCase()} {payment.order_count} {formatCurrency(payment.total_amount)} {(payment.percentage ?? 0).toFixed(1)}%
TOTAL {paymentAnalytics?.summary.total_orders ?? 0} {formatCurrency(paymentAnalytics?.summary.total_amount ?? 0)}
{/* Category Summary */}

2. Ringkasan Kategori

{category?.data?.map((c, index) => ( )) || []}
Nama Total Produk Qty Jumlah Order Pendapatan
{c.category_name} {c.product_count} {c.total_quantity} {c.order_count} {formatCurrency(c.total_revenue)}
TOTAL {categorySummary?.productCount ?? 0} {categorySummary?.totalQuantity ?? 0} {categorySummary?.orderCount ?? 0} {formatCurrency(categorySummary?.totalRevenue ?? 0)}
{/* Transaction Summary */}

4. Ringkasan Item

{products?.data?.map((item, index) => ( )) || []}
Produk Kategori Qty Order Pendapatan Rata Rata
{item.product_name} {item.category_name} {item.quantity_sold} {item.order_count ?? 0} {formatCurrency(item.revenue)} {formatCurrency(item.average_price)}
TOTAL {productSummary.totalQuantitySold ?? 0} {productSummary.totalOrders ?? 0} {formatCurrency(productSummary.totalRevenue ?? 0)}
{/* Profit Loss Product Table */} {/* Footer */}

© 2025 Apskel - Sistem POS Terpadu

Dicetak pada: {now.toLocaleDateString('id-ID')}

) } export default DailyPOSReport