/** * ROI Calculator Application * Main application class that coordinates all modules */ class ROICalculatorApp { constructor() { this.calculator = null; this.chartManager = null; this.uiManager = null; this.exportManager = null; this.isInitialized = false; } async initialize() { if (this.isInitialized) { console.warn('ROI Calculator already initialized'); return; } try { // Create the main calculator instance this.calculator = new ROICalculator(); // Create UI and chart managers this.uiManager = new UIManager(this.calculator); this.chartManager = new ChartManager(this.calculator); this.exportManager = new ExportManager(this.calculator, this.uiManager); // Replace the methods in calculator with manager methods this.calculator.updateSummaryMetrics = () => this.uiManager.updateSummaryMetrics(); this.calculator.updateCharts = () => this.chartManager.updateCharts(); this.calculator.updateComparisonTable = () => this.uiManager.updateComparisonTable(); this.calculator.updateMonthlyBreakdown = () => this.uiManager.updateMonthlyBreakdown(); this.calculator.initializeCharts = () => this.chartManager.initializeCharts(); this.calculator.formatCurrency = (amount) => this.uiManager.formatCurrency(amount); this.calculator.formatCurrencyDetailed = (amount) => this.uiManager.formatCurrencyDetailed(amount); this.calculator.formatPercentage = (value) => this.uiManager.formatPercentage(value); // Re-initialize charts with the chart manager this.chartManager.initializeCharts(); this.calculator.charts = this.chartManager.charts; // Initialize tooltips this.initializeTooltips(); // Check export libraries this.checkExportLibraries(); // Run initial calculation this.calculator.updateCalculations(); this.isInitialized = true; console.log('ROI Calculator initialized successfully'); } catch (error) { console.error('Failed to initialize ROI Calculator:', error); } } // Initialize tooltips with Bootstrap (loaded directly via CDN) initializeTooltips() { // Wait for Bootstrap to be available if (typeof bootstrap !== 'undefined' && bootstrap.Tooltip) { try { var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); } catch (error) { console.warn('Failed to initialize Bootstrap tooltips:', error); this.initializeNativeTooltips(); } } else { // Retry after a short delay for deferred scripts setTimeout(() => this.initializeTooltips(), 100); } } initializeNativeTooltips() { try { var tooltipElements = document.querySelectorAll('[data-bs-toggle="tooltip"]'); tooltipElements.forEach(function (element) { // Ensure the title attribute is set for native tooltips var tooltipContent = element.getAttribute('title'); if (!tooltipContent) { // Get tooltip content from data-bs-original-title or title attribute tooltipContent = element.getAttribute('data-bs-original-title'); if (tooltipContent) { element.setAttribute('title', tooltipContent); } } }); } catch (error) { console.error('Error initializing native tooltips:', error); } } // Check if export libraries are loaded checkExportLibraries() { try { const pdfButton = document.querySelector('button[onclick="exportToPDF()"]'); if (typeof window.jspdf === 'undefined') { if (pdfButton) { pdfButton.disabled = true; pdfButton.innerHTML = ' Loading PDF...'; } // Retry after a short delay setTimeout(() => { if (typeof window.jspdf !== 'undefined' && pdfButton) { pdfButton.disabled = false; pdfButton.innerHTML = ' Export PDF Report'; } }, 2000); } } catch (error) { console.error('Error checking export libraries:', error); } } // Public API methods for global function calls updateCalculations() { if (this.calculator) { this.calculator.updateCalculations(); } } exportToPDF() { if (this.exportManager) { this.exportManager.exportToPDF(); } } exportToCSV() { if (this.exportManager) { this.exportManager.exportToCSV(); } } updateInvestmentAmount(value) { try { const input = document.getElementById('investment-amount'); if (input) { input.setAttribute('data-value', value); input.value = InputUtils.formatNumberWithCommas(value); this.updateCalculations(); } } catch (error) { console.error('Error updating investment amount:', error); } } updateRevenuePerInstance(value) { try { const element = document.getElementById('revenue-per-instance'); if (element) { element.value = value; this.updateCalculations(); } } catch (error) { console.error('Error updating revenue per instance:', error); } } updateServalaShare(value) { try { const element = document.getElementById('servala-share'); if (element) { element.value = value; this.updateCalculations(); } } catch (error) { console.error('Error updating servala share:', error); } } updateGracePeriod(value) { try { const element = document.getElementById('grace-period'); if (element) { element.value = value; this.updateCalculations(); } } catch (error) { console.error('Error updating grace period:', error); } } updateLoanRate(value) { try { const element = document.getElementById('loan-interest-rate'); if (element) { element.value = value; this.updateCalculations(); } } catch (error) { console.error('Error updating loan rate:', error); } } updateScenarioChurn(scenarioKey, churnRate) { try { if (this.calculator && this.calculator.scenarios[scenarioKey]) { this.calculator.scenarios[scenarioKey].churnRate = parseFloat(churnRate) / 100; this.updateCalculations(); } } catch (error) { console.error('Error updating scenario churn:', error); } } updateScenarioPhase(scenarioKey, phaseIndex, newInstancesPerMonth) { try { if (this.calculator && this.calculator.scenarios[scenarioKey] && this.calculator.scenarios[scenarioKey].phases[phaseIndex]) { this.calculator.scenarios[scenarioKey].phases[phaseIndex].newInstancesPerMonth = parseInt(newInstancesPerMonth); this.updateCalculations(); } } catch (error) { console.error('Error updating scenario phase:', error); } } resetAdvancedParameters() { if (!confirm('Reset all advanced parameters to default values?')) { return; } try { if (!this.calculator) return; // Reset Conservative this.calculator.scenarios.conservative.churnRate = 0.02; this.calculator.scenarios.conservative.phases = [ { months: 6, newInstancesPerMonth: 50 }, { months: 6, newInstancesPerMonth: 75 }, { months: 12, newInstancesPerMonth: 100 }, { months: 12, newInstancesPerMonth: 150 } ]; // Reset Moderate this.calculator.scenarios.moderate.churnRate = 0.03; this.calculator.scenarios.moderate.phases = [ { months: 6, newInstancesPerMonth: 100 }, { months: 6, newInstancesPerMonth: 200 }, { months: 12, newInstancesPerMonth: 300 }, { months: 12, newInstancesPerMonth: 400 } ]; // Reset Aggressive this.calculator.scenarios.aggressive.churnRate = 0.05; this.calculator.scenarios.aggressive.phases = [ { months: 6, newInstancesPerMonth: 200 }, { months: 6, newInstancesPerMonth: 400 }, { months: 12, newInstancesPerMonth: 600 }, { months: 12, newInstancesPerMonth: 800 } ]; // Update UI inputs const inputMappings = [ ['conservative-churn', '2.0'], ['conservative-phase-0', '50'], ['conservative-phase-1', '75'], ['conservative-phase-2', '100'], ['conservative-phase-3', '150'], ['moderate-churn', '3.0'], ['moderate-phase-0', '100'], ['moderate-phase-1', '200'], ['moderate-phase-2', '300'], ['moderate-phase-3', '400'], ['aggressive-churn', '5.0'], ['aggressive-phase-0', '200'], ['aggressive-phase-1', '400'], ['aggressive-phase-2', '600'], ['aggressive-phase-3', '800'] ]; inputMappings.forEach(([id, value]) => { const element = document.getElementById(id); if (element) { element.value = value; } }); this.updateCalculations(); } catch (error) { console.error('Error resetting advanced parameters:', error); } } toggleScenario(scenarioKey) { try { const checkbox = document.getElementById(scenarioKey + '-enabled'); if (!checkbox || !this.calculator) return; const enabled = checkbox.checked; this.calculator.scenarios[scenarioKey].enabled = enabled; const card = document.getElementById(scenarioKey + '-card'); if (card) { if (enabled) { card.classList.add('active'); card.classList.remove('disabled'); } else { card.classList.remove('active'); card.classList.add('disabled'); } } this.updateCalculations(); } catch (error) { console.error('Error toggling scenario:', error); } } toggleCollapsible(elementId) { try { const content = document.getElementById(elementId); if (!content) return; const header = content.previousElementSibling; if (!header) return; const chevron = header.querySelector('.bi-chevron-down, .bi-chevron-up'); if (content.classList.contains('show')) { content.classList.remove('show'); if (chevron) { chevron.classList.remove('bi-chevron-up'); chevron.classList.add('bi-chevron-down'); } } else { content.classList.add('show'); if (chevron) { chevron.classList.remove('bi-chevron-down'); chevron.classList.add('bi-chevron-up'); } } } catch (error) { console.error('Error toggling collapsible:', error); } } resetCalculator() { if (!confirm('Are you sure you want to reset all parameters to default values?')) { return; } try { // Reset input values const investmentInput = document.getElementById('investment-amount'); if (investmentInput) { investmentInput.setAttribute('data-value', '500000'); investmentInput.value = '500,000'; } const resetMappings = [ ['investment-slider', 500000], ['timeframe', 3], ['loan-interest-rate', 5.0], ['loan-rate-slider', 5.0], ['revenue-per-instance', 50], ['revenue-slider', 50], ['servala-share', 25], ['share-slider', 25], ['grace-period', 6], ['grace-slider', 6] ]; resetMappings.forEach(([id, value]) => { const element = document.getElementById(id); if (element) { element.value = value; } }); // Check direct model radio button const directModel = document.getElementById('direct-model'); if (directModel) { directModel.checked = true; } // Reset scenarios ['conservative', 'moderate', 'aggressive'].forEach(scenario => { const checkbox = document.getElementById(scenario + '-enabled'); const card = document.getElementById(scenario + '-card'); if (checkbox) checkbox.checked = true; if (this.calculator) this.calculator.scenarios[scenario].enabled = true; if (card) { card.classList.add('active'); card.classList.remove('disabled'); } }); // Reset advanced parameters this.resetAdvancedParameters(); // Reset investment model toggle this.toggleInvestmentModel(); // Recalculate this.updateCalculations(); } catch (error) { console.error('Error resetting calculator:', error); } } toggleInvestmentModel() { try { const selectedModelElement = document.querySelector('input[name="investment-model"]:checked'); const selectedModel = selectedModelElement ? selectedModelElement.value : 'direct'; const loanSection = document.getElementById('loan-rate-section'); const modelDescription = document.getElementById('model-description'); if (selectedModel === 'loan') { if (loanSection) loanSection.style.display = 'block'; if (modelDescription) modelDescription.textContent = 'Loan Model: 3-7% guaranteed annual returns with predictable monthly payments and low risk'; } else { if (loanSection) loanSection.style.display = 'none'; if (modelDescription) modelDescription.textContent = 'Direct Investment: Performance-based returns with progressive scaling, bonuses up to 15%, and dynamic grace periods'; } this.updateCalculations(); } catch (error) { console.error('Error toggling investment model:', error); } } logout() { if (!confirm('Are you sure you want to logout?')) { return; } try { // Create a form to submit logout request const form = document.createElement('form'); form.method = 'POST'; form.action = window.location.pathname; // Add CSRF token from page meta tag or cookie const csrfInput = document.createElement('input'); csrfInput.type = 'hidden'; csrfInput.name = 'csrfmiddlewaretoken'; csrfInput.value = InputUtils.getCSRFToken(); form.appendChild(csrfInput); // Add logout parameter const logoutInput = document.createElement('input'); logoutInput.type = 'hidden'; logoutInput.name = 'logout'; logoutInput.value = 'true'; form.appendChild(logoutInput); document.body.appendChild(form); form.submit(); } catch (error) { console.error('Error during logout:', error); } } } // Initialize the application when DOM is ready document.addEventListener('DOMContentLoaded', function () { window.ROICalculatorApp = new ROICalculatorApp(); window.ROICalculatorApp.initialize(); });