Compare commits

..

No commits in common. "8672f01261ae5edcc28aa0e50195aba47b2fd9fd" and "bd14adde2389e68368fd65bbfc7f66288b1f2004" have entirely different histories.

2 changed files with 41 additions and 173 deletions

View File

@ -23,7 +23,6 @@ const DailyPOSReport = () => {
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 = () => {
@ -102,212 +101,81 @@ const DailyPOSReport = () => {
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,
// Optimized canvas capture dengan scale lebih rendah
const canvas = await html2canvas(reportElement!, {
scale: 1.5, // Reduced from 2 to 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)
logging: false, // Disable logging for performance
removeContainer: true, // Clean up after capture
imageTimeout: 0, // No timeout for image loading
height: reportElement!.scrollHeight,
width: reportElement!.scrollWidth
})
console.log('Canvas captured:', canvas.width, 'x', canvas.height)
console.log('Generating PDF pages...')
// Compress canvas using JPEG with quality setting
const imgData = canvas.toDataURL('image/jpeg', 0.85) // JPEG with 85% quality instead of PNG
// Restore overflow
reportElement.style.overflow = originalOverflow
// Create PDF
// Create PDF with compression
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'mm',
format: 'a4',
compress: true
compress: true // Enable built-in compression
})
// A4 dimensions
const pdfWidth = 210
const pdfHeight = 297
const margin = 5 // Small margin to prevent cutoff
const imgWidth = 210
const pageHeight = 295
const imgHeight = (canvas.height * imgWidth) / canvas.width
let heightLeft = imgHeight
let position = 0
// Calculate scaling
const availableWidth = pdfWidth - 2 * margin
const availableHeight = pdfHeight - 2 * margin
// Add first page with compressed image
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, '', 'FAST') // Use FAST compression
heightLeft -= pageHeight
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) {
// Handle multiple pages if needed
while (heightLeft >= 0) {
position = heightLeft - imgHeight
pdf.addPage()
yPosition = margin
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, '', 'FAST')
heightLeft -= pageHeight
}
// 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
// Additional compression options
pdf.setProperties({
title: getReportTitle(),
subject: 'Transaction Report',
title: `${getReportTitle()}`,
subject: 'Daily Transaction Report',
author: 'Apskel POS System',
creator: 'Apskel'
})
// Generate filename
// Save with optimized settings
const fileName =
filterType === 'single'
? `laporan-transaksi-${formatDateForInput(selectedDate)}.pdf`
: `laporan-transaksi-${formatDateForInput(dateRange.startDate)}-to-${formatDateForInput(dateRange.endDate)}.pdf`
console.log('Saving PDF:', fileName)
pdf.save(fileName, {
returnPromise: true
})
// Save PDF
pdf.save(fileName)
console.log('PDF generated successfully!')
// Clean up canvas to free memory
canvas.width = canvas.height = 0
} catch (error) {
console.error('Error generating PDF:', error)
alert(`Terjadi kesalahan saat membuat PDF: ${error}`)
} finally {
// Reset loading state
setIsGeneratingPDF(false)
alert('Terjadi kesalahan saat membuat PDF. Pastikan jsPDF dan html2canvas sudah terinstall.')
}
}
const LoadingOverlay = ({ isVisible, message = 'Generating PDF...' }: { isVisible: boolean; message?: string }) => {
if (!isVisible) return null
return (
<div className='fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 backdrop-blur-sm'>
<div className='bg-white rounded-lg shadow-xl p-8 max-w-sm w-full mx-4'>
<div className='text-center'>
{/* Animated Spinner */}
<div className='inline-block animate-spin rounded-full h-12 w-12 border-4 border-gray-200 border-t-purple-600 mb-4'></div>
{/* Loading Message */}
<h3 className='text-lg font-semibold text-gray-800 mb-2'>{message}</h3>
<p className='text-sm text-gray-600'>Mohon tunggu, proses ini mungkin membutuhkan beberapa detik...</p>
{/* Progress Steps */}
<div className='mt-6 space-y-2'>
<div className='flex items-center text-xs text-gray-500'>
<div className='w-2 h-2 bg-green-500 rounded-full mr-2 flex-shrink-0'></div>
<span>Capturing report content</span>
</div>
<div className='flex items-center text-xs text-gray-500'>
<div className='w-2 h-2 bg-blue-500 rounded-full mr-2 flex-shrink-0 animate-pulse'></div>
<span>Processing pages</span>
</div>
<div className='flex items-center text-xs text-gray-500'>
<div className='w-2 h-2 bg-gray-300 rounded-full mr-2 flex-shrink-0'></div>
<span>Finalizing PDF</span>
</div>
</div>
</div>
</div>
</div>
)
}
return (
<div className='min-h-screen'>
<LoadingOverlay isVisible={isGeneratingPDF} message='Membuat PDF Laporan...' />
{/* Control Panel */}
<ReportGeneratorComponent
// Props wajib

View File

@ -61,7 +61,7 @@ const ReportHeader: FC<ReportHeaderProps> = ({
sx={{
fontWeight: 600,
mt: 1,
color: '#222222'
color: theme.palette.text.primary
}}
>
{outlet.name}
@ -72,7 +72,7 @@ const ReportHeader: FC<ReportHeaderProps> = ({
variant='body2'
sx={{
mt: 1,
color: '#222222'
color: theme.palette.text.secondary
}}
>
{outlet.address}
@ -88,21 +88,21 @@ const ReportHeader: FC<ReportHeaderProps> = ({
component='h3'
sx={{
fontWeight: 700,
color: '#222222'
color: theme.palette.text.primary
}}
>
{reportTitle}
</Typography>
{periode && (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 2, mt: 2 }}>
<Typography variant='body2' sx={{ color: '#222222' }}>
<Typography variant='body2' sx={{ color: theme.palette.text.secondary }}>
{periode}
</Typography>
</Box>
)}
{reportSubtitle && (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 2, mt: 2 }}>
<Typography variant='body2' sx={{ color: '#222222' }}>
<Typography variant='body2' sx={{ color: theme.palette.text.secondary }}>
{reportSubtitle}
</Typography>
</Box>