import jsPDF from 'jspdf'; import { toPng } from 'html-to-image'; import toast from 'react-hot-toast'; import { formatDate } from '@/lib/helper'; import { DashboardFilterType } from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema'; import { DashboardExportChartsRef } from '@/components/pages/dashboard/export/DashboardExportCharts'; import { DashboardExportStatsRef } from '@/components/pages/dashboard/export/DashboardExportStats'; interface DashboardPDFExportParams { filterValues: DashboardFilterType; allStatsRef: React.RefObject; allChartsRef: React.RefObject; setExporting: (value: boolean) => void; } export const generateDashboardPDF = async ({ filterValues, allStatsRef, allChartsRef, setExporting, }: DashboardPDFExportParams): Promise => { try { setExporting(true); toast.loading('Generating PDF...', { id: 'export-pdf' }); // Wait for DOM to update await new Promise((resolve) => setTimeout(resolve, 200)); const pdf = new jsPDF('p', 'mm', 'a4'); const pageWidth = pdf.internal.pageSize.getWidth(); const pageHeight = pdf.internal.pageSize.getHeight(); const margin = 10; let yPosition = margin; // Add title pdf.setFontSize(16); pdf.setFont('helvetica', 'bold'); pdf.text('Dashboard Produksi', margin, yPosition); yPosition += 10; // Add filter information (horizontal layout) pdf.setFontSize(6); pdf.setFont('helvetica', 'normal'); const filterItems: string[] = []; // Period if (filterValues.startDate || filterValues.endDate) { const periodText = `Periode: ${ filterValues.startDate ? formatDate(filterValues.startDate, 'DD MMM YYYY') : '-' } s.d ${ filterValues.endDate ? formatDate(filterValues.endDate, 'DD MMM YYYY') : '-' }`; filterItems.push(periodText); } // Analysis Mode const analysisModeText = `Analysis Mode: ${ filterValues.analysisMode === 'OVERVIEW' ? 'Performance Overview' : 'Performance Comparison' }`; filterItems.push(analysisModeText); // Comparison Type (only for COMPARISON mode) if ( filterValues.analysisMode === 'COMPARISON' && filterValues.comparisonType ) { const comparisonTypeLabel = filterValues.comparisonType === 'FARM' ? 'Farm' : filterValues.comparisonType === 'FLOCK' ? 'Flock' : filterValues.comparisonType === 'KANDANG' ? 'Kandang' : filterValues.comparisonType; filterItems.push(`Compared By: ${comparisonTypeLabel}`); } // Farm if (filterValues.location) { const locationText = Array.isArray(filterValues.location) ? filterValues.location.map((loc) => loc.label).join(', ') : filterValues.location.label; filterItems.push(`Farm: ${locationText || '-'}`); } // Flock if ( filterValues.flock && (Array.isArray(filterValues.flock) ? filterValues.flock.length > 0 : filterValues.flock) ) { const flockText = Array.isArray(filterValues.flock) ? filterValues.flock.map((f) => f.label).join(', ') : filterValues.flock.label; filterItems.push(`Flock: ${flockText || '-'}`); } // Kandang if ( filterValues.kandang && (Array.isArray(filterValues.kandang) ? filterValues.kandang.length > 0 : filterValues.kandang) ) { const kandangText = Array.isArray(filterValues.kandang) ? filterValues.kandang.map((k) => k.label).join(', ') : filterValues.kandang.label; filterItems.push(`Kandang: ${kandangText || '-'}`); } // Generated timestamp filterItems.push(`Dicetak: ${formatDate(new Date(), 'DD MMM YYYY HH:mm')}`); // Render filter items horizontally with word wrap and gray background const maxWidth = pageWidth - 2 * margin; let currentLine = ''; const lines: string[] = []; // First pass: calculate all lines filterItems.forEach((item, index) => { const separator = index > 0 ? ' | ' : ''; const testLine = currentLine + separator + item; const testWidth = pdf.getTextWidth(testLine); if (testWidth > maxWidth && currentLine !== '') { lines.push(currentLine); currentLine = item; } else { currentLine = testLine; } }); // Add last line if (currentLine) { lines.push(currentLine); } // Calculate background dimensions const lineHeight = 3; const padding = 1; const backgroundHeight = lines.length * lineHeight + padding * 2; // Draw gray background pdf.setFillColor(240, 240, 240); // Light gray (RGB: 240, 240, 240) pdf.rect( margin - padding, yPosition - padding - 2, pageWidth - 2 * margin + padding * 2, backgroundHeight, 'F' ); // Render text on top of background lines.forEach((line, index) => { pdf.text(line, margin, yPosition); if (index < lines.length - 1) { yPosition += lineHeight; } }); yPosition += 10; // Capture and add stats if available if (allStatsRef.current) { const statsContainer = allStatsRef.current.getContainerRef(); if (statsContainer) { const statsImage = await toPng(statsContainer, { quality: 1, pixelRatio: 2, }); const statsImgProps = pdf.getImageProperties(statsImage); const statsWidth = pageWidth - 2 * margin; const statsHeight = (statsImgProps.height * statsWidth) / statsImgProps.width; // Check if we need a new page if (yPosition + statsHeight > pageHeight - margin) { pdf.addPage(); yPosition = margin; } pdf.addImage( statsImage, 'PNG', margin, yPosition, statsWidth, statsHeight ); yPosition += statsHeight + 10; } } if (allChartsRef.current) { // Get all individual chart refs const chartRefs = allChartsRef.current.getChartRefs(); // Capture each chart separately and add to PDF for (let i = 0; i < chartRefs.length; i++) { const { ref: chartElement, label } = chartRefs[i]; if (chartElement) { // Add chart title pdf.setFontSize(12); pdf.setFont('helvetica', 'bold'); const chartImage = await toPng(chartElement, { quality: 1, pixelRatio: 2, }); const chartImgProps = pdf.getImageProperties(chartImage); const chartWidth = pageWidth - 2 * margin; const chartHeight = (chartImgProps.height * chartWidth) / chartImgProps.width; // Calculate total height needed (title + spacing + chart) const titleHeight = 10; const totalHeight = titleHeight + chartHeight; // Check if chart fits on current page if (yPosition + totalHeight > pageHeight - margin) { pdf.addPage(); yPosition = margin; } // Add title pdf.text(label, margin, yPosition); yPosition += titleHeight; // Add chart image pdf.addImage( chartImage, 'PNG', margin, yPosition, chartWidth, chartHeight ); // Update yPosition for next chart (add spacing between charts) yPosition += chartHeight + 10; } } } // Save the PDF const fileName = `dashboard-production-${new Date().toISOString().split('T')[0]}.pdf`; pdf.save(fileName); toast.success('PDF exported successfully!', { id: 'export-pdf' }); } catch { toast.error('Failed to export PDF. Please try again.', { id: 'export-pdf', }); } finally { setExporting(false); } };