'use client' import { useProductSalesAnalytics, useProfitLossAnalytics, useSalesAnalytics, usePaymentAnalytics, useCategoryAnalytics } from '@/services/queries/analytics' import { useOutletById } from '@/services/queries/outlets' import { generateExcel } from '@/utils/excelGenerator' import { generatePDF } from '@/utils/pdfGenerator' 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' // Dummy HPP values — ganti dengan data real nantinya const DUMMY_STD_HPP = 30 // % — ganti dengan data real nantinya const DUMMY_REAL_HPP = 0 // % — ganti dengan kalkulasi real nantinya const getHppStatus = (stdHpp: number, realHpp: number): 'Sehat' | 'Tidak Sehat' => realHpp <= stdHpp ? 'Sehat' : 'Tidak Sehat' const StatusBadge = ({ status }: { status: 'Sehat' | 'Tidak Sehat' }) => ( {status} ) const DailyPOSReport = () => { const reportRef = useRef(null) const [isGeneratingExcel, setIsGeneratingExcel] = useState(false) 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') const [isGeneratingPDF, setIsGeneratingPDF] = useState(false) // PDF Font Size Configuration const PDF_FONT_SIZES = { heading: 18, subheading: 16, tableContent: 12, tableHeader: 12, tableFooter: 12, grandTotal: 14, footer: 11 } // PDF Spacing Configuration const PDF_SPACING = { cellPadding: 5, cellPaddingLarge: 6, sectionGap: 20, headerGap: 15 } 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 today = new Date() const sevenDaysAgo = new Date() sevenDaysAgo.setDate(today.getDate() - 7) const { data: outlet } = useOutletById() const { data: profitLoss7Days } = useProfitLossAnalytics({ date_from: formatDateDDMMYYYY(sevenDaysAgo), date_to: formatDateDDMMYYYY(today) }) 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 } 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()) }, []) const formatDateForInput = (date: Date) => { return date.toISOString().split('T')[0] } const getReportPeriodText = () => { if (filterType === 'single') { return `${formatDateDDMMYYYY(selectedDate)} - ${formatDateDDMMYYYY(selectedDate)}` } else { return `${formatDateDDMMYYYY(dateRange.startDate)} - ${formatDateDDMMYYYY(dateRange.endDate)}` } } const getReportTitle = () => { return 'Laporan Transaksi' } const handleGeneratePDF = async () => { setIsGeneratingPDF(true) try { await generatePDF({ reportRef, outlet, profitLoss, products, paymentAnalytics, category, productSummary, categorySummary, filterType, selectedDate, dateRange, now }) } catch (error) { console.error('Error generating PDF:', error) alert(`Terjadi kesalahan saat membuat PDF: ${error}`) } finally { setIsGeneratingPDF(false) } } const handleGenerateExcel = async () => { setIsGeneratingExcel(true) try { await generateExcel({ outlet, profitLoss, products, paymentAnalytics, category, productSummary, categorySummary, filterType, selectedDate, dateRange }) } catch (error) { console.error('Error generating Excel:', error) alert(`Terjadi kesalahan saat membuat Excel: ${error}`) } finally { setIsGeneratingExcel(false) } } const LoadingOverlay = ({ isVisible, message = 'Generating PDF...' }: { isVisible: boolean; message?: string }) => { if (!isVisible) return null return (

{message}

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

Capturing report content
Processing pages
Finalizing PDF
) } // Group products by category const groupedProducts = products?.data?.reduce( (acc, item) => { const categoryName = item.category_name || 'Tidak Berkategori' if (!acc[categoryName]) { acc[categoryName] = [] } acc[categoryName].push(item) return acc }, {} as Record ) || {} return (
{/* Wrap header in a section for easy capture */}
{/* Performance Summary */}

Ringkasan

Total Penjualan {formatCurrency(profitLoss?.summary.total_revenue ?? 0)}
Total Diskon {formatCurrency(profitLoss?.summary.total_discount ?? 0)}
Total Pajak {formatCurrency(profitLoss?.summary.total_tax ?? 0)}
Service Charge {formatCurrency(0)}
Total {formatCurrency(profitLoss?.summary.total_revenue ?? 0)}
{/* Invoice */}

Invoice

Total Invoice {profitLoss?.summary.total_orders ?? 0}
{/* Payment Method Summary */}

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 — +3 kolom baru */}

Ringkasan Kategori

{category?.data?.map((c, index) => { const stdHpp = DUMMY_STD_HPP const realHpp = DUMMY_REAL_HPP const status = getHppStatus(stdHpp, realHpp) return ( ) }) || []}
Nama Qty Pendapatan % Std HPP % Real HPP Status
{c.category_name} {c.total_quantity} {formatCurrency(c.total_revenue)} {stdHpp}% {realHpp}%
TOTAL {categorySummary?.totalQuantity ?? 0} {formatCurrency(categorySummary?.totalRevenue ?? 0)}
{/* Product Summary — +3 kolom baru */}

Ringkasan Item Per Kategori

{Object.keys(groupedProducts) .sort((a, b) => { const productsA = groupedProducts[a] const productsB = groupedProducts[b] const orderA = productsA[0]?.category_order ?? 999 const orderB = productsB[0]?.category_order ?? 999 return orderA - orderB }) .map((categoryName, catIndex) => { const categoryProducts = groupedProducts[categoryName].sort((a, b) => { const skuA = a.product_sku || '' const skuB = b.product_sku || '' return skuA.localeCompare(skuB) }) const categoryTotalQty = categoryProducts.reduce((sum, item) => sum + (item.quantity_sold || 0), 0) const categoryTotalRevenue = categoryProducts.reduce((sum, item) => sum + (item.revenue || 0), 0) return (

{categoryName.toUpperCase()}

{categoryProducts.map((item, index) => { const stdHpp = DUMMY_STD_HPP const realHpp = DUMMY_REAL_HPP const status = getHppStatus(stdHpp, realHpp) return ( ) })}
Produk Qty Pendapatan % Std HPP % Real HPP Status
{item.product_name} {item.quantity_sold} {formatCurrency(item.revenue)} {stdHpp}% {realHpp}%
Subtotal {categoryName} {categoryTotalQty} {formatCurrency(categoryTotalRevenue)}
) })} {/* Grand Total */}
TOTAL KESELURUHAN {productSummary.totalQuantitySold ?? 0} {formatCurrency(productSummary.totalRevenue ?? 0)}
{/* Footer */}

© 2025 Apskel - Sistem POS Terpadu

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

) } export default DailyPOSReport