improved behaviour

This commit is contained in:
Tobias Brunner 2025-06-20 08:57:05 +02:00
parent 9d423ce61e
commit 3f3b9da992
No known key found for this signature in database
2 changed files with 164 additions and 40 deletions

View file

@ -335,9 +335,18 @@ Please contact me with next steps for ordering this configuration.`;
this.cpuValue.textContent = selectedPlan.vcpus;
this.memoryValue.textContent = selectedPlan.ram;
// Fade out CPU and Memory sliders since plan is manually selected
this.fadeOutSliders(['cpu', 'memory']);
// Update addons for the new configuration
this.updateAddons();
// Update pricing with the selected plan
this.updatePricingWithPlan(selectedPlan);
} else {
// Auto-select mode - fade sliders back in
this.fadeInSliders(['cpu', 'memory']);
// Auto-select mode - update addons and recalculate
this.updateAddons();
this.updatePricing();
}
@ -555,6 +564,8 @@ Please contact me with next steps for ordering this configuration.`;
if (!addon.is_mandatory) {
const checkbox = addonElement.querySelector('.addon-checkbox');
checkbox.addEventListener('change', () => {
// Update addon prices and recalculate total
this.updateAddonPrices();
this.updatePricing();
});
}
@ -571,7 +582,7 @@ Please contact me with next steps for ordering this configuration.`;
const storage = parseInt(this.storageRange?.value || 20);
const instances = parseInt(this.instancesRange?.value || 1);
// Find the current plan data to get variable_unit
// Find the current plan data to get variable_unit for addon calculations
const matchedPlan = this.getCurrentPlan();
const variableUnit = matchedPlan?.variable_unit || 'CPU';
const units = variableUnit === 'CPU' ? cpus : memory;
@ -583,17 +594,22 @@ Please contact me with next steps for ordering this configuration.`;
const priceElement = checkbox.parentElement.querySelector('.addon-price-value');
let calculatedPrice = 0;
// Calculate addon price based on type
if (addon.addon_type === 'BASE_FEE') {
// Base fee: price per instance
calculatedPrice = parseFloat(addon.price || 0) * instances;
} else if (addon.addon_type === 'UNIT_RATE') {
// Unit rate: price per unit (CPU or memory) across all instances
calculatedPrice = parseFloat(addon.price_per_unit || 0) * totalUnits;
}
// Update the display price
if (priceElement) {
priceElement.textContent = calculatedPrice.toFixed(2);
}
// Update the checkbox data for later calculation
// Store the calculated price for later use in total calculations
checkbox.dataset.calculatedPrice = calculatedPrice.toString();
});
}
@ -665,7 +681,7 @@ Please contact me with next steps for ordering this configuration.`;
updatePricing() {
if (!this.pricingData || !this.cpuRange || !this.memoryRange || !this.storageRange || !this.instancesRange) return;
// Update addon prices first
// Update addon prices first to ensure they're current
this.updateAddonPrices();
// Reset plan selection if in auto-select mode
@ -690,7 +706,13 @@ Please contact me with next steps for ordering this configuration.`;
} else {
// Plan is directly selected, update storage pricing
const selectedPlan = JSON.parse(this.planSelect.value);
this.updatePricingWithPlan(selectedPlan);
const storage = parseInt(this.storageRange.value);
const instances = parseInt(this.instancesRange.value);
// Update addon prices for current configuration
this.updateAddonPrices();
this.showPlanDetails(selectedPlan, storage, instances);
this.updateStatusMessage('Plan selected directly!', 'success');
}
}
@ -715,13 +737,18 @@ Please contact me with next steps for ordering this configuration.`;
if (this.planInstances) this.planInstances.textContent = instances;
if (this.planServiceLevel) this.planServiceLevel.textContent = serviceLevel;
// Ensure addon prices are calculated with current configuration
this.updateAddonPrices();
// Calculate pricing using final price from plan data (which already includes mandatory addons)
// plan.final_price = compute_plan_price + sla_price (where sla_price includes mandatory addons)
const managedServicePricePerInstance = parseFloat(plan.final_price);
// Collect mandatory addons for display (but don't add to price since they're already included)
// Collect addon information for display and calculation
let mandatoryAddonTotal = 0;
let optionalAddonTotal = 0;
const mandatoryAddons = [];
const selectedOptionalAddons = [];
if (this.addonsContainer) {
const addonCheckboxes = this.addonsContainer.querySelectorAll('.addon-checkbox');
@ -730,11 +757,19 @@ Please contact me with next steps for ordering this configuration.`;
const calculatedPrice = parseFloat(checkbox.dataset.calculatedPrice || 0);
if (addon.is_mandatory) {
// Don't add to mandatoryAddonTotal since it's already in plan.final_price
// Mandatory addons are already included in plan.final_price
// We collect them for display purposes only
mandatoryAddons.push({
name: addon.name,
price: calculatedPrice.toFixed(2)
});
} else if (checkbox.checked) {
// Only count checked optional addons
optionalAddonTotal += calculatedPrice;
selectedOptionalAddons.push({
name: addon.name,
price: calculatedPrice.toFixed(2)
});
}
});
}
@ -745,26 +780,7 @@ Please contact me with next steps for ordering this configuration.`;
const storageUnitPrice = plan.storage_price !== undefined ? parseFloat(plan.storage_price) : this.storagePrice;
const storagePriceValue = storage * storageUnitPrice * instances;
// Calculate optional addon total
let optionalAddonTotal = 0;
const selectedOptionalAddons = [];
if (this.addonsContainer) {
const addonCheckboxes = this.addonsContainer.querySelectorAll('.addon-checkbox:checked');
addonCheckboxes.forEach(checkbox => {
const addon = JSON.parse(checkbox.dataset.addon);
const calculatedPrice = parseFloat(checkbox.dataset.calculatedPrice || 0);
if (!addon.is_mandatory) {
optionalAddonTotal += calculatedPrice;
selectedOptionalAddons.push({
name: addon.name,
price: calculatedPrice.toFixed(2)
});
}
});
}
// Total price = managed service price (includes mandatory addons) + storage + optional addons
const totalPriceValue = managedServicePrice + storagePriceValue + optionalAddonTotal;
// Update pricing display
@ -797,20 +813,33 @@ Please contact me with next steps for ordering this configuration.`;
// Clear existing addon pricing display
this.addonPricingContainer.innerHTML = '';
// Add mandatory addons to pricing breakdown
// Add mandatory addons to pricing breakdown (for informational purposes only)
if (mandatoryAddons && mandatoryAddons.length > 0) {
// Add a note explaining mandatory addons are included
const mandatoryNote = document.createElement('div');
mandatoryNote.className = 'text-muted small mb-2';
mandatoryNote.innerHTML = '<em>Required add-ons (included in managed service price):</em>';
this.addonPricingContainer.appendChild(mandatoryNote);
mandatoryAddons.forEach(addon => {
const addonRow = document.createElement('div');
addonRow.className = 'd-flex justify-content-between mb-2';
addonRow.className = 'd-flex justify-content-between mb-1 ps-3';
addonRow.innerHTML = `
<span>Add-on: ${addon.name} <small class="text-muted">(Required)</small></span>
<span class="fw-bold text-muted">CHF ${addon.price}</span>
<span class="text-muted small">${addon.name}</span>
<span class="text-muted small">CHF ${addon.price}</span>
`;
this.addonPricingContainer.appendChild(addonRow);
});
// Add separator if there are also optional addons
if (selectedOptionalAddons && selectedOptionalAddons.length > 0) {
const separator = document.createElement('hr');
separator.className = 'my-2';
this.addonPricingContainer.appendChild(separator);
}
}
// Add optional addons to pricing breakdown
// Add optional addons to pricing breakdown (these are added to total)
if (selectedOptionalAddons && selectedOptionalAddons.length > 0) {
selectedOptionalAddons.forEach(addon => {
const addonRow = document.createElement('div');
@ -852,6 +881,58 @@ Please contact me with next steps for ordering this configuration.`;
this.planMatchStatus.style.display = 'block';
}
}
// Fade out specified sliders when plan is manually selected
fadeOutSliders(sliderTypes) {
sliderTypes.forEach(type => {
const sliderContainer = this.getSliderContainer(type);
if (sliderContainer) {
sliderContainer.style.transition = 'opacity 0.3s ease-in-out';
sliderContainer.style.opacity = '0.3';
sliderContainer.style.pointerEvents = 'none';
// Add visual indicator that sliders are disabled
const slider = sliderContainer.querySelector('.form-range');
if (slider) {
slider.style.cursor = 'not-allowed';
}
}
});
}
// Fade in specified sliders when auto-select mode is chosen
fadeInSliders(sliderTypes) {
sliderTypes.forEach(type => {
const sliderContainer = this.getSliderContainer(type);
if (sliderContainer) {
sliderContainer.style.transition = 'opacity 0.3s ease-in-out';
sliderContainer.style.opacity = '1';
sliderContainer.style.pointerEvents = 'auto';
// Remove visual indicator
const slider = sliderContainer.querySelector('.form-range');
if (slider) {
slider.style.cursor = 'pointer';
}
}
});
}
// Get slider container element by type
getSliderContainer(type) {
switch (type) {
case 'cpu':
return this.cpuRange?.closest('.mb-4');
case 'memory':
return this.memoryRange?.closest('.mb-4');
case 'storage':
return this.storageRange?.closest('.mb-4');
case 'instances':
return this.instancesRange?.closest('.mb-4');
default:
return null;
}
}
}
// Initialize calculator when DOM is loaded