/** * Export Management Module * Handles PDF and CSV export functionality */ class ExportManager { constructor(calculator, uiManager) { this.calculator = calculator; this.uiManager = uiManager; } exportToPDF() { // Check if jsPDF is available if (typeof window.jspdf === 'undefined') { alert('PDF export library is loading. Please try again in a moment.'); return; } try { const { jsPDF } = window.jspdf; const doc = new jsPDF(); // Add header doc.setFontSize(20); doc.setTextColor(0, 123, 255); // Bootstrap primary blue doc.text('CSP ROI Calculator Report', 20, 25); // Add generation date doc.setFontSize(10); doc.setTextColor(100, 100, 100); const currentDate = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); doc.text(`Generated on: ${currentDate}`, 20, 35); // Reset text color doc.setTextColor(0, 0, 0); // Add input parameters section doc.setFontSize(16); doc.text('Investment Parameters', 20, 50); const inputs = this.calculator.getInputValues(); let yPos = 60; doc.setFontSize(11); const params = [ ['Investment Amount:', this.uiManager.formatCurrencyDetailed(inputs.investmentAmount)], ['Investment Timeframe:', `${inputs.timeframe} years`], ['Investment Model:', inputs.investmentModel === 'loan' ? 'Loan Model' : 'Direct Investment'], ...(inputs.investmentModel === 'loan' ? [['Loan Interest Rate:', `${(inputs.loanInterestRate * 100).toFixed(1)}%`]] : []), ['Revenue per Instance:', this.uiManager.formatCurrencyDetailed(inputs.revenuePerInstance)], ...(inputs.investmentModel === 'direct' ? [ ['Servala Revenue Share:', `${(inputs.servalaShare * 100).toFixed(0)}%`], ['Grace Period:', `${inputs.gracePeriod} months`] ] : []) ]; params.forEach(([label, value]) => { doc.text(label, 25, yPos); doc.text(value, 80, yPos); yPos += 8; }); // Add scenario results section yPos += 10; doc.setFontSize(16); doc.text('Scenario Results', 20, yPos); yPos += 10; doc.setFontSize(11); Object.values(this.calculator.results).forEach(result => { if (yPos > 250) { doc.addPage(); yPos = 20; } // Scenario header doc.setFontSize(14); doc.setTextColor(0, 123, 255); doc.text(`${result.scenario} Scenario`, 25, yPos); yPos += 10; doc.setFontSize(11); doc.setTextColor(0, 0, 0); const resultData = [ ['Final Instances:', result.finalInstances.toLocaleString()], ['Total Revenue:', this.uiManager.formatCurrencyDetailed(result.totalRevenue)], ['CSP Revenue:', this.uiManager.formatCurrencyDetailed(result.cspRevenue)], ['Servala Revenue:', this.uiManager.formatCurrencyDetailed(result.servalaRevenue)], ['ROI:', this.uiManager.formatPercentage(result.roi)], ['Break-even:', result.breakEvenMonth ? `${result.breakEvenMonth} months` : 'Not achieved'] ]; resultData.forEach(([label, value]) => { doc.text(label, 30, yPos); doc.text(value, 90, yPos); yPos += 7; }); yPos += 8; }); // Add summary section if (yPos > 220) { doc.addPage(); yPos = 20; } yPos += 10; doc.setFontSize(16); doc.text('Executive Summary', 20, yPos); yPos += 10; doc.setFontSize(11); const enabledResults = Object.values(this.calculator.results); if (enabledResults.length > 0) { const avgROI = enabledResults.reduce((sum, r) => sum + r.roi, 0) / enabledResults.length; const avgBreakeven = enabledResults.filter(r => r.breakEvenMonth).reduce((sum, r) => sum + r.breakEvenMonth, 0) / enabledResults.filter(r => r.breakEvenMonth).length; doc.text(`This analysis evaluates ${enabledResults.length} growth scenario(s) over a ${inputs.timeframe}-year period.`, 25, yPos); yPos += 8; doc.text(`Average projected ROI: ${this.uiManager.formatPercentage(avgROI)}`, 25, yPos); yPos += 8; if (!isNaN(avgBreakeven)) { doc.text(`Average break-even timeline: ${Math.round(avgBreakeven)} months`, 25, yPos); yPos += 8; } yPos += 5; doc.text('Key assumptions:', 25, yPos); yPos += 8; doc.text('• Growth rates based on market analysis and industry benchmarks', 30, yPos); yPos += 6; doc.text('• Churn rates reflect typical SaaS industry standards', 30, yPos); yPos += 6; doc.text('• Revenue calculations include grace period provisions', 30, yPos); } // Add footer const pageCount = doc.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.setFontSize(8); doc.setTextColor(150, 150, 150); doc.text(`Page ${i} of ${pageCount}`, doc.internal.pageSize.getWidth() - 30, doc.internal.pageSize.getHeight() - 10); doc.text('Generated by Servala CSP ROI Calculator', 20, doc.internal.pageSize.getHeight() - 10); } // Save the PDF const filename = `servala-csp-roi-report-${new Date().toISOString().split('T')[0]}.pdf`; doc.save(filename); } catch (error) { console.error('PDF Export Error:', error); alert('An error occurred while generating the PDF. Please try again or export as CSV instead.'); } } exportToCSV() { try { // Create comprehensive CSV with summary and detailed data let csvContent = 'CSP ROI Calculator Export\n'; csvContent += `Generated on: ${new Date().toLocaleDateString()}\n\n`; // Add input parameters csvContent += 'INPUT PARAMETERS\n'; const inputs = this.calculator.getInputValues(); csvContent += `Investment Amount,${inputs.investmentAmount}\n`; csvContent += `Timeframe (years),${inputs.timeframe}\n`; csvContent += `Investment Model,${inputs.investmentModel === 'loan' ? 'Loan Model' : 'Direct Investment'}\n`; if (inputs.investmentModel === 'loan') { csvContent += `Loan Interest Rate (%),${(inputs.loanInterestRate * 100).toFixed(1)}\n`; } csvContent += `Revenue per Instance,${inputs.revenuePerInstance}\n`; if (inputs.investmentModel === 'direct') { csvContent += `Servala Share (%),${(inputs.servalaShare * 100).toFixed(0)}\n`; csvContent += `Grace Period (months),${inputs.gracePeriod}\n`; } csvContent += '\n'; // Add scenario summary csvContent += 'SCENARIO SUMMARY\n'; csvContent += 'Scenario,Investment Model,Final Instances,Total Revenue,CSP Revenue,Servala Revenue,ROI (%),Break-even (months)\n'; Object.values(this.calculator.results).forEach(result => { const modelText = result.investmentModel === 'loan' ? 'Loan' : 'Direct'; csvContent += `${result.scenario},${modelText},${result.finalInstances},${result.totalRevenue.toFixed(2)},${result.cspRevenue.toFixed(2)},${result.servalaRevenue.toFixed(2)},${result.roi.toFixed(2)},${result.breakEvenMonth || 'N/A'}\n`; }); csvContent += '\n'; // Add detailed monthly data csvContent += 'MONTHLY BREAKDOWN\n'; csvContent += 'Month,Scenario,New Instances,Churned Instances,Total Instances,Monthly Revenue,CSP Revenue,Servala Revenue,Cumulative CSP Revenue,Cumulative Servala Revenue\n'; // Combine all monthly data const allData = []; Object.keys(this.calculator.monthlyData).forEach(scenario => { this.calculator.monthlyData[scenario].forEach(monthData => { allData.push(monthData); }); }); allData.sort((a, b) => a.month - b.month || a.scenario.localeCompare(b.scenario)); allData.forEach(data => { csvContent += `${data.month},${data.scenario},${data.newInstances},${data.churnedInstances},${data.totalInstances},${data.monthlyRevenue.toFixed(2)},${data.cspRevenue.toFixed(2)},${data.servalaRevenue.toFixed(2)},${data.cumulativeCSPRevenue.toFixed(2)},${data.cumulativeServalaRevenue.toFixed(2)}\n`; }); // Create and download file const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); const filename = `servala-csp-roi-data-${new Date().toISOString().split('T')[0]}.csv`; link.setAttribute('download', filename); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); // Clean up URL.revokeObjectURL(url); } catch (error) { console.error('CSV Export Error:', error); alert('An error occurred while generating the CSV file. Please try again.'); } } }