add excel pdf
This commit is contained in:
@@ -8,6 +8,8 @@ import {
|
||||
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'
|
||||
@@ -16,6 +18,8 @@ import React, { useEffect, useRef, useState } from 'react'
|
||||
const DailyPOSReport = () => {
|
||||
const reportRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
const [isGeneratingExcel, setIsGeneratingExcel] = useState(false)
|
||||
|
||||
const [now, setNow] = useState(new Date())
|
||||
const [selectedDate, setSelectedDate] = useState(new Date())
|
||||
const [dateRange, setDateRange] = useState({
|
||||
@@ -112,536 +116,22 @@ const DailyPOSReport = () => {
|
||||
}
|
||||
|
||||
const handleGeneratePDF = async () => {
|
||||
const reportElement = reportRef.current
|
||||
if (!reportElement) {
|
||||
alert('Report element tidak ditemukan')
|
||||
return
|
||||
}
|
||||
|
||||
setIsGeneratingPDF(true)
|
||||
|
||||
try {
|
||||
const jsPDF = (await import('jspdf')).default
|
||||
const html2canvas = (await import('html2canvas')).default
|
||||
const autoTable = (await import('jspdf-autotable')).default
|
||||
|
||||
const pdf = new jsPDF({
|
||||
orientation: 'portrait',
|
||||
unit: 'mm',
|
||||
format: 'a4',
|
||||
compress: true
|
||||
await generatePDF({
|
||||
reportRef,
|
||||
outlet,
|
||||
profitLoss,
|
||||
products,
|
||||
paymentAnalytics,
|
||||
category,
|
||||
productSummary,
|
||||
categorySummary,
|
||||
filterType,
|
||||
selectedDate,
|
||||
dateRange,
|
||||
now
|
||||
})
|
||||
|
||||
// Capture header section only (tanpa tabel kategori)
|
||||
const headerElement = reportElement.querySelector('.report-header-section') as HTMLElement
|
||||
if (headerElement) {
|
||||
const headerCanvas = await html2canvas(headerElement, {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
backgroundColor: '#ffffff',
|
||||
logging: false,
|
||||
windowWidth: 794
|
||||
})
|
||||
|
||||
const headerImgWidth = 190
|
||||
const headerImgHeight = (headerCanvas.height * headerImgWidth) / headerCanvas.width
|
||||
const headerImgData = headerCanvas.toDataURL('image/jpeg', 0.95)
|
||||
|
||||
pdf.addImage(headerImgData, 'JPEG', 10, 10, headerImgWidth, headerImgHeight)
|
||||
}
|
||||
|
||||
let currentY = 80 // Start position after header
|
||||
|
||||
// Add summary sections with autoTable
|
||||
pdf.setFontSize(PDF_FONT_SIZES.heading)
|
||||
pdf.setTextColor(54, 23, 94)
|
||||
pdf.setFont('helvetica', 'bold')
|
||||
pdf.text('Ringkasan', 14, currentY)
|
||||
currentY += 15
|
||||
|
||||
// Summary table - TETAP PLAIN (TANPA BORDER)
|
||||
autoTable(pdf, {
|
||||
startY: currentY,
|
||||
head: [],
|
||||
body: [
|
||||
['Total Penjualan', formatCurrency(profitLoss?.summary.total_revenue ?? 0)],
|
||||
['Total Diskon', formatCurrency(profitLoss?.summary.total_discount ?? 0)],
|
||||
['Total Pajak', formatCurrency(profitLoss?.summary.total_tax ?? 0)],
|
||||
['Total', formatCurrency(profitLoss?.summary.total_revenue ?? 0)]
|
||||
],
|
||||
theme: 'grid',
|
||||
styles: {
|
||||
fontSize: PDF_FONT_SIZES.subheading,
|
||||
cellPadding: PDF_SPACING.cellPadding,
|
||||
lineColor: [0, 0, 0]
|
||||
},
|
||||
columnStyles: {
|
||||
0: { fontStyle: 'normal', textColor: [60, 60, 60] },
|
||||
1: { halign: 'right', fontStyle: 'bold', textColor: [60, 60, 60] }
|
||||
},
|
||||
margin: { left: 14, right: 14 }
|
||||
})
|
||||
|
||||
currentY = (pdf as any).lastAutoTable.finalY + 20
|
||||
|
||||
// Invoice section - TETAP PLAIN (TANPA BORDER)
|
||||
pdf.setFontSize(PDF_FONT_SIZES.heading)
|
||||
pdf.text('Invoice', 14, currentY)
|
||||
currentY += 15
|
||||
|
||||
autoTable(pdf, {
|
||||
startY: currentY,
|
||||
head: [],
|
||||
body: [['Total Invoice', String(profitLoss?.summary.total_orders ?? 0)]],
|
||||
theme: 'grid',
|
||||
styles: {
|
||||
lineColor: [0, 0, 0],
|
||||
fontSize: PDF_FONT_SIZES.subheading,
|
||||
cellPadding: PDF_SPACING.cellPadding
|
||||
},
|
||||
columnStyles: {
|
||||
0: { fontStyle: 'normal', textColor: [60, 60, 60] },
|
||||
1: { halign: 'right', fontStyle: 'bold', textColor: [60, 60, 60] }
|
||||
},
|
||||
margin: { left: 14, right: 14 }
|
||||
})
|
||||
|
||||
pdf.addPage()
|
||||
currentY = 20
|
||||
|
||||
// === TREN PENJUALAN (berdasarkan data sales analytics) ===
|
||||
// if (profitLoss7Days?.data && profitLoss7Days.data.length > 0) {
|
||||
// pdf.setFontSize(PDF_FONT_SIZES.heading)
|
||||
// pdf.text('Tren Penjualan 7 Hari Terakhir', 14, currentY)
|
||||
// currentY += 10
|
||||
|
||||
// // Create canvas for chart
|
||||
// const canvas = document.createElement('canvas')
|
||||
// canvas.width = 800
|
||||
// canvas.height = 400
|
||||
// const ctx = canvas.getContext('2d')
|
||||
|
||||
// if (ctx) {
|
||||
// const { Chart, registerables } = await import('chart.js')
|
||||
// Chart.register(...registerables)
|
||||
|
||||
// // Prepare chart data
|
||||
// const chartLabels = profitLoss7Days.data.map(day =>
|
||||
// new Date(day.date).toLocaleDateString('id-ID', { day: '2-digit', month: 'short' })
|
||||
// )
|
||||
// const revenueData = profitLoss7Days.data.map(day => day.revenue)
|
||||
// const ordersData = profitLoss7Days.data.map(day => day.orders)
|
||||
|
||||
// // Create chart
|
||||
// new Chart(ctx, {
|
||||
// type: 'bar',
|
||||
// data: {
|
||||
// labels: chartLabels,
|
||||
// datasets: [
|
||||
// {
|
||||
// label: 'Total Penjualan (Rp)',
|
||||
// data: revenueData,
|
||||
// backgroundColor: 'rgba(54, 23, 94, 0.8)',
|
||||
// borderColor: 'rgba(54, 23, 94, 1)',
|
||||
// borderWidth: 1,
|
||||
// yAxisID: 'y'
|
||||
// },
|
||||
// {
|
||||
// label: 'Jumlah Invoice',
|
||||
// data: ordersData,
|
||||
// type: 'line',
|
||||
// borderColor: 'rgba(59, 130, 246, 1)',
|
||||
// backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
// borderWidth: 2,
|
||||
// tension: 0.4,
|
||||
// yAxisID: 'y1',
|
||||
// pointRadius: 4,
|
||||
// pointBackgroundColor: 'rgba(59, 130, 246, 1)'
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// options: {
|
||||
// responsive: false,
|
||||
// animation: false,
|
||||
// plugins: {
|
||||
// legend: {
|
||||
// display: true,
|
||||
// position: 'top',
|
||||
// labels: {
|
||||
// font: { size: 12 },
|
||||
// padding: 15
|
||||
// }
|
||||
// },
|
||||
// title: {
|
||||
// display: false
|
||||
// }
|
||||
// },
|
||||
// scales: {
|
||||
// y: {
|
||||
// type: 'linear',
|
||||
// position: 'left',
|
||||
// beginAtZero: true,
|
||||
// ticks: {
|
||||
// callback: function (value) {
|
||||
// if (typeof value !== 'number') return ''
|
||||
// return 'Rp ' + (value / 1000).toFixed(0) + 'k'
|
||||
// }
|
||||
// },
|
||||
|
||||
// title: {
|
||||
// display: true,
|
||||
// text: 'Total Penjualan'
|
||||
// }
|
||||
// },
|
||||
// y1: {
|
||||
// type: 'linear',
|
||||
// position: 'right',
|
||||
// beginAtZero: true,
|
||||
// grid: {
|
||||
// drawOnChartArea: false
|
||||
// },
|
||||
// title: {
|
||||
// display: true,
|
||||
// text: 'Jumlah Invoice'
|
||||
// }
|
||||
// },
|
||||
// x: {
|
||||
// grid: {
|
||||
// display: false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
|
||||
// // Wait for chart to render
|
||||
// await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
// // Convert chart to image
|
||||
// const chartImage = canvas.toDataURL('image/png', 1.0)
|
||||
// const chartWidth = 180
|
||||
// const chartHeight = 90
|
||||
|
||||
// pdf.addImage(chartImage, 'PNG', 15, currentY, chartWidth, chartHeight)
|
||||
// currentY += chartHeight + 10
|
||||
|
||||
// // Add summary table below chart
|
||||
// autoTable(pdf, {
|
||||
// startY: currentY,
|
||||
// head: [['Total Invoice', 'Total Penjualan']],
|
||||
// body: [
|
||||
// [String(profitLoss7Days.summary.total_orders), formatCurrency(profitLoss7Days.summary.total_revenue)]
|
||||
// ],
|
||||
// theme: 'grid',
|
||||
// styles: {
|
||||
// fontSize: PDF_FONT_SIZES.tableContent,
|
||||
// cellPadding: PDF_SPACING.cellPadding,
|
||||
// lineColor: [0, 0, 0],
|
||||
// lineWidth: 0.1,
|
||||
// halign: 'center'
|
||||
// },
|
||||
// headStyles: {
|
||||
// fillColor: [54, 23, 94],
|
||||
// textColor: 255,
|
||||
// fontStyle: 'bold',
|
||||
// fontSize: PDF_FONT_SIZES.tableHeader
|
||||
// },
|
||||
// margin: { left: 14, right: 14 }
|
||||
// })
|
||||
|
||||
// currentY = (pdf as any).lastAutoTable.finalY + 20
|
||||
// }
|
||||
// }
|
||||
|
||||
// Payment Method Summary - DENGAN BORDER HITAM
|
||||
pdf.setFontSize(PDF_FONT_SIZES.heading)
|
||||
pdf.text('Ringkasan Metode Pembayaran', 14, currentY)
|
||||
currentY += 15
|
||||
|
||||
const paymentBody =
|
||||
paymentAnalytics?.data?.map(payment => [
|
||||
payment.payment_method_name,
|
||||
String(payment.order_count),
|
||||
formatCurrency(payment.total_amount),
|
||||
`${(payment.percentage ?? 0).toFixed(1)}%`
|
||||
]) || []
|
||||
|
||||
autoTable(pdf, {
|
||||
startY: currentY,
|
||||
head: [['Metode', 'Jumlah', 'Total', 'Persentase']],
|
||||
body: paymentBody,
|
||||
foot: [
|
||||
[
|
||||
'TOTAL',
|
||||
String(paymentAnalytics?.summary.total_orders ?? 0),
|
||||
formatCurrency(paymentAnalytics?.summary.total_amount ?? 0),
|
||||
''
|
||||
]
|
||||
],
|
||||
theme: 'grid',
|
||||
styles: {
|
||||
fontSize: PDF_FONT_SIZES.tableContent,
|
||||
cellPadding: PDF_SPACING.cellPadding,
|
||||
lineColor: [0, 0, 0],
|
||||
lineWidth: 0.1
|
||||
},
|
||||
headStyles: {
|
||||
fillColor: [54, 23, 94],
|
||||
textColor: 255,
|
||||
fontStyle: 'bold',
|
||||
fontSize: PDF_FONT_SIZES.tableHeader,
|
||||
lineColor: [0, 0, 0],
|
||||
lineWidth: 0.1
|
||||
},
|
||||
footStyles: {
|
||||
fillColor: [220, 220, 220],
|
||||
textColor: [60, 60, 60],
|
||||
fontStyle: 'bold',
|
||||
fontSize: PDF_FONT_SIZES.tableFooter,
|
||||
lineColor: [0, 0, 0],
|
||||
lineWidth: 0.1,
|
||||
halign: 'center'
|
||||
},
|
||||
columnStyles: {
|
||||
1: { halign: 'center' },
|
||||
2: { halign: 'right' },
|
||||
3: { halign: 'right' }
|
||||
},
|
||||
didParseCell: data => {
|
||||
if (data.section === 'foot') {
|
||||
if (data.column.index === 0) {
|
||||
data.cell.styles.halign = 'left'
|
||||
} else if (data.column.index === 1) {
|
||||
data.cell.styles.halign = 'center'
|
||||
} else if (data.column.index === 2) {
|
||||
data.cell.styles.halign = 'right'
|
||||
}
|
||||
}
|
||||
},
|
||||
margin: { left: 14, right: 14 }
|
||||
})
|
||||
|
||||
currentY = (pdf as any).lastAutoTable.finalY + 20
|
||||
|
||||
// Category Summary - DENGAN BORDER HITAM
|
||||
// Category Summary - DENGAN BORDER HITAM
|
||||
pdf.setFontSize(PDF_FONT_SIZES.heading)
|
||||
pdf.text('Ringkasan Kategori', 14, currentY)
|
||||
currentY += 15
|
||||
|
||||
const categoryBody =
|
||||
category?.data?.map(c => [c.category_name, String(c.total_quantity), formatCurrency(c.total_revenue)]) || []
|
||||
|
||||
autoTable(pdf, {
|
||||
startY: currentY,
|
||||
head: [['Nama', 'Qty', 'Pendapatan']],
|
||||
body: categoryBody,
|
||||
foot: [
|
||||
['TOTAL', String(categorySummary?.totalQuantity ?? 0), formatCurrency(categorySummary?.totalRevenue ?? 0)]
|
||||
],
|
||||
theme: 'grid',
|
||||
// TAMBAHKAN INI: paksa semua row di satu page, jangan split
|
||||
showFoot: 'lastPage', // footer cuma muncul di halaman terakhir
|
||||
tableWidth: 'auto',
|
||||
styles: {
|
||||
fontSize: PDF_FONT_SIZES.tableContent,
|
||||
cellPadding: PDF_SPACING.cellPadding,
|
||||
lineColor: [0, 0, 0],
|
||||
lineWidth: 0.1
|
||||
},
|
||||
headStyles: {
|
||||
fillColor: [54, 23, 94],
|
||||
textColor: 255,
|
||||
fontStyle: 'bold',
|
||||
fontSize: PDF_FONT_SIZES.tableHeader,
|
||||
lineColor: [0, 0, 0],
|
||||
lineWidth: 0.1
|
||||
},
|
||||
footStyles: {
|
||||
fillColor: [220, 220, 220],
|
||||
textColor: [60, 60, 60],
|
||||
fontStyle: 'bold',
|
||||
fontSize: PDF_FONT_SIZES.tableFooter,
|
||||
lineColor: [0, 0, 0],
|
||||
lineWidth: 0.1,
|
||||
halign: 'center'
|
||||
},
|
||||
columnStyles: {
|
||||
1: { halign: 'center' },
|
||||
2: { halign: 'right' }
|
||||
},
|
||||
didParseCell: data => {
|
||||
if (data.section === 'foot') {
|
||||
if (data.column.index === 0) {
|
||||
data.cell.styles.halign = 'left'
|
||||
} else if (data.column.index === 1) {
|
||||
data.cell.styles.halign = 'center'
|
||||
} else if (data.column.index === 2) {
|
||||
data.cell.styles.halign = 'right'
|
||||
}
|
||||
}
|
||||
},
|
||||
margin: { left: 14, right: 14 }
|
||||
})
|
||||
|
||||
currentY = (pdf as any).lastAutoTable.finalY + 20
|
||||
|
||||
// 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<string, any[]>
|
||||
) || {}
|
||||
|
||||
// Add new page for product details
|
||||
pdf.addPage()
|
||||
currentY = 20
|
||||
|
||||
pdf.setFontSize(PDF_FONT_SIZES.heading)
|
||||
pdf.text('Ringkasan Item Per Kategori', 14, currentY)
|
||||
currentY += 10
|
||||
|
||||
// Loop through each category
|
||||
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
|
||||
})
|
||||
.forEach((categoryName, index) => {
|
||||
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)
|
||||
|
||||
const productBody = categoryProducts.map(item => [
|
||||
item.product_name,
|
||||
String(item.quantity_sold),
|
||||
formatCurrency(item.revenue)
|
||||
])
|
||||
|
||||
const estimatedHeight = (productBody.length + 3) * 12
|
||||
if (currentY + estimatedHeight > 270) {
|
||||
pdf.addPage()
|
||||
currentY = 20
|
||||
}
|
||||
|
||||
pdf.setFontSize(PDF_FONT_SIZES.subheading)
|
||||
pdf.setFont('helvetica', 'bold')
|
||||
pdf.setTextColor(54, 23, 94)
|
||||
pdf.text(`${index + 1}. ${categoryName.toUpperCase()}`, 16, currentY)
|
||||
currentY += 15
|
||||
|
||||
autoTable(pdf, {
|
||||
startY: currentY,
|
||||
head: [['Produk', 'Qty', 'Pendapatan']],
|
||||
body: productBody,
|
||||
foot: [[`Subtotal ${categoryName}`, String(categoryTotalQty), formatCurrency(categoryTotalRevenue)]],
|
||||
showFoot: 'lastPage', // ← TAMBAHKAN INI: subtotal cuma muncul di akhir kategori
|
||||
theme: 'grid',
|
||||
styles: {
|
||||
fontSize: PDF_FONT_SIZES.tableContent,
|
||||
cellPadding: PDF_SPACING.cellPadding,
|
||||
lineColor: [0, 0, 0],
|
||||
lineWidth: 0.1
|
||||
},
|
||||
headStyles: {
|
||||
fillColor: [54, 23, 94],
|
||||
textColor: 255,
|
||||
fontStyle: 'bold',
|
||||
fontSize: PDF_FONT_SIZES.tableHeader,
|
||||
lineColor: [0, 0, 0],
|
||||
lineWidth: 0.1
|
||||
},
|
||||
footStyles: {
|
||||
fillColor: [200, 200, 200],
|
||||
textColor: [60, 60, 60],
|
||||
fontStyle: 'bold',
|
||||
fontSize: PDF_FONT_SIZES.tableFooter,
|
||||
lineColor: [0, 0, 0],
|
||||
lineWidth: 0.1,
|
||||
halign: 'center'
|
||||
},
|
||||
columnStyles: {
|
||||
0: { cellWidth: 90 },
|
||||
1: { halign: 'center', cellWidth: 40 },
|
||||
2: { halign: 'right', cellWidth: 52 }
|
||||
},
|
||||
didParseCell: data => {
|
||||
if (data.section === 'foot') {
|
||||
if (data.column.index === 0) {
|
||||
data.cell.styles.halign = 'left'
|
||||
} else if (data.column.index === 1) {
|
||||
data.cell.styles.halign = 'center'
|
||||
} else if (data.column.index === 2) {
|
||||
data.cell.styles.halign = 'right'
|
||||
}
|
||||
}
|
||||
},
|
||||
margin: { left: 14, right: 14 }
|
||||
})
|
||||
|
||||
currentY = (pdf as any).lastAutoTable.finalY + 15
|
||||
})
|
||||
|
||||
// Grand Total - DENGAN BORDER HITAM
|
||||
if (currentY > 250) {
|
||||
pdf.addPage()
|
||||
currentY = 20
|
||||
}
|
||||
|
||||
autoTable(pdf, {
|
||||
startY: currentY,
|
||||
head: [],
|
||||
body: [
|
||||
['TOTAL KESELURUHAN', String(productSummary.totalQuantitySold), formatCurrency(productSummary.totalRevenue)]
|
||||
],
|
||||
theme: 'grid',
|
||||
styles: {
|
||||
fontSize: PDF_FONT_SIZES.grandTotal,
|
||||
cellPadding: 6,
|
||||
fontStyle: 'bold',
|
||||
textColor: [54, 23, 94],
|
||||
lineColor: [0, 0, 0],
|
||||
lineWidth: 0.2
|
||||
},
|
||||
columnStyles: {
|
||||
0: { cellWidth: 90 },
|
||||
1: { halign: 'center', cellWidth: 40 },
|
||||
2: { halign: 'right', cellWidth: 52 }
|
||||
},
|
||||
margin: { left: 14, right: 14 }
|
||||
})
|
||||
|
||||
// Footer
|
||||
const pageCount = pdf.getNumberOfPages()
|
||||
for (let i = 1; i <= pageCount; i++) {
|
||||
pdf.setPage(i)
|
||||
pdf.setFontSize(11)
|
||||
pdf.setTextColor(120, 120, 120)
|
||||
pdf.text('© 2025 Apskel - Sistem POS Terpadu', 14, 287)
|
||||
pdf.text(`Dicetak pada: ${now.toLocaleDateString('id-ID')}`, 190, 287, { align: 'right' })
|
||||
}
|
||||
|
||||
const fileName =
|
||||
filterType === 'single'
|
||||
? `laporan-transaksi-${formatDateForInput(selectedDate)}.pdf`
|
||||
: `laporan-transaksi-${formatDateForInput(dateRange.startDate)}-to-${formatDateForInput(dateRange.endDate)}.pdf`
|
||||
|
||||
pdf.save(fileName)
|
||||
} catch (error) {
|
||||
console.error('Error generating PDF:', error)
|
||||
alert(`Terjadi kesalahan saat membuat PDF: ${error}`)
|
||||
@@ -650,6 +140,29 @@ const DailyPOSReport = () => {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -707,6 +220,8 @@ const DailyPOSReport = () => {
|
||||
onSingleDateChange={setSelectedDate}
|
||||
onDateRangeChange={setDateRange}
|
||||
onGeneratePDF={handleGeneratePDF}
|
||||
onGenerateExcel={handleGenerateExcel}
|
||||
isGeneratingExcel={isGeneratingExcel}
|
||||
/>
|
||||
|
||||
<div ref={reportRef} className='max-w-4xl mx-auto bg-white min-h-[297mm]' style={{ width: '210mm' }}>
|
||||
|
||||
Reference in New Issue
Block a user