diff --git a/src/app/[lang]/(dashboard)/(private)/dashboards/daily-report/page.tsx b/src/app/[lang]/(dashboard)/(private)/dashboards/daily-report/page.tsx
index 386171d..210223e 100644
--- a/src/app/[lang]/(dashboard)/(private)/dashboards/daily-report/page.tsx
+++ b/src/app/[lang]/(dashboard)/(private)/dashboards/daily-report/page.tsx
@@ -22,10 +22,9 @@ const DailyPOSReport = () => {
startDate: new Date(),
endDate: new Date()
})
- const [filterType, setFilterType] = useState<'single' | 'range'>('single') // 'single' or 'range'
+ const [filterType, setFilterType] = useState<'single' | 'range'>('single')
const [isGeneratingPDF, setIsGeneratingPDF] = useState(false)
- // Use selectedDate for single date filter, or dateRange for range filter
const getDateParams = () => {
if (filterType === 'single') {
return {
@@ -66,12 +65,10 @@ const DailyPOSReport = () => {
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)}`
@@ -80,13 +77,10 @@ const DailyPOSReport = () => {
}
}
- // 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`
}
}
@@ -99,27 +93,17 @@ const DailyPOSReport = () => {
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,
@@ -132,18 +116,12 @@ const DailyPOSReport = () => {
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',
@@ -151,43 +129,30 @@ const DailyPOSReport = () => {
compress: true
})
- // A4 dimensions
const pdfWidth = 210
const pdfHeight = 297
- const margin = 5 // Small margin to prevent cutoff
+ const margin = 5
- // 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')
@@ -198,41 +163,21 @@ const DailyPOSReport = () => {
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
- )
+ tempCtx.drawImage(canvas, 0, sourceY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight)
- // 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',
@@ -240,23 +185,16 @@ const DailyPOSReport = () => {
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)
}
}
@@ -268,15 +206,9 @@ const DailyPOSReport = () => {
- {/* Animated Spinner */}
-
- {/* Loading Message */}
{message}
-
Mohon tunggu, proses ini mungkin membutuhkan beberapa detik...
-
- {/* Progress Steps */}
@@ -297,12 +229,25 @@ const DailyPOSReport = () => {
)
}
+ // 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 (
- {/* Control Panel */}
+
{
onSingleDateChange={setSelectedDate}
onDateRangeChange={setDateRange}
onGeneratePDF={handleGeneratePDF}
- // Props opsional
- // isGenerating={isGenerating}
- // customQuickActions={customQuickActions}
- // labels={customLabels}
/>
- {/* Report Template */}
- {/* Header */}
{
{/* Performance Summary */}
-
+
Ringkasan
-
-
-
Total Penjualan
-
+
+
+ Total Penjualan
+
{formatCurrency(profitLoss?.summary.total_revenue ?? 0)}
-
-
Total Diskon
-
+
+ Total Diskon
+
{formatCurrency(profitLoss?.summary.total_discount ?? 0)}
-
-
Total Pajak
-
+
+ Total Pajak
+
{formatCurrency(profitLoss?.summary.total_tax ?? 0)}
-
-
Total
-
+
+ Total
+
{formatCurrency(profitLoss?.summary.total_revenue ?? 0)}
+ {/* Invoice */}
-
+
Invoice
-
-
-
Total Invoice
-
{profitLoss?.summary.total_orders ?? 0}
+
+
+ Total Invoice
+ {profitLoss?.summary.total_orders ?? 0}
{/* Payment Method Summary */}
-
+
Ringkasan Metode Pembayaran
@@ -386,20 +326,20 @@ const DailyPOSReport = () => {
- | Metode Pembayaran |
- Tipe |
- Jumlah Order |
- Total Amount |
- Persentase |
+ Metode Pembayaran |
+ Tipe |
+ Jumlah Order |
+ Total Amount |
+ Persentase |
{paymentAnalytics?.data?.map((payment, index) => (
- | {payment.payment_method_name} |
-
+ | {payment.payment_method_name} |
+
{
{payment.payment_method_type.toUpperCase()}
|
- {payment.order_count} |
-
+ | {payment.order_count} |
+
{formatCurrency(payment.total_amount)}
|
-
+ |
{(payment.percentage ?? 0).toFixed(1)}%
|
@@ -420,13 +360,13 @@ const DailyPOSReport = () => {
- | TOTAL |
- |
- {paymentAnalytics?.summary.total_orders ?? 0} |
-
+ | TOTAL |
+ |
+ {paymentAnalytics?.summary.total_orders ?? 0} |
+
{formatCurrency(paymentAnalytics?.summary.total_amount ?? 0)}
|
- |
+ |
@@ -435,7 +375,7 @@ const DailyPOSReport = () => {
{/* Category Summary */}
-
+
Ringkasan Kategori
@@ -443,17 +383,17 @@ const DailyPOSReport = () => {
- | Nama |
- Qty |
- Pendapatan |
+ Nama |
+ Qty |
+ Pendapatan |
{category?.data?.map((c, index) => (
- | {c.category_name} |
- {c.total_quantity} |
-
+ | {c.category_name} |
+ {c.total_quantity} |
+
{formatCurrency(c.total_revenue)}
|
@@ -461,9 +401,9 @@ const DailyPOSReport = () => {
- | TOTAL |
- {categorySummary?.totalQuantity ?? 0} |
-
+ | TOTAL |
+ {categorySummary?.totalQuantity ?? 0} |
+
{formatCurrency(categorySummary?.totalRevenue ?? 0)}
|
@@ -472,134 +412,129 @@ const DailyPOSReport = () => {
- {/* Transaction Summary */}
+ {/* Product Summary - Dipisah per kategori dengan tabel terpisah */}
-
- Ringkasan Item
+
+ Ringkasan Item Per Kategori
-
-
-
-
-
-
-
-
-
-
- | Produk |
- Qty |
- Pendapatan |
-
-
-
- {(() => {
- // 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
- ) || {}
+
+ {Object.keys(groupedProducts)
+ .sort()
+ .map(categoryName => {
+ const categoryProducts = groupedProducts[categoryName]
+ const categoryTotalQty = categoryProducts.reduce((sum, item) => sum + (item.quantity_sold || 0), 0)
+ const categoryTotalRevenue = categoryProducts.reduce((sum, item) => sum + (item.revenue || 0), 0)
- const rows: JSX.Element[] = []
- let globalIndex = 0
+ return (
+
+ {/* Category Title */}
+
+ {categoryName.toUpperCase()}
+
- // Sort categories alphabetically
- Object.keys(groupedProducts)
- .sort()
- .forEach(categoryName => {
- const categoryProducts = groupedProducts[categoryName]
-
- // Category header row
- rows.push(
-
- |
- {categoryName.toUpperCase()}
- |
- |
- |
+ {/* Category Table */}
+
+
+
+
+
+
+
+
+
+ | Produk |
+ Qty |
+ Pendapatan |
- )
-
- // Product rows for this category
- categoryProducts.forEach((item, index) => {
- globalIndex++
- rows.push(
+
+
+ {categoryProducts.map((item, index) => (
- |
+ |
{item.product_name}
|
-
+ |
{item.quantity_sold}
|
-
+ |
{formatCurrency(item.revenue)}
|
- )
- })
-
- // Category subtotal row
- const categoryTotalQty = categoryProducts.reduce(
- (sum, item) => sum + (item.quantity_sold || 0),
- 0
- )
- const categoryTotalRevenue = categoryProducts.reduce(
- (sum, item) => sum + (item.revenue || 0),
- 0
- )
-
- rows.push(
-
- |
+ ))}
+ |
+
+
+ |
Subtotal {categoryName}
|
-
+ |
{categoryTotalQty}
|
-
+ |
{formatCurrency(categoryTotalRevenue)}
|
- )
- })
+
+
+
+
+ )
+ })}
- return rows
- })()}
-
+ {/* Grand Total */}
+
+
-
- | TOTAL KESELURUHAN |
-
+ |
+ |
+ TOTAL KESELURUHAN
+ |
+
{productSummary.totalQuantitySold ?? 0}
|
-
+ |
{formatCurrency(productSummary.totalRevenue ?? 0)}
|
@@ -611,7 +546,7 @@ const DailyPOSReport = () => {
{/* Footer */}
-
+
© 2025 Apskel - Sistem POS Terpadu
Dicetak pada: {now.toLocaleDateString('id-ID')}
diff --git a/src/views/dashboards/daily-report/report-header.tsx b/src/views/dashboards/daily-report/report-header.tsx
index 955fa4d..b920096 100644
--- a/src/views/dashboards/daily-report/report-header.tsx
+++ b/src/views/dashboards/daily-report/report-header.tsx
@@ -38,8 +38,8 @@ const ReportHeader: FC
= ({
= ({
{periode && (