2025-07-22 08:50:48 +02:00
/ * *
* 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' ;
2025-07-22 09:16:40 +02:00
if ( modelDescription ) modelDescription . textContent = 'Loan Model: 3-7% guaranteed annual returns with predictable monthly payments and low risk' ;
2025-07-22 08:50:48 +02:00
} else {
if ( loanSection ) loanSection . style . display = 'none' ;
2025-07-22 09:16:40 +02:00
if ( modelDescription ) modelDescription . textContent = 'Direct Investment: Performance-based returns with progressive scaling, bonuses up to 15%, and dynamic grace periods' ;
2025-07-22 08:50:48 +02:00
}
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 ( ) ;
} ) ;