frontend price calculator

This commit is contained in:
Tobias Brunner 2025-06-02 16:22:54 +02:00
parent 4f9a39fd36
commit 475a4643fd
No known key found for this signature in database
4 changed files with 617 additions and 64 deletions

View file

@ -4,6 +4,11 @@
{% block title %}Managed {{ offering.service.name }} on {{ offering.cloud_provider.name }}{% endblock %}
{% block extra_js %}
<script defer src="{% static "js/price-calculator.js" %}"></script>
<link rel="stylesheet" type="text/css" href='{% static "css/price-calculator.css" %}'>
{% endblock %}
{% block content %}
<section class="section bg-primary-subtle">
<div class="container mx-auto px-20 px-lg-0 pt-40 pb-60">
@ -152,76 +157,153 @@
</div>
{% endif %}
<!-- Plans or Service Plans -->
<!-- Price Calculator -->
<div class="pt-24" id="plans" style="scroll-margin-top: 30px;">
{% if offering.msp == "VS" and pricing_data_by_group_and_service_level %}
<!-- Service Plans with Pricing Data -->
<h3 class="fs-24 fw-semibold lh-1 mb-12">Service Plans</h3>
<div class="accordion" id="servicePlansAccordion">
{% for group_name, service_levels in pricing_data_by_group_and_service_level.items %}
<div class="accordion-item">
<h2 class="accordion-header" id="heading{{ forloop.counter }}">
<button class="accordion-button{% if not forloop.first %} collapsed{% endif %}" type="button" data-bs-toggle="collapse" data-bs-target="#collapse{{ forloop.counter }}" aria-expanded="{% if forloop.first %}true{% else %}false{% endif %}" aria-controls="collapse{{ forloop.counter }}">
<strong>{{ group_name }}</strong>
</button>
</h2>
<div id="collapse{{ forloop.counter }}" class="accordion-collapse collapse{% if forloop.first %} show{% endif %}" aria-labelledby="heading{{ forloop.counter }}" data-bs-parent="#servicePlansAccordion">
<div class="accordion-body">
{% comment %} Display group description from first available plan {% endcomment %}
{% for service_level, pricing_data in service_levels.items %}
{% if pricing_data and forloop.first %}
{% with pricing_data.0 as representative_plan %}
{% if representative_plan.compute_plan_group_description %}
<p class="text-muted mb-3">{{ representative_plan.compute_plan_group_description }}</p>
{% endif %}
{% endwith %}
{% endif %}
{% if forloop.first %}
{% comment %} Only show description for first service level {% endcomment %}
{% endif %}
{% endfor %}
<!-- Interactive Price Calculator -->
<h3 class="fs-24 fw-semibold lh-1 mb-12">Configure Your Plan</h3>
<div class="bg-light rounded-4 p-4 mb-4">
<div class="row">
<!-- Calculator Controls -->
<div class="col-12 col-lg-6">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title mb-4">Customize Your Configuration</h5>
{% for service_level, pricing_data in service_levels.items %}
<div class="mb-4">
<h4 class="mb-3 text-primary">{{ service_level }}</h4>
{% if pricing_data %}
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead class="table-dark">
<tr>
<th>Compute Plan</th>
<th>vCPUs</th>
<th>RAM (GB)</th>
<th>Currency</th>
<th>Compute Price</th>
<th>Service Price</th>
<th class="table-warning">Total Price</th>
</tr>
</thead>
<tbody>
{% for row in pricing_data %}
<tr>
<td>{{ row.compute_plan }}</td>
<td>{{ row.vcpus }}</td>
<td>{{ row.ram }}</td>
<td>{{ row.currency }}</td>
<td>{{ row.compute_plan_price|floatformat:2 }}</td>
<td>{{ row.sla_price|floatformat:2 }}</td>
<td class="table-warning fw-bold">{{ row.final_price|floatformat:2 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted">No pricing data available for {{ service_level }}.</p>
{% endif %}
<!-- CPU Slider -->
<div class="mb-4">
<label for="cpuRange" class="form-label d-flex justify-content-between">
<span>vCPUs</span>
<span class="fw-bold" id="cpuValue">2</span>
</label>
<input type="range" class="form-range" id="cpuRange" min="1" max="32" value="2" step="1">
<div class="d-flex justify-content-between text-muted small">
<span>1</span>
<span>32</span>
</div>
{% endfor %}
</div>
<!-- Memory Slider -->
<div class="mb-4">
<label for="memoryRange" class="form-label d-flex justify-content-between">
<span>Memory (GB)</span>
<span class="fw-bold" id="memoryValue">4</span>
</label>
<input type="range" class="form-range" id="memoryRange" min="1" max="128" value="4" step="1">
<div class="d-flex justify-content-between text-muted small">
<span>1 GB</span>
<span>128 GB</span>
</div>
</div>
<!-- Storage Slider -->
<div class="mb-4">
<label for="storageRange" class="form-label d-flex justify-content-between">
<span>Storage (GB)</span>
<span class="fw-bold" id="storageValue">20</span>
</label>
<input type="range" class="form-range" id="storageRange" min="10" max="1000" value="20" step="10">
<div class="d-flex justify-content-between text-muted small">
<span>10 GB</span>
<span>1000 GB</span>
</div>
</div>
<!-- Service Level Selection -->
<div class="mb-4">
<label class="form-label">Service Level</label>
<div class="btn-group w-100" role="group" id="serviceLevelGroup">
<input type="radio" class="btn-check" name="serviceLevel" id="serviceLevelBestEffort" value="Best Effort" checked>
<label class="btn btn-outline-primary" for="serviceLevelBestEffort">Best Effort</label>
<input type="radio" class="btn-check" name="serviceLevel" id="serviceLevelGuaranteed" value="Guaranteed">
<label class="btn btn-outline-primary" for="serviceLevelGuaranteed">Guaranteed Availability</label>
</div>
</div>
<!-- Direct Plan Selection -->
<div class="mb-4">
<label for="planSelect" class="form-label">Or choose a specific plan</label>
<select class="form-select" id="planSelect">
<option value="">Auto-select best matching plan</option>
</select>
<small class="form-text text-muted">Selecting a plan will override the slider configuration</small>
</div>
</div>
</div>
</div>
{% endfor %}
<!-- Results Panel -->
<div class="col-12 col-lg-6">
<div class="card h-100 border-primary">
<div class="card-body">
<h5 class="card-title text-primary mb-4">Your Configuration</h5>
<!-- Plan Match Status -->
<div id="planMatchStatus" class="alert alert-info mb-3">
<i class="bi bi-info-circle me-2"></i>
<span>Finding best matching plan...</span>
</div>
<!-- Selected Plan Details -->
<div id="selectedPlanDetails" style="display: none;">
<div class="mb-3">
<div class="d-flex align-items-center mb-2">
<span class="badge me-2" id="planGroup"></span>
<strong id="planName"></strong>
</div>
<small class="text-muted" id="planDescription"></small>
</div>
<div class="row mb-3">
<div class="col-6">
<small class="text-muted">vCPUs</small>
<div class="fw-bold" id="planCpus"></div>
</div>
<div class="col-6">
<small class="text-muted">Memory</small>
<div class="fw-bold" id="planMemory"></div>
</div>
</div>
<!-- Pricing Breakdown -->
<div class="border-top pt-3">
<div class="d-flex justify-content-between mb-2">
<span>Compute Plan</span>
<span class="fw-bold">CHF <span id="computePrice">0.00</span></span>
</div>
<div class="d-flex justify-content-between mb-2">
<span>Service Price</span>
<span class="fw-bold">CHF <span id="servicePrice">0.00</span></span>
</div>
<div class="d-flex justify-content-between mb-2">
<span>Storage (<span id="storageAmount">20</span> GB)</span>
<span class="fw-bold">CHF <span id="storagePrice">0.00</span></span>
</div>
<hr>
<div class="d-flex justify-content-between">
<span class="fs-5 fw-bold">Total Monthly Price</span>
<span class="fs-4 fw-bold text-primary">CHF <span id="totalPrice">0.00</span></span>
</div>
</div>
</div>
<!-- No Match Found -->
<div id="noMatchFound" style="display: none;" class="alert alert-warning">
<i class="bi bi-exclamation-triangle me-2"></i>
No matching plan found for your requirements. Please adjust your configuration.
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Order Button -->
<div class="text-center mt-4">
<a href="#interest" class="btn btn-primary btn-lg px-5 py-3 fw-semibold">
<i class="bi bi-cart me-2"></i>Order This Configuration
</a>
</div>
{% elif offering.plans.all %}
<!-- Traditional Plans -->