diff --git a/hub/services/static/js/roi-calculator.js b/hub/services/static/js/roi-calculator.js index 4a3b36f..eba1b73 100644 --- a/hub/services/static/js/roi-calculator.js +++ b/hub/services/static/js/roi-calculator.js @@ -48,16 +48,58 @@ class ROICalculator { } getInputValues() { - const investmentModel = document.querySelector('input[name="investment-model"]:checked').value; - return { - investmentAmount: parseFloat(document.getElementById('investment-amount').getAttribute('data-value')), - timeframe: parseInt(document.getElementById('timeframe').value), - investmentModel: investmentModel, - loanInterestRate: parseFloat(document.getElementById('loan-interest-rate').value) / 100, - revenuePerInstance: parseFloat(document.getElementById('revenue-per-instance').value), - servalaShare: parseFloat(document.getElementById('servala-share').value) / 100, - gracePeriod: parseInt(document.getElementById('grace-period').value) - }; + try { + // Get investment model with fallback + const investmentModelElement = document.querySelector('input[name="investment-model"]:checked'); + const investmentModel = investmentModelElement ? investmentModelElement.value : 'direct'; + + // Get investment amount with validation + const investmentAmountElement = document.getElementById('investment-amount'); + const investmentAmountValue = investmentAmountElement ? investmentAmountElement.getAttribute('data-value') : '500000'; + const investmentAmount = parseFloat(investmentAmountValue) || 500000; + + // Get timeframe with validation + const timeframeElement = document.getElementById('timeframe'); + const timeframe = timeframeElement ? parseInt(timeframeElement.value) || 3 : 3; + + // Get loan interest rate with validation + const loanRateElement = document.getElementById('loan-interest-rate'); + const loanInterestRate = loanRateElement ? (parseFloat(loanRateElement.value) || 5.0) / 100 : 0.05; + + // Get revenue per instance with validation + const revenueElement = document.getElementById('revenue-per-instance'); + const revenuePerInstance = revenueElement ? parseFloat(revenueElement.value) || 50 : 50; + + // Get servala share with validation + const shareElement = document.getElementById('servala-share'); + const servalaShare = shareElement ? (parseFloat(shareElement.value) || 25) / 100 : 0.25; + + // Get grace period with validation + const graceElement = document.getElementById('grace-period'); + const gracePeriod = graceElement ? parseInt(graceElement.value) || 6 : 6; + + return { + investmentAmount, + timeframe, + investmentModel, + loanInterestRate, + revenuePerInstance, + servalaShare, + gracePeriod + }; + } catch (error) { + console.error('Error getting input values:', error); + // Return safe default values + return { + investmentAmount: 500000, + timeframe: 3, + investmentModel: 'direct', + loanInterestRate: 0.05, + revenuePerInstance: 50, + servalaShare: 0.25, + gracePeriod: 6 + }; + } } calculateScenario(scenarioKey, inputs) { @@ -242,78 +284,123 @@ class ROICalculator { } initializeCharts() { - // Instance Growth Chart - const instanceCtx = document.getElementById('instanceGrowthChart').getContext('2d'); - this.charts.instanceGrowth = new Chart(instanceCtx, { - type: 'line', - data: { labels: [], datasets: [] }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { position: 'top' } - }, - scales: { - y: { - beginAtZero: true, - title: { display: true, text: 'Total Instances' } - }, - x: { - title: { display: true, text: 'Month' } - } - } - } - }); + // Check if Chart.js is available + if (typeof Chart === 'undefined') { + console.error('Chart.js library not loaded. Charts will not be available.'); + this.showChartError('Chart.js library failed to load. Please refresh the page.'); + return; + } - // Revenue Chart - const revenueCtx = document.getElementById('revenueChart').getContext('2d'); - this.charts.revenue = new Chart(revenueCtx, { - type: 'line', - data: { labels: [], datasets: [] }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { position: 'top' } - }, - scales: { - y: { - beginAtZero: true, - title: { display: true, text: 'Cumulative Revenue (CHF)' } - }, - x: { - title: { display: true, text: 'Month' } - } - } + try { + // Instance Growth Chart + const instanceCanvas = document.getElementById('instanceGrowthChart'); + if (!instanceCanvas) { + console.error('Instance growth chart canvas not found'); + return; } - }); - - // Cash Flow Chart - const cashFlowCtx = document.getElementById('cashFlowChart').getContext('2d'); - this.charts.cashFlow = new Chart(cashFlowCtx, { - type: 'bar', - data: { labels: [], datasets: [] }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { position: 'top' } - }, - scales: { - y: { - title: { display: true, text: 'Monthly Cash Flow (CHF)' } + const instanceCtx = instanceCanvas.getContext('2d'); + this.charts.instanceGrowth = new Chart(instanceCtx, { + type: 'line', + data: { labels: [], datasets: [] }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { position: 'top' } }, - x: { - title: { display: true, text: 'Month' } + scales: { + y: { + beginAtZero: true, + title: { display: true, text: 'Total Instances' } + }, + x: { + title: { display: true, text: 'Month' } + } } } + }); + + // Revenue Chart + const revenueCanvas = document.getElementById('revenueChart'); + if (!revenueCanvas) { + console.error('Revenue chart canvas not found'); + return; + } + const revenueCtx = revenueCanvas.getContext('2d'); + this.charts.revenue = new Chart(revenueCtx, { + type: 'line', + data: { labels: [], datasets: [] }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { position: 'top' } + }, + scales: { + y: { + beginAtZero: true, + title: { display: true, text: 'Cumulative Revenue (CHF)' } + }, + x: { + title: { display: true, text: 'Month' } + } + } + } + }); + + // Cash Flow Chart + const cashFlowCanvas = document.getElementById('cashFlowChart'); + if (!cashFlowCanvas) { + console.error('Cash flow chart canvas not found'); + return; + } + const cashFlowCtx = cashFlowCanvas.getContext('2d'); + this.charts.cashFlow = new Chart(cashFlowCtx, { + type: 'bar', + data: { labels: [], datasets: [] }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { position: 'top' } + }, + scales: { + y: { + title: { display: true, text: 'Monthly Cash Flow (CHF)' } + }, + x: { + title: { display: true, text: 'Month' } + } + } + } + }); + } catch (error) { + console.error('Error initializing charts:', error); + this.showChartError('Failed to initialize charts. Please refresh the page.'); + } + } + + showChartError(message) { + // Show error message in place of charts + const chartContainers = ['instanceGrowthChart', 'revenueChart', 'cashFlowChart']; + chartContainers.forEach(containerId => { + const container = document.getElementById(containerId); + if (container) { + container.style.display = 'flex'; + container.style.justifyContent = 'center'; + container.style.alignItems = 'center'; + container.style.minHeight = '300px'; + container.style.backgroundColor = '#f8f9fa'; + container.style.border = '1px solid #dee2e6'; + container.style.borderRadius = '4px'; + container.innerHTML = `