diff --git a/hub/services/static/js/price-calculator/dom-manager.js b/hub/services/static/js/price-calculator/dom-manager.js index 9c08813..0e21d0a 100644 --- a/hub/services/static/js/price-calculator/dom-manager.js +++ b/hub/services/static/js/price-calculator/dom-manager.js @@ -145,6 +145,36 @@ class DOMManager { } } + // Set smart default values based on available plans + setSmartDefaults(pricingDataManager) { + const { cpuValues, memoryValues } = pricingDataManager.getAvailableSliderValues(); + + // Use the smallest available CPU value as default + if (cpuValues.length > 0 && this.elements.cpuRange) { + const defaultCpu = Math.min(...cpuValues); + this.elements.cpuRange.value = defaultCpu; + if (this.elements.cpuValue) this.elements.cpuValue.textContent = defaultCpu; + } + + // Use the smallest available memory value as default + if (memoryValues.length > 0 && this.elements.memoryRange) { + const defaultMemory = Math.min(...memoryValues); + this.elements.memoryRange.value = defaultMemory; + if (this.elements.memoryValue) this.elements.memoryValue.textContent = defaultMemory; + } + + // Keep existing defaults for storage and instances + if (this.elements.storageRange) { + this.elements.storageRange.value = '20'; + if (this.elements.storageValue) this.elements.storageValue.textContent = '20'; + } + + if (this.elements.instancesRange) { + this.elements.instancesRange.value = '1'; + if (this.elements.instancesValue) this.elements.instancesValue.textContent = '1'; + } + } + // Get current selected service level getSelectedServiceLevel() { return document.querySelector('input[name="serviceLevel"]:checked')?.value; diff --git a/hub/services/static/js/price-calculator/price-calculator.js b/hub/services/static/js/price-calculator/price-calculator.js index 85c1150..34ee6c1 100644 --- a/hub/services/static/js/price-calculator/price-calculator.js +++ b/hub/services/static/js/price-calculator/price-calculator.js @@ -77,9 +77,12 @@ class PriceCalculator { // Setup service levels based on available data this.uiManager.setupServiceLevels(this.domManager, this.pricingDataManager); - // Calculate and set slider maximums + // Calculate and set slider maximums and ranges this.uiManager.updateSliderMaximums(this.domManager, this.pricingDataManager); + // Set smart default values based on available plans + this.domManager.setSmartDefaults(this.pricingDataManager); + // Populate plan dropdown this.planManager.populatePlanDropdown(this.domManager); @@ -99,11 +102,21 @@ class PriceCalculator { // Slider event listeners cpuRange.addEventListener('input', () => { this.domManager.get('cpuValue').textContent = cpuRange.value; + // Only synchronize if in auto-select mode (no manual plan selection) + const planSelect = this.domManager.get('planSelect'); + if (!planSelect?.value) { + this.synchronizeMemoryToMatchingPlan(parseFloat(cpuRange.value)); + } this.updatePricing(); }); memoryRange.addEventListener('input', () => { this.domManager.get('memoryValue').textContent = memoryRange.value; + // Only synchronize if in auto-select mode (no manual plan selection) + const planSelect = this.domManager.get('planSelect'); + if (!planSelect?.value) { + this.synchronizeCpuToMatchingPlan(parseFloat(memoryRange.value)); + } this.updatePricing(); }); @@ -136,8 +149,8 @@ class PriceCalculator { // Update pricing with the selected plan this.updatePricingWithPlan(selectedPlan); } else { - // Auto-select mode - reset sliders to default values - this.domManager.resetSlidersToDefaults(); + // Auto-select mode - reset sliders to smart default values + this.domManager.setSmartDefaults(this.pricingDataManager); // Auto-select mode - fade sliders back in this.uiManager.fadeInSliders(this.domManager, ['cpu', 'memory']); @@ -275,6 +288,150 @@ class PriceCalculator { [...addons.mandatory, ...addons.optional] ); } + + // Synchronize memory slider to match CPU value with best matching plan + synchronizeMemoryToMatchingPlan(targetCpu) { + const serviceLevel = this.domManager.getSelectedServiceLevel(); + if (!serviceLevel) return; + + // Get all available plans for the current service level + const availablePlans = this.pricingDataManager.getPlansForServiceLevel(serviceLevel); + if (!availablePlans || availablePlans.length === 0) return; + + // Snap CPU to nearest available value first + const { cpuValues } = this.pricingDataManager.getAvailableSliderValues(); + const snappedCpu = this.findNearestValue(targetCpu, cpuValues); + + // Update CPU slider to snapped value if different + if (snappedCpu !== targetCpu) { + const cpuRange = this.domManager.get('cpuRange'); + const cpuValue = this.domManager.get('cpuValue'); + if (cpuRange && cpuValue) { + cpuRange.value = snappedCpu; + cpuValue.textContent = snappedCpu; + } + } + + // Find the plan that best matches the snapped CPU requirement + let bestPlan = null; + let minDifference = Infinity; + + availablePlans.forEach(plan => { + const planCpu = parseFloat(plan.vcpus); + // Look for plans that meet or exceed the CPU requirement + if (planCpu >= snappedCpu) { + const difference = planCpu - snappedCpu; + if (difference < minDifference) { + minDifference = difference; + bestPlan = plan; + } + } + }); + + // If no plan meets the CPU requirement, find the closest one below it + if (!bestPlan) { + availablePlans.forEach(plan => { + const planCpu = parseFloat(plan.vcpus); + const difference = Math.abs(planCpu - snappedCpu); + if (difference < minDifference) { + minDifference = difference; + bestPlan = plan; + } + }); + } + + // Update memory slider to match the found plan + if (bestPlan) { + const memoryRange = this.domManager.get('memoryRange'); + const memoryValue = this.domManager.get('memoryValue'); + + if (memoryRange && memoryValue) { + memoryRange.value = bestPlan.ram; + memoryValue.textContent = bestPlan.ram; + } + } + } + + // Synchronize CPU slider to match memory value with best matching plan + synchronizeCpuToMatchingPlan(targetMemory) { + const serviceLevel = this.domManager.getSelectedServiceLevel(); + if (!serviceLevel) return; + + // Get all available plans for the current service level + const availablePlans = this.pricingDataManager.getPlansForServiceLevel(serviceLevel); + if (!availablePlans || availablePlans.length === 0) return; + + // Snap memory to nearest available value first + const { memoryValues } = this.pricingDataManager.getAvailableSliderValues(); + const snappedMemory = this.findNearestValue(targetMemory, memoryValues); + + // Update memory slider to snapped value if different + if (snappedMemory !== targetMemory) { + const memoryRange = this.domManager.get('memoryRange'); + const memoryValue = this.domManager.get('memoryValue'); + if (memoryRange && memoryValue) { + memoryRange.value = snappedMemory; + memoryValue.textContent = snappedMemory; + } + } + + // Find the plan that best matches the snapped memory requirement + let bestPlan = null; + let minDifference = Infinity; + + availablePlans.forEach(plan => { + const planMemory = parseFloat(plan.ram); + // Look for plans that meet or exceed the memory requirement + if (planMemory >= snappedMemory) { + const difference = planMemory - snappedMemory; + if (difference < minDifference) { + minDifference = difference; + bestPlan = plan; + } + } + }); + + // If no plan meets the memory requirement, find the closest one below it + if (!bestPlan) { + availablePlans.forEach(plan => { + const planMemory = parseFloat(plan.ram); + const difference = Math.abs(planMemory - snappedMemory); + if (difference < minDifference) { + minDifference = difference; + bestPlan = plan; + } + }); + } + + // Update CPU slider to match the found plan + if (bestPlan) { + const cpuRange = this.domManager.get('cpuRange'); + const cpuValue = this.domManager.get('cpuValue'); + + if (cpuRange && cpuValue) { + cpuRange.value = bestPlan.vcpus; + cpuValue.textContent = bestPlan.vcpus; + } + } + } + + // Find the nearest value in an array to a target value + findNearestValue(target, availableValues) { + if (!availableValues || availableValues.length === 0) return target; + + let nearest = availableValues[0]; + let minDifference = Math.abs(target - nearest); + + for (let i = 1; i < availableValues.length; i++) { + const difference = Math.abs(target - availableValues[i]); + if (difference < minDifference) { + minDifference = difference; + nearest = availableValues[i]; + } + } + + return nearest; + } } // Export for use in other modules diff --git a/hub/services/static/js/price-calculator/pricing-data-manager.js b/hub/services/static/js/price-calculator/pricing-data-manager.js index 411d7ef..b4d10e8 100644 --- a/hub/services/static/js/price-calculator/pricing-data-manager.js +++ b/hub/services/static/js/price-calculator/pricing-data-manager.js @@ -150,6 +150,31 @@ class PricingDataManager { return { maxCpus, maxMemory }; } + // Get all unique CPU and memory values from plans + getAvailableSliderValues() { + if (!this.pricingData) return { cpuValues: [], memoryValues: [] }; + + const cpuSet = new Set(); + const memorySet = new Set(); + + // Collect all unique CPU and memory values across all plans + Object.keys(this.pricingData).forEach(groupName => { + const group = this.pricingData[groupName]; + Object.keys(group).forEach(serviceLevel => { + group[serviceLevel].forEach(plan => { + cpuSet.add(parseFloat(plan.vcpus)); + memorySet.add(parseFloat(plan.ram)); + }); + }); + }); + + // Convert to sorted arrays + const cpuValues = Array.from(cpuSet).sort((a, b) => a - b); + const memoryValues = Array.from(memorySet).sort((a, b) => a - b); + + return { cpuValues, memoryValues }; + } + // Get all plans for a specific service level getPlansForServiceLevel(serviceLevel) { if (!this.pricingData || !serviceLevel) return []; diff --git a/hub/services/static/js/price-calculator/ui-manager.js b/hub/services/static/js/price-calculator/ui-manager.js index 729c069..47ded07 100644 --- a/hub/services/static/js/price-calculator/ui-manager.js +++ b/hub/services/static/js/price-calculator/ui-manager.js @@ -245,23 +245,51 @@ class UIManager { if (!cpuRange || !memoryRange) return; - const { maxCpus, maxMemory } = pricingDataManager.getSliderMaximums(); + const { cpuValues, memoryValues } = pricingDataManager.getAvailableSliderValues(); - // Set slider maximums with some padding - if (maxCpus > 0) { - cpuRange.min = "0.25"; - cpuRange.max = Math.ceil(maxCpus); + // Set CPU slider range based on available plan values + if (cpuValues.length > 0) { + cpuRange.min = Math.min(...cpuValues); + cpuRange.max = Math.max(...cpuValues); + // Calculate step size - use the smallest difference between consecutive values + const cpuStep = this.calculateOptimalStep(cpuValues); + cpuRange.step = cpuStep; } - if (maxMemory > 0) { - memoryRange.min = "0.25"; - memoryRange.max = Math.ceil(maxMemory); + // Set Memory slider range based on available plan values + if (memoryValues.length > 0) { + memoryRange.min = Math.min(...memoryValues); + memoryRange.max = Math.max(...memoryValues); + // Calculate step size - use the smallest difference between consecutive values + const memoryStep = this.calculateOptimalStep(memoryValues); + memoryRange.step = memoryStep; } // Update display values after changing min/max domManager.updateSliderDisplayValues(); } + // Calculate optimal step size for slider based on available values + calculateOptimalStep(values) { + if (values.length <= 1) return 0.25; // Default step + + // Find the smallest difference between consecutive values + let minDiff = Infinity; + for (let i = 1; i < values.length; i++) { + const diff = values[i] - values[i - 1]; + if (diff > 0 && diff < minDiff) { + minDiff = diff; + } + } + + // Use the minimum difference as step, but ensure it's reasonable + // Round to common step values (0.25, 0.5, 1, etc.) + if (minDiff <= 0.25) return 0.25; + if (minDiff <= 0.5) return 0.5; + if (minDiff <= 1) return 1; + return Math.ceil(minDiff); + } + // Update instances slider based on service level and replica info updateInstancesSlider(domManager, pricingDataManager) { const instancesRange = domManager.get('instancesRange');