website/hub/services/templates/calculator/csp_roi_calculator.html

440 lines
No EOL
24 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}CSP ROI Calculator{% endblock %}
{% block extra_head %}
<meta name="csrf-token" content="{{ csrf_token }}">
{% endblock %}
{% block extra_css %}
<link rel="stylesheet" type="text/css" href='{% static "css/roi-calculator.css" %}'>
{% endblock %}
{% block extra_js %}
<script src="{% static "js/chart.umd.min.js" %}"></script>
<script src="{% static "js/jspdf.umd.min.js" %}"></script>
<!-- ROI Calculator Modules -->
<script src="{% static "js/roi-calculator/input-utils.js" %}"></script>
<script src="{% static "js/roi-calculator/calculator-core.js" %}"></script>
<script src="{% static "js/roi-calculator/chart-manager.js" %}"></script>
<script src="{% static "js/roi-calculator/ui-manager.js" %}"></script>
<script src="{% static "js/roi-calculator/export-manager.js" %}"></script>
<script src="{% static "js/roi-calculator/roi-calculator-app.js" %}"></script>
<script>
// Global function wrappers for HTML onclick handlers
function updateCalculations() { window.ROICalculatorApp?.updateCalculations(); }
function exportToPDF() { window.ROICalculatorApp?.exportToPDF(); }
function exportToCSV() { window.ROICalculatorApp?.exportToCSV(); }
function handleInvestmentAmountInput(input) { InputUtils.handleInvestmentAmountInput(input); }
function updateInvestmentAmount(value) { window.ROICalculatorApp?.updateInvestmentAmount(value); }
function updateRevenuePerInstance(value) { window.ROICalculatorApp?.updateRevenuePerInstance(value); }
function updateServalaShare(value) { window.ROICalculatorApp?.updateServalaShare(value); }
function updateGracePeriod(value) { window.ROICalculatorApp?.updateGracePeriod(value); }
function updateLoanRate(value) { window.ROICalculatorApp?.updateLoanRate(value); }
function updateScenarioChurn(scenarioKey, churnRate) { window.ROICalculatorApp?.updateScenarioChurn(scenarioKey, churnRate); }
function updateScenarioPhase(scenarioKey, phaseIndex, newInstancesPerMonth) { window.ROICalculatorApp?.updateScenarioPhase(scenarioKey, phaseIndex, newInstancesPerMonth); }
function resetAdvancedParameters() { window.ROICalculatorApp?.resetAdvancedParameters(); }
function toggleScenario(scenarioKey) { window.ROICalculatorApp?.toggleScenario(scenarioKey); }
function toggleCollapsible(elementId) { window.ROICalculatorApp?.toggleCollapsible(elementId); }
function resetCalculator() { window.ROICalculatorApp?.resetCalculator(); }
function toggleInvestmentModel() { window.ROICalculatorApp?.toggleInvestmentModel(); }
function logout() { window.ROICalculatorApp?.logout(); }
// Manual toggle functions for collapse elements
function toggleAdvancedControls() {
const element = document.getElementById('advancedControls');
const button = document.getElementById('advancedToggleBtn');
console.log('Toggling advanced controls, current classes:', element.className);
if (element.style.display === 'none' || element.style.display === '') {
element.style.display = 'block';
button.innerHTML = '<i class="bi bi-gear"></i> Less';
console.log('Showing advanced controls');
} else {
element.style.display = 'none';
button.innerHTML = '<i class="bi bi-gear"></i> More';
console.log('Hiding advanced controls');
}
}
function toggleDataCollapse() {
const element = document.getElementById('dataCollapse');
const button = document.getElementById('dataToggleBtn');
console.log('Toggling data collapse, current style:', element.style.display);
if (element.style.display === 'none' || element.style.display === '') {
element.style.display = 'block';
button.classList.remove('collapsed');
button.setAttribute('aria-expanded', 'true');
console.log('Showing data collapse');
} else {
element.style.display = 'none';
button.classList.add('collapsed');
button.setAttribute('aria-expanded', 'false');
console.log('Hiding data collapse');
}
}
// Initialize collapse states
document.addEventListener('DOMContentLoaded', function() {
// Ensure both sections start collapsed
const advancedControls = document.getElementById('advancedControls');
const dataCollapse = document.getElementById('dataCollapse');
if (advancedControls) {
advancedControls.style.display = 'none';
}
if (dataCollapse) {
dataCollapse.style.display = 'none';
}
console.log('Collapse elements initialized as hidden');
});
</script>
{% endblock %}
{% block content %}
<div class="container-fluid p-0" style="min-height: 100vh;">
<!-- Minimal Header with Controls -->
<div class="bg-light border-bottom sticky-top" style="z-index: 1030;">
<div class="container-fluid">
<!-- Title Row -->
<div class="row py-2 border-bottom">
<div class="col-md-6">
<h4 class="mb-0">CSP ROI Calculator</h4>
<small class="text-muted">Real-time investment analysis</small>
</div>
<div class="col-md-6 text-end">
<button type="button" class="btn btn-sm btn-outline-primary me-1" onclick="exportToPDF()">
<i class="bi bi-file-pdf"></i> PDF
</button>
<button type="button" class="btn btn-sm btn-outline-success me-1" onclick="exportToCSV()">
<i class="bi bi-file-csv"></i> CSV
</button>
<button type="button" class="btn btn-sm btn-outline-secondary me-1" onclick="resetCalculator()">
<i class="bi bi-arrow-clockwise"></i> Reset
</button>
<button type="button" class="btn btn-sm btn-danger" onclick="logout()">
<i class="bi bi-box-arrow-right"></i> Logout
</button>
</div>
</div>
<!-- Compact Controls Row -->
<div class="row py-3">
<!-- Core Investment Parameters -->
<div class="col-lg-2 col-md-3 mb-2">
<label class="form-label small fw-semibold mb-1">Investment</label>
<div class="input-group input-group-sm">
<span class="input-group-text">CHF</span>
<input type="text" class="form-control" id="investment-amount"
data-value="500000" value="500,000"
oninput="handleInvestmentAmountInput(this)"
onchange="updateCalculations()">
</div>
<input type="range" class="form-range mt-1" id="investment-slider"
min="100000" max="2000000" step="50000" value="500000"
onchange="updateInvestmentAmount(this.value)">
</div>
<div class="col-lg-1 col-md-2 mb-2">
<label class="form-label small fw-semibold mb-1">Years</label>
<select class="form-select form-select-sm" id="timeframe" onchange="updateCalculations()">
<option value="1">1</option>
<option value="2">2</option>
<option value="3" selected>3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</div>
<!-- Investment Model -->
<div class="col-lg-2 col-md-3 mb-2">
<label class="form-label small fw-semibold mb-1">Model</label>
<div class="btn-group w-100" role="group">
<input type="radio" class="btn-check" name="investment-model" id="loan-model" value="loan" onchange="toggleInvestmentModel()">
<label class="btn btn-outline-warning btn-sm" for="loan-model">Loan</label>
<input type="radio" class="btn-check" name="investment-model" id="direct-model" value="direct" checked onchange="toggleInvestmentModel()">
<label class="btn btn-outline-success btn-sm" for="direct-model">Direct</label>
</div>
</div>
<!-- Revenue/Instance -->
<div class="col-lg-1 col-md-2 mb-2">
<label class="form-label small fw-semibold mb-1">Revenue</label>
<div class="input-group input-group-sm">
<input type="number" class="form-control" id="revenue-per-instance"
min="20" max="200" step="5" value="50" onchange="updateCalculations()">
<span class="input-group-text">CHF</span>
</div>
<input type="range" class="form-range mt-1" id="revenue-slider"
min="20" max="200" step="5" value="50"
onchange="updateRevenuePerInstance(this.value)">
</div>
<!-- Scenarios Toggle -->
<div class="col-lg-2 col-md-3 mb-2">
<label class="form-label small fw-semibold mb-1">Scenarios</label>
<div class="d-flex gap-1">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="conservative-enabled" checked onchange="toggleScenario('conservative')">
<label class="form-check-label small text-success" for="conservative-enabled" data-bs-toggle="tooltip" title="Conservative: 2% churn, steady growth">Safe</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="moderate-enabled" checked onchange="toggleScenario('moderate')">
<label class="form-check-label small text-warning" for="moderate-enabled" data-bs-toggle="tooltip" title="Moderate: 3% churn, balanced growth">Balanced</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="aggressive-enabled" checked onchange="toggleScenario('aggressive')">
<label class="form-check-label small text-danger" for="aggressive-enabled" data-bs-toggle="tooltip" title="Aggressive: 5% churn, rapid growth">Fast</label>
</div>
</div>
</div>
<!-- Advanced Controls Toggle -->
<div class="col-lg-2 col-md-3 mb-2">
<label class="form-label small fw-semibold mb-1">Controls</label>
<div class="d-flex gap-1">
<button class="btn btn-outline-info btn-sm" type="button" onclick="toggleAdvancedControls()" id="advancedToggleBtn">
<i class="bi bi-gear"></i> More
</button>
<a href="{% url 'services:roi_calculator_help' %}" class="btn btn-outline-secondary btn-sm" target="_blank">
<i class="bi bi-question-circle"></i> Help
</a>
</div>
</div>
<!-- Remaining space for metrics -->
<div class="col-lg-2 col-md-4 mb-2 text-end">
<div class="d-flex justify-content-end gap-3">
<div class="text-center">
<div class="fw-bold text-success" id="net-position" style="font-size: 1.1rem; white-space: nowrap; line-height: 1.2;">CHF 0</div>
<div style="font-size: 0.8rem;" class="text-muted">Net Position</div>
</div>
<div class="text-center">
<div class="fw-bold text-primary" id="roi-percentage" style="font-size: 1.1rem; white-space: nowrap; line-height: 1.2;">0%</div>
<div style="font-size: 0.8rem;" class="text-muted">ROI</div>
</div>
</div>
</div>
</div>
<!-- Collapsible Advanced Controls -->
<div class="collapse" id="advancedControls">
<div class="row py-2 bg-white border-top">
<!-- Loan Rate (conditional) -->
<div class="col-md-2" id="loan-rate-section" style="display: none;">
<label class="form-label small mb-1">Loan Rate (%)</label>
<input type="number" class="form-control form-control-sm" id="loan-interest-rate"
min="3" max="8" step="0.1" value="5.0" onchange="updateCalculations()">
<input type="range" class="form-range mt-1" id="loan-rate-slider"
min="3" max="8" step="0.1" value="5.0" onchange="updateLoanRate(this.value)">
</div>
<!-- Servala Share -->
<div class="col-md-2">
<label class="form-label small mb-1">Servala Share (%)</label>
<input type="number" class="form-control form-control-sm" id="servala-share"
min="10" max="40" step="1" value="25" onchange="updateCalculations()">
<input type="range" class="form-range mt-1" id="share-slider"
min="10" max="40" step="1" value="25" onchange="updateServalaShare(this.value)">
</div>
<!-- Grace Period -->
<div class="col-md-2">
<label class="form-label small mb-1">Grace Period (Mo)</label>
<input type="number" class="form-control form-control-sm" id="grace-period"
min="0" max="24" step="1" value="6" onchange="updateCalculations()">
<input type="range" class="form-range mt-1" id="grace-slider"
min="0" max="24" step="1" value="6" onchange="updateGracePeriod(this.value)">
</div>
<!-- Scenario Tuning -->
<div class="col-md-6">
<div class="row">
<div class="col-4">
<label class="form-label small mb-1">Conservative Churn (%)</label>
<input type="number" class="form-control form-control-sm" id="conservative-churn"
min="0" max="10" step="0.1" value="2.0" onchange="updateScenarioChurn('conservative', this.value)">
</div>
<div class="col-4">
<label class="form-label small mb-1">Moderate Churn (%)</label>
<input type="number" class="form-control form-control-sm" id="moderate-churn"
min="0" max="10" step="0.1" value="3.0" onchange="updateScenarioChurn('moderate', this.value)">
</div>
<div class="col-4">
<label class="form-label small mb-1">Aggressive Churn (%)</label>
<input type="number" class="form-control form-control-sm" id="aggressive-churn"
min="0" max="15" step="0.1" value="5.0" onchange="updateScenarioChurn('aggressive', this.value)">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- CHARTS - Maximum Space -->
<div class="container-fluid px-3 py-3" style="background: #f8f9fa;">
<!-- Loading Spinner -->
<div class="loading-spinner text-center py-5" id="loading-spinner" style="display: none;">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Calculating...</span>
</div>
<p class="mt-2">Calculating scenarios...</p>
</div>
<!-- Additional Key Metrics (Horizontal) -->
<div class="row mb-3" id="summary-metrics">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-body py-3">
<div class="row text-center">
<div class="col-lg-3 col-md-6 mb-2">
<div class="h5 mb-0" id="csp-revenue">CHF 0</div>
<div class="small text-muted">Your Total Revenue</div>
</div>
<div class="col-lg-3 col-md-6 mb-2">
<div class="h5 mb-0" id="breakeven-time">N/A</div>
<div class="small text-muted">Break-Even Time</div>
</div>
<div class="col-lg-3 col-md-6 mb-2">
<div class="h5 mb-0 text-info" id="model-description-display">Direct Investment</div>
<div class="small text-muted">Investment Model</div>
</div>
<div class="col-lg-3 col-md-6 mb-2">
<div class="h5 mb-0 text-secondary">3 Scenarios</div>
<div class="small text-muted">Active Comparisons</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- PRIMARY CHART - Full Width, Large Height -->
<div class="row mb-4">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0 pb-0">
<h5 class="mb-1"><i class="bi bi-graph-up-arrow text-primary"></i> ROI Progression Over Time</h5>
<p class="small text-muted mb-0">Investment profitability timeline - when you'll break even and achieve target returns</p>
</div>
<div class="card-body pt-3">
<canvas id="instanceGrowthChart" style="height: 500px; width: 100%;"></canvas>
</div>
</div>
</div>
</div>
<!-- SECONDARY CHARTS - Side by Side, Large -->
<div class="row mb-4">
<div class="col-xl-6 mb-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white border-0 pb-0">
<h5 class="mb-1"><i class="bi bi-cash-stack text-success"></i> Net Financial Position</h5>
<p class="small text-muted mb-0">Cumulative profit/loss over time</p>
</div>
<div class="card-body pt-3">
<canvas id="revenueChart" style="height: 400px; width: 100%;"></canvas>
</div>
</div>
</div>
<div class="col-xl-6 mb-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white border-0 pb-0">
<h5 class="mb-1"><i class="bi bi-bar-chart text-warning"></i> Performance Comparison</h5>
<p class="small text-muted mb-0">ROI performance across growth scenarios</p>
</div>
<div class="card-body pt-3">
<canvas id="cashFlowChart" style="height: 400px; width: 100%;"></canvas>
</div>
</div>
</div>
</div>
<!-- TERTIARY CHART - Full Width -->
<div class="row mb-4">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0 pb-0">
<h5 class="mb-1"><i class="bi bi-graph-up text-info"></i> Investment Model Comparison</h5>
<p class="small text-muted mb-0">Net profit comparison: Fixed loan returns vs. performance-based direct investment across scenarios</p>
</div>
<div class="card-body pt-3">
<canvas id="modelComparisonChart" style="height: 400px; width: 100%;"></canvas>
</div>
</div>
</div>
</div>
<!-- DATA TABLE - Collapsible to Save Space -->
<div class="row mb-4">
<div class="col-12">
<div class="accordion" id="dataAccordion">
<div class="accordion-item border-0 shadow-sm">
<h2 class="accordion-header" id="dataHeading">
<button class="accordion-button collapsed" type="button" onclick="toggleDataCollapse()" id="dataToggleBtn">
<i class="bi bi-table me-2"></i> Detailed Financial Analysis
<span class="badge bg-secondary ms-2">Optional</span>
</button>
</h2>
<div id="dataCollapse" class="accordion-collapse collapse" aria-labelledby="dataHeading" data-bs-parent="#dataAccordion">
<div class="accordion-body">
<!-- Comparison Table -->
<h6 class="mb-3">Scenario Performance Summary</h6>
<div class="table-responsive mb-4">
<table class="table table-sm table-striped" id="comparison-table">
<thead class="table-dark">
<tr>
<th>Scenario</th>
<th>Model</th>
<th>Final Scale</th>
<th>Total Revenue</th>
<th>Your Revenue</th>
<th>Servala Share</th>
<th>ROI & Bonuses</th>
<th>Break-even</th>
</tr>
</thead>
<tbody id="comparison-tbody">
<!-- Dynamic content -->
</tbody>
</table>
</div>
<!-- Monthly Breakdown -->
<h6 class="mb-3">Monthly Financial Flow</h6>
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
<table class="table table-sm table-striped" id="monthly-table">
<thead class="table-dark sticky-top">
<tr>
<th>Month</th>
<th>Scenario</th>
<th>Growth</th>
<th>Churn</th>
<th>Scale</th>
<th>Monthly Revenue</th>
<th>Your Share</th>
<th>Servala Share</th>
<th>Net Position</th>
</tr>
</thead>
<tbody id="monthly-tbody">
<!-- Dynamic content -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}