Percentage system overhaul
This commit is contained in:
parent
e789f481f3
commit
0a7020cb88
259
iframe.html
259
iframe.html
@ -1657,7 +1657,7 @@
|
||||
this.redistributePercentages();
|
||||
this.renderFoodSource(foodSource);
|
||||
this.updateAddButton();
|
||||
this.updateFoodCalculations();
|
||||
this.refreshAllPercentageUI();
|
||||
}
|
||||
|
||||
removeFoodSource(id) {
|
||||
@ -1680,7 +1680,7 @@
|
||||
this.redistributePercentages();
|
||||
this.updateFoodSourceNames();
|
||||
this.updateAddButton();
|
||||
this.updateFoodCalculations();
|
||||
this.refreshAllPercentageUI();
|
||||
}
|
||||
|
||||
generateFoodSourceId() {
|
||||
@ -1711,9 +1711,13 @@
|
||||
}
|
||||
|
||||
// Update the UI sliders and inputs
|
||||
this.updatePercentageInputs();
|
||||
this.refreshAllPercentageUI();
|
||||
}
|
||||
|
||||
// OBSOLETE METHODS - Replaced by new validation system
|
||||
// Keeping for reference but these are no longer used
|
||||
|
||||
/*
|
||||
updatePercentageInputs() {
|
||||
this.foodSources.forEach(fs => {
|
||||
const slider = document.getElementById(`percentage-slider-${fs.id}`);
|
||||
@ -1836,6 +1840,187 @@
|
||||
this.updatePercentageInputs();
|
||||
this.updateFoodCalculations();
|
||||
}
|
||||
*/
|
||||
|
||||
// New validation system methods
|
||||
validatePercentageChange(sourceId, requestedValue) {
|
||||
// Find the source being changed
|
||||
const changedSource = this.foodSources.find(fs => fs.id === sourceId);
|
||||
if (!changedSource) {
|
||||
return { isValid: false, reason: 'Source not found' };
|
||||
}
|
||||
|
||||
// If the source is locked, no change allowed
|
||||
if (changedSource.isLocked) {
|
||||
return { isValid: false, reason: 'Source is locked' };
|
||||
}
|
||||
|
||||
// Ensure requested value is within bounds
|
||||
const clampedValue = Math.max(0, Math.min(100, requestedValue));
|
||||
|
||||
// Calculate locked and other unlocked totals
|
||||
const lockedSources = this.foodSources.filter(fs => fs.id !== sourceId && fs.isLocked);
|
||||
const otherUnlockedSources = this.foodSources.filter(fs => fs.id !== sourceId && !fs.isLocked);
|
||||
|
||||
const totalLocked = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
|
||||
|
||||
// Check if the only unlocked source
|
||||
if (otherUnlockedSources.length === 0) {
|
||||
// This is the only unlocked source, must fill remaining percentage
|
||||
const requiredPercentage = 100 - totalLocked;
|
||||
return {
|
||||
isValid: true,
|
||||
actualValue: requiredPercentage,
|
||||
affectedSources: [{ id: sourceId, newPercentage: requiredPercentage }],
|
||||
reason: 'Only unlocked source, forced to fill remainder'
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate available percentage for redistribution
|
||||
const availableForOthers = 100 - clampedValue - totalLocked;
|
||||
|
||||
// Check if redistribution is possible
|
||||
if (availableForOthers < 0) {
|
||||
// Cannot accommodate this value
|
||||
const maxAllowed = 100 - totalLocked;
|
||||
return {
|
||||
isValid: true,
|
||||
actualValue: maxAllowed,
|
||||
affectedSources: this.calculateRedistribution(sourceId, maxAllowed, otherUnlockedSources),
|
||||
reason: 'Value clamped to maximum allowed'
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate redistribution
|
||||
const affectedSources = this.calculateRedistribution(sourceId, clampedValue, otherUnlockedSources);
|
||||
|
||||
return {
|
||||
isValid: true,
|
||||
actualValue: clampedValue,
|
||||
affectedSources: affectedSources,
|
||||
reason: 'Valid change'
|
||||
};
|
||||
}
|
||||
|
||||
calculateRedistribution(sourceId, newValue, otherUnlockedSources) {
|
||||
const result = [{ id: sourceId, newPercentage: newValue }];
|
||||
|
||||
if (otherUnlockedSources.length === 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Calculate total locked percentage
|
||||
const lockedSources = this.foodSources.filter(fs => fs.id !== sourceId && fs.isLocked);
|
||||
const totalLocked = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
|
||||
|
||||
// Available percentage for other unlocked sources
|
||||
const availableForOthers = 100 - newValue - totalLocked;
|
||||
|
||||
// Current total of other unlocked sources
|
||||
const currentOtherTotal = otherUnlockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
|
||||
|
||||
if (currentOtherTotal === 0 || availableForOthers === 0) {
|
||||
// Distribute equally among other unlocked sources
|
||||
const equalShare = Math.floor(availableForOthers / otherUnlockedSources.length);
|
||||
const remainder = availableForOthers - (equalShare * otherUnlockedSources.length);
|
||||
|
||||
otherUnlockedSources.forEach((fs, index) => {
|
||||
const newPercentage = equalShare + (index < remainder ? 1 : 0);
|
||||
result.push({ id: fs.id, newPercentage });
|
||||
});
|
||||
} else {
|
||||
// Distribute proportionally
|
||||
const scale = availableForOthers / currentOtherTotal;
|
||||
let distributedTotal = 0;
|
||||
|
||||
otherUnlockedSources.forEach((fs, index) => {
|
||||
let newPercentage;
|
||||
if (index === otherUnlockedSources.length - 1) {
|
||||
// Last item gets remainder to ensure exact total
|
||||
newPercentage = availableForOthers - distributedTotal;
|
||||
} else {
|
||||
newPercentage = Math.round(fs.percentage * scale);
|
||||
distributedTotal += newPercentage;
|
||||
}
|
||||
result.push({ id: fs.id, newPercentage });
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
applyValidatedChanges(validationResult) {
|
||||
if (!validationResult.isValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply all percentage changes
|
||||
validationResult.affectedSources.forEach(change => {
|
||||
const source = this.foodSources.find(fs => fs.id === change.id);
|
||||
if (source) {
|
||||
source.percentage = change.newPercentage;
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
refreshAllPercentageUI() {
|
||||
this.foodSources.forEach(fs => {
|
||||
// Update all UI elements from single source of truth
|
||||
const slider = document.getElementById(`percentage-slider-${fs.id}`);
|
||||
const input = document.getElementById(`percentage-input-${fs.id}`);
|
||||
const display = document.getElementById(`percentage-display-${fs.id}`);
|
||||
|
||||
if (slider) slider.value = fs.percentage;
|
||||
if (input) input.value = fs.percentage;
|
||||
if (display) display.textContent = `${fs.percentage}%`;
|
||||
|
||||
// Update constraints and disabled states
|
||||
this.updateSliderConstraints(fs);
|
||||
});
|
||||
|
||||
// Update food calculations
|
||||
this.updateFoodCalculations();
|
||||
}
|
||||
|
||||
updateSliderConstraints(foodSource) {
|
||||
const slider = document.getElementById(`percentage-slider-${foodSource.id}`);
|
||||
const input = document.getElementById(`percentage-input-${foodSource.id}`);
|
||||
|
||||
if (!slider || !input) return;
|
||||
|
||||
// Always keep 0-100 scale
|
||||
slider.max = 100;
|
||||
input.max = 100;
|
||||
|
||||
if (foodSource.isLocked) {
|
||||
slider.disabled = true;
|
||||
input.disabled = true;
|
||||
} else {
|
||||
// Calculate maximum allowed and store for validation
|
||||
const maxAllowed = this.calculateMaxAllowed(foodSource.id);
|
||||
slider.disabled = (maxAllowed <= 0);
|
||||
input.disabled = (maxAllowed <= 0);
|
||||
slider.dataset.maxAllowed = maxAllowed;
|
||||
input.dataset.maxAllowed = maxAllowed;
|
||||
}
|
||||
}
|
||||
|
||||
calculateMaxAllowed(sourceId) {
|
||||
const lockedSources = this.foodSources.filter(fs => fs.id !== sourceId && fs.isLocked);
|
||||
const otherUnlockedSources = this.foodSources.filter(fs => fs.id !== sourceId && !fs.isLocked);
|
||||
|
||||
const totalLocked = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
|
||||
|
||||
// If this is the only unlocked source, it must take up the remainder
|
||||
if (otherUnlockedSources.length === 0) {
|
||||
return 100 - totalLocked;
|
||||
}
|
||||
|
||||
// Otherwise, maximum is 100 minus locked percentages
|
||||
return Math.max(0, 100 - totalLocked);
|
||||
}
|
||||
|
||||
updateFoodSourceNames() {
|
||||
this.foodSources.forEach((fs, index) => {
|
||||
@ -1940,65 +2125,26 @@
|
||||
|
||||
if (percentageSlider) {
|
||||
percentageSlider.addEventListener('input', () => {
|
||||
// Check if this source is locked first
|
||||
const foodSource = this.foodSources.find(fs => fs.id === id);
|
||||
if (foodSource && foodSource.isLocked) {
|
||||
// Reset slider to original value and return
|
||||
percentageSlider.value = foodSource.percentage;
|
||||
return;
|
||||
const requestedValue = parseInt(percentageSlider.value);
|
||||
const result = this.validatePercentageChange(id, requestedValue);
|
||||
|
||||
if (result.isValid) {
|
||||
this.applyValidatedChanges(result);
|
||||
}
|
||||
|
||||
// Check if this source has no flexibility (either only unlocked or no percentage available)
|
||||
const otherUnlockedSources = this.foodSources.filter((fs, index) =>
|
||||
fs.id !== id && !fs.isLocked
|
||||
);
|
||||
|
||||
// Calculate available percentage for this source
|
||||
const lockedSources = this.foodSources.filter(fs => fs.id !== id && fs.isLocked);
|
||||
const totalLockedPercentage = lockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
|
||||
const otherUnlockedPercentage = otherUnlockedSources.reduce((sum, fs) => sum + fs.percentage, 0);
|
||||
const availablePercentage = 100 - totalLockedPercentage - otherUnlockedPercentage;
|
||||
|
||||
if (otherUnlockedSources.length === 0 || availablePercentage <= 0) {
|
||||
// This source has no flexibility - don't update display, just call adjustPercentages
|
||||
// which will force it to the correct value and update display properly
|
||||
this.adjustPercentages(id, parseInt(percentageSlider.value));
|
||||
return;
|
||||
}
|
||||
|
||||
let newPercentage = parseInt(percentageSlider.value);
|
||||
const maxAllowed = parseInt(percentageSlider.dataset.maxAllowed) || 100;
|
||||
|
||||
// Constrain to max allowed but keep slider scale 0-100
|
||||
if (newPercentage > maxAllowed) {
|
||||
newPercentage = maxAllowed;
|
||||
percentageSlider.value = maxAllowed;
|
||||
}
|
||||
|
||||
this.adjustPercentages(id, newPercentage);
|
||||
document.getElementById(`percentage-display-${id}`).textContent = `${newPercentage}%`;
|
||||
// Always refresh to ensure valid state
|
||||
this.refreshAllPercentageUI();
|
||||
});
|
||||
}
|
||||
|
||||
if (percentageInput) {
|
||||
percentageInput.addEventListener('change', () => {
|
||||
// Check if this source is locked first
|
||||
const foodSource = this.foodSources.find(fs => fs.id === id);
|
||||
if (foodSource && foodSource.isLocked) {
|
||||
// Reset input to original value and return
|
||||
percentageInput.value = foodSource.percentage;
|
||||
return;
|
||||
const requestedValue = parseInt(percentageInput.value) || 0;
|
||||
const result = this.validatePercentageChange(id, requestedValue);
|
||||
|
||||
if (result.isValid) {
|
||||
this.applyValidatedChanges(result);
|
||||
}
|
||||
|
||||
let newPercentage = parseInt(percentageInput.value) || 0;
|
||||
const maxAllowed = parseInt(percentageInput.dataset.maxAllowed) || 100;
|
||||
|
||||
// Constrain to valid range and max allowed
|
||||
newPercentage = Math.max(0, Math.min(maxAllowed, newPercentage));
|
||||
percentageInput.value = newPercentage;
|
||||
|
||||
this.adjustPercentages(id, newPercentage);
|
||||
document.getElementById(`percentage-display-${id}`).textContent = `${newPercentage}%`;
|
||||
this.refreshAllPercentageUI();
|
||||
});
|
||||
}
|
||||
|
||||
@ -2027,6 +2173,7 @@
|
||||
foodSource.isLocked = !foodSource.isLocked;
|
||||
this.updateLockIcon(id);
|
||||
this.updateLockStates();
|
||||
this.refreshAllPercentageUI();
|
||||
}
|
||||
|
||||
updateLockIcon(id) {
|
||||
@ -2064,7 +2211,7 @@
|
||||
});
|
||||
|
||||
// Update percentage constraints based on lock states
|
||||
this.updatePercentageConstraints();
|
||||
this.refreshAllPercentageUI();
|
||||
}
|
||||
|
||||
updateFoodSourceData(id, field, value) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user