/** * UI Management Module * Handles DOM updates, table rendering, and metric display */ class UIManager { constructor(calculator) { this.calculator = calculator; } updateSummaryMetrics() { try { const enabledResults = Object.values(this.calculator.results); if (enabledResults.length === 0) { this.setElementText('total-instances', '0'); this.setElementText('total-revenue', 'CHF 0'); this.setElementText('roi-percentage', '0%'); this.setElementText('breakeven-time', 'N/A'); return; } // Calculate averages across enabled scenarios const avgInstances = Math.round(enabledResults.reduce((sum, r) => sum + r.finalInstances, 0) / enabledResults.length); const avgRevenue = enabledResults.reduce((sum, r) => sum + r.totalRevenue, 0) / enabledResults.length; 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; this.setElementText('total-instances', avgInstances.toLocaleString()); this.setElementText('total-revenue', this.formatCurrency(avgRevenue)); this.setElementText('roi-percentage', this.formatPercentage(avgROI)); this.setElementText('breakeven-time', isNaN(avgBreakeven) ? 'N/A' : `${Math.round(avgBreakeven)} months`); } catch (error) { console.error('Error updating summary metrics:', error); } } updateComparisonTable() { try { const tbody = document.getElementById('comparison-tbody'); if (!tbody) { console.error('Comparison table body not found'); return; } tbody.innerHTML = ''; Object.values(this.calculator.results).forEach(result => { const modelLabel = result.investmentModel === 'loan' ? 'Loan' : 'Direct'; const row = tbody.insertRow(); row.innerHTML = ` ${result.scenario} ${modelLabel} ${result.finalInstances.toLocaleString()} ${this.formatCurrencyDetailed(result.totalRevenue)} ${this.formatCurrencyDetailed(result.cspRevenue)} ${this.formatCurrencyDetailed(result.servalaRevenue)} ${this.formatPercentage(result.roi)} ${result.breakEvenMonth ? result.breakEvenMonth + ' months' : 'N/A'} `; }); } catch (error) { console.error('Error updating comparison table:', error); } } updateMonthlyBreakdown() { try { const tbody = document.getElementById('monthly-tbody'); if (!tbody) { console.error('Monthly breakdown table body not found'); return; } tbody.innerHTML = ''; // Combine all monthly data and sort by month and scenario 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 => { const row = tbody.insertRow(); row.innerHTML = ` ${data.month} ${data.scenario} ${data.newInstances} ${data.churnedInstances} ${data.totalInstances.toLocaleString()} ${this.formatCurrencyDetailed(data.monthlyRevenue)} ${this.formatCurrencyDetailed(data.cspRevenue)} ${this.formatCurrencyDetailed(data.servalaRevenue)} ${this.formatCurrencyDetailed(data.cumulativeCSPRevenue)} `; }); } catch (error) { console.error('Error updating monthly breakdown:', error); } } setElementText(elementId, text) { const element = document.getElementById(elementId); if (element) { element.textContent = text; } } formatCurrency(amount) { try { // Use compact notation for large numbers in metric cards if (amount >= 1000000) { return new Intl.NumberFormat('de-CH', { style: 'currency', currency: 'CHF', notation: 'compact', minimumFractionDigits: 0, maximumFractionDigits: 1 }).format(amount); } else { return new Intl.NumberFormat('de-CH', { style: 'currency', currency: 'CHF', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(amount); } } catch (error) { console.error('Error formatting currency:', error); return `CHF ${amount.toFixed(0)}`; } } formatCurrencyDetailed(amount) { try { // Use full formatting for detailed views (tables, exports) return new Intl.NumberFormat('de-CH', { style: 'currency', currency: 'CHF', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(amount); } catch (error) { console.error('Error formatting detailed currency:', error); return `CHF ${amount.toFixed(0)}`; } } formatPercentage(value) { try { return new Intl.NumberFormat('de-CH', { style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1 }).format(value / 100); } catch (error) { console.error('Error formatting percentage:', error); return `${value.toFixed(1)}%`; } } }