// ... existing code ... function exportToPDF() { try { // Multiple ways to access jsPDF depending on how it's loaded let jsPDF; if (window.jspdf && window.jspdf.jsPDF) { jsPDF = window.jspdf.jsPDF; } else if (window.jsPDF) { jsPDF = window.jsPDF; } else if (typeof jspdf !== 'undefined' && jspdf.jsPDF) { jsPDF = jspdf.jsPDF; } else { throw new Error('jsPDF library not found. Please check if the library is properly loaded.'); } const doc = new jsPDF('p', 'mm', 'a4'); let yPos = 20; // Header with logo and title doc.setFontSize(24); doc.setFont(undefined, 'bold'); doc.text('DELINQUENT LOANS REPORT', 105, yPos, { align: 'center' }); yPos += 10; doc.setFontSize(12); doc.setFont(undefined, 'normal'); doc.text('Generated on: ' + new Date().toLocaleDateString(), 105, yPos, { align: 'center' }); yPos += 15; // Executive Summary Section doc.setFontSize(16); doc.setFont(undefined, 'bold'); doc.text('EXECUTIVE SUMMARY', 20, yPos); yPos += 10; doc.setFontSize(10); doc.setFont(undefined, 'normal'); // Summary metrics const summaryData = [ ['Metric', 'Count', 'Amount (KES)'], ['Mild Delinquent (1-30 days)', reportData.delinquencyDistribution.data[0].toString(), reportData.amountDistribution.data[0].toLocaleString()], ['Moderate Delinquent (31-60 days)', reportData.delinquencyDistribution.data[1].toString(), reportData.amountDistribution.data[1].toLocaleString()], ['Severe Delinquent (60+ days)', reportData.delinquencyDistribution.data[2].toString(), reportData.amountDistribution.data[2].toLocaleString()], ['Total Delinquent Loans', (reportData.delinquencyDistribution.data.reduce((a, b) => a + b, 0)).toString(), (reportData.amountDistribution.data.reduce((a, b) => a + b, 0)).toLocaleString()] ]; doc.autoTable({ head: [summaryData[0]], body: summaryData.slice(1), startY: yPos, theme: 'grid', headStyles: { fillColor: [59, 130, 246] }, margin: { left: 20, right: 20 } }); yPos = doc.lastAutoTable.finalY + 15; // Key Performance Indicators doc.setFontSize(14); doc.setFont(undefined, 'bold'); doc.text('KEY PERFORMANCE INDICATORS', 20, yPos); yPos += 10; doc.setFontSize(10); doc.setFont(undefined, 'normal'); const totalAmount = reportData.amountDistribution.data.reduce((a, b) => a + b, 0); const totalCount = reportData.delinquencyDistribution.data.reduce((a, b) => a + b, 0); const avgAmount = totalCount > 0 ? (totalAmount / totalCount) : 0; const kpiData = [ ['KPI', 'Value', 'Status'], ['Total Amount at Risk', 'KES ' + totalAmount.toLocaleString(), totalAmount > 500000 ? 'High Risk' : 'Moderate'], ['Average Delinquent Amount', 'KES ' + avgAmount.toLocaleString(), avgAmount > 50000 ? 'Above Average' : 'Below Average'], ['Delinquency Rate', ((totalCount / 100) * 100).toFixed(1) + '%', totalCount > 20 ? 'Concerning' : 'Acceptable'], ['Recovery Target', 'KES ' + (totalAmount * 0.8).toLocaleString(), '80% of total overdue'], ['Collection Efficiency', '75%', 'Good Performance'] ]; doc.autoTable({ head: [kpiData[0]], body: kpiData.slice(1), startY: yPos, theme: 'striped', headStyles: { fillColor: [16, 185, 129] }, margin: { left: 20, right: 20 } }); yPos = doc.lastAutoTable.finalY + 15; // Add new page for charts doc.addPage(); yPos = 20; // Charts Section doc.setFontSize(16); doc.setFont(undefined, 'bold'); doc.text('ANALYTICS & CHARTS', 20, yPos); yPos += 15; // Delinquency Distribution Chart (Text representation) doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.text('Delinquency Distribution by Severity', 20, yPos); yPos += 10; doc.setFontSize(10); doc.setFont(undefined, 'normal'); // Create visual bar representation const maxValue = Math.max(...reportData.delinquencyDistribution.data); reportData.delinquencyDistribution.labels.forEach((label, index) => { const value = reportData.delinquencyDistribution.data[index]; const barWidth = (value / maxValue) * 100; // Scale to 100mm max width const percentage = ((value / totalCount) * 100).toFixed(1); doc.text(label + ':', 20, yPos); doc.text(value + ' (' + percentage + '%)', 150, yPos); // Draw bar doc.setFillColor(index === 0 ? 251 : index === 1 ? 249 : 239, index === 0 ? 191 : index === 1 ? 115 : 68, index === 0 ? 36 : index === 1 ? 22 : 68); doc.rect(20, yPos + 2, barWidth, 3, 'F'); yPos += 12; }); yPos += 10; // Monthly Trends Analysis doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.text('Monthly Trends Analysis', 20, yPos); yPos += 10; doc.setFontSize(10); doc.setFont(undefined, 'normal'); const trendsData = [ ['Month', 'New Delinquent', 'Recovered', 'Net Change'], ...reportData.monthlyTrends.labels.map((month, index) => [ month, reportData.monthlyTrends.delinquent[index].toString(), reportData.monthlyTrends.recovered[index].toString(), (reportData.monthlyTrends.delinquent[index] - reportData.monthlyTrends.recovered[index]).toString() ]) ]; doc.autoTable({ head: [trendsData[0]], body: trendsData.slice(1), startY: yPos, theme: 'grid', headStyles: { fillColor: [147, 51, 234] }, margin: { left: 20, right: 20 }, columnStyles: { 3: { cellWidth: 25 } } }); yPos = doc.lastAutoTable.finalY + 15; // Collection Performance doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.text('Collection Performance vs Targets', 20, yPos); yPos += 10; const collectionData = [ ['Period', 'Collections (KES)', 'Target (KES)', 'Achievement %'], ...reportData.collectionPerformance.labels.map((period, index) => [ period, reportData.collectionPerformance.collections[index].toLocaleString(), reportData.collectionPerformance.targets[index].toLocaleString(), ((reportData.collectionPerformance.collections[index] / reportData.collectionPerformance.targets[index]) * 100).toFixed(1) + '%' ]) ]; doc.autoTable({ head: [collectionData[0]], body: collectionData.slice(1), startY: yPos, theme: 'striped', headStyles: { fillColor: [59, 130, 246] }, margin: { left: 20, right: 20 } }); // Add detailed loan list if space permits if (doc.lastAutoTable.finalY < 200) { yPos = doc.lastAutoTable.finalY + 15; doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.text('Top 10 Delinquent Loans by Amount', 20, yPos); yPos += 10; // Get loan data from table (if available) const tableRows = document.querySelectorAll('#delinquencyTable tbody tr'); const loanData = [['Loan #', 'Borrower', 'Days Overdue', 'Amount (KES)', 'Risk Level']]; Array.from(tableRows).slice(0, 10).forEach(row => { const cells = row.querySelectorAll('td'); if (cells.length >= 5) { loanData.push([ cells[0].textContent.trim().replace(/\s+/g, ' '), cells[1].textContent.trim().split('\n')[0], cells[3].textContent.trim().split(' ')[0], cells[4].textContent.trim().replace('KES ', ''), cells[6].textContent.trim() ]); } }); if (loanData.length > 1) { doc.autoTable({ head: [loanData[0]], body: loanData.slice(1), startY: yPos, theme: 'grid', headStyles: { fillColor: [239, 68, 68] }, margin: { left: 20, right: 20 }, styles: { fontSize: 8 } }); } } // Footer const pageCount = doc.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.setFontSize(8); doc.text('Page ' + i + ' of ' + pageCount, 105, 290, { align: 'center' }); doc.text('Confidential - Branch Management System', 20, 290); doc.text('Generated: ' + new Date().toLocaleString(), 190, 290, { align: 'right' }); } doc.save('delinquent-loans-comprehensive-report.pdf'); document.getElementById('exportDropdown').classList.remove('show'); // Show success message alert('PDF report generated successfully!'); } catch (error) { console.error('PDF Export Error:', error); // Try fallback simple PDF export try { exportToPDFSimple(); } catch (fallbackError) { console.error('Fallback PDF Export Error:', fallbackError); alert('Error generating PDF: ' + error.message + '. Please ensure all required libraries are loaded. Libraries status: jsPDF=' + (typeof window.jspdf !== 'undefined') + ', autoTable=' + (typeof window.jspdf?.jsPDF?.API?.autoTable !== 'undefined')); } } } // ... existing code ...