235 lines
No EOL
10 KiB
JavaScript
235 lines
No EOL
10 KiB
JavaScript
/**
|
|
* 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.');
|
|
}
|
|
}
|
|
} |