website/hub/services/static/js/roi-calculator/roi-calculator-app.js

467 lines
17 KiB
JavaScript
Raw Normal View History

/**
* 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 = '<i class="bi bi-file-pdf"></i> Loading PDF...';
}
// Retry after a short delay
setTimeout(() => {
if (typeof window.jspdf !== 'undefined' && pdfButton) {
pdfButton.disabled = false;
pdfButton.innerHTML = '<i class="bi bi-file-pdf"></i> 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: Guaranteed returns with fixed monthly payments';
} else {
if (loanSection) loanSection.style.display = 'none';
if (modelDescription) modelDescription.textContent = 'Direct Investment: Higher potential returns based on your sales performance';
}
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();
});