Fixes and improvements

This commit is contained in:
Dayowe 2025-11-12 18:34:26 +01:00
parent 73c4648978
commit 374d067cf4
2 changed files with 68 additions and 54 deletions

View File

@ -2202,7 +2202,8 @@ const CALCULATOR_CONFIG = {
energyUnit: 'kcal100g',
percentage: 0,
isLocked: false,
chartType: null
chartType: null,
splitByMeals: false
};
this.foodSources.push(treats);
this.renderFoodSource(treats);
@ -2654,6 +2655,11 @@ const CALCULATOR_CONFIG = {
const container = document.getElementById('foodSources');
if (!container) return;
const isChart = foodSource.chartType === 'gc' || foodSource.chartType === 'kibble';
const energyReadonlyAttr = isChart ? 'readonly' : '';
const energyTitle = isChart ? 'Chart-based food: kcal locked' : 'Enter energy content';
const unitDisabledAttr = isChart ? 'disabled' : '';
const cardHTML = `
<div class="dog-calculator-food-source-card" id="foodSource-${foodSource.id}">
<div class="dog-calculator-food-source-header">
@ -2664,11 +2670,11 @@ const CALCULATOR_CONFIG = {
<div class="dog-calculator-input-group">
<div class="dog-calculator-form-group">
<label for="energy-${foodSource.id}">Energy Content:</label>
<input type="number" id="energy-${foodSource.id}" min="1" step="1" placeholder="Enter energy content" value="${foodSource.energy}">
<input type="number" id="energy-${foodSource.id}" ${energyReadonlyAttr} title="${energyTitle}" min="1" step="1" placeholder="Enter energy content" value="${foodSource.energy}">
</div>
<div class="dog-calculator-form-group">
<label for="energy-unit-${foodSource.id}">Unit:</label>
<select id="energy-unit-${foodSource.id}" class="dog-calculator-unit-select">
<select id="energy-unit-${foodSource.id}" class="dog-calculator-unit-select" ${unitDisabledAttr} title="${isChart ? 'Chart-based food: unit locked' : 'Select energy unit'}">
<option value="kcal100g" ${foodSource.energyUnit === 'kcal100g' ? 'selected' : ''}>kcal/100g</option>
<option value="kcalkg" ${foodSource.energyUnit === 'kcalkg' ? 'selected' : ''}>kcal/kg</option>
<option value="kcalcup" ${foodSource.energyUnit === 'kcalcup' ? 'selected' : ''}>kcal/cup</option>
@ -2729,7 +2735,7 @@ const CALCULATOR_CONFIG = {
});
}
if (energyInput) {
if (energyInput && !energyInput.hasAttribute('readonly')) {
energyInput.addEventListener('input', () => {
this.updateFoodSourceData(id, 'energy', energyInput.value);
// If kibble reference changed, recompute daily target
@ -2753,7 +2759,7 @@ const CALCULATOR_CONFIG = {
energyInput.addEventListener('blur', () => this.validateFoodSourceEnergy(id));
}
if (energyUnitSelect) {
if (energyUnitSelect && !energyUnitSelect.hasAttribute('disabled')) {
energyUnitSelect.addEventListener('change', () => {
this.updateFoodSourceData(id, 'energyUnit', energyUnitSelect.value);
if (id === this.kibbleRefId) {
@ -3365,7 +3371,7 @@ const CALCULATOR_CONFIG = {
// Debug: log what unit is being used
console.log('UpdateFoodCalculations - unit:', unit, 'unitLabel:', unitLabel);
// Determine frequency suffix for display
// Determine frequency suffix for display (will adjust per-item below)
const frequencySuffix = this.showPerMeal ? '/meal' : '/day';
// Clear all food source errors first
@ -3429,6 +3435,8 @@ const CALCULATOR_CONFIG = {
const kcalPerPercent = chartedPercent > 0 ? (chartedKcal / chartedPercent) : null;
// Second pass: finalize amounts
let splitDailyTotal = 0;
let dailyOnlyTotal = 0;
firstPass.forEach(({ fs, energyPer100g, gramsPortion }) => {
let dailyGramsForThisFood = 0;
let hasEnergyContent = !!(energyPer100g && energyPer100g > 0);
@ -3447,12 +3455,14 @@ const CALCULATOR_CONFIG = {
}
}
const displayGrams = this.showPerMeal ? dailyGramsForThisFood / this.mealsPerDay : dailyGramsForThisFood;
const isDailyOnly = fs.splitByMeals === false;
const displayGrams = (this.showPerMeal && !isDailyOnly) ? (dailyGramsForThisFood / this.mealsPerDay) : dailyGramsForThisFood;
foodBreakdowns.push({
name: fs.name,
percentage: fs.percentage,
dailyGrams: dailyGramsForThisFood,
isDailyOnly: isDailyOnly,
displayGrams: displayGrams,
dailyCups: null,
displayCups: null,
@ -3463,6 +3473,7 @@ const CALCULATOR_CONFIG = {
foodSource: fs
});
totalDailyGrams += dailyGramsForThisFood;
if (isDailyOnly) dailyOnlyTotal += dailyGramsForThisFood; else splitDailyTotal += dailyGramsForThisFood;
if (dailyGramsForThisFood > 0) hasValidFoods = true;
});
@ -3483,12 +3494,13 @@ const CALCULATOR_CONFIG = {
const unitButtons = document.getElementById('unitButtons');
if (unitButtons) unitButtons.style.display = 'none';
// If we have any food sources without energy content, still show the breakdown section
if (foodBreakdowns.length > 0) {
// If we have any foods with >0% but missing energy, show warnings only for those
const visibleBreakdownsMissing = foodBreakdowns.filter(b => b.percentage > 0);
if (visibleBreakdownsMissing.length > 0) {
// Show food amounts section with warnings for missing energy content
const unitLabel = unit === 'g' ? 'g' : unit === 'kg' ? 'kg' : unit === 'oz' ? 'oz' : 'lb';
const foodAmountsHTML = foodBreakdowns.map(breakdown => {
const foodAmountsHTML = visibleBreakdownsMissing.map(breakdown => {
const lockIndicator = breakdown.isLocked ? '<span class="dog-calculator-lock-indicator">🔒</span>' : '';
return `
@ -3539,31 +3551,24 @@ const CALCULATOR_CONFIG = {
const unitButtons = document.getElementById('unitButtons');
if (unitButtons) unitButtons.style.display = 'flex';
// Update per-food breakdown
if (foodBreakdownList && foodBreakdowns.length > 1) {
const breakdownHTML = foodBreakdowns.map(breakdown => {
// Update per-food breakdown (show only items with >0%)
const visibleBreakdowns = foodBreakdowns.filter(b => b.percentage > 0);
if (foodBreakdownList && visibleBreakdowns.length > 0) {
const breakdownHTML = visibleBreakdowns.map(breakdown => {
let valueContent;
// Choose per-item frequency suffix: daily-only items stay /day even in per-meal view
const itemSuffix = (this.showPerMeal && !breakdown.isDailyOnly) ? '/meal' : '/day';
if (breakdown.hasEnergyContent) {
if (unit === 'cups') {
// For cups, use the pre-calculated cups value if available
if (breakdown.displayCups !== null) {
if (breakdown.hasRange && breakdown.displayCupsMin !== breakdown.displayCupsMax) {
valueContent = `${this.formatNumber(breakdown.displayCupsMin, decimals)}-${this.formatNumber(breakdown.displayCupsMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
valueContent = `${this.formatNumber(breakdown.displayCups, decimals)} ${unitLabel}${frequencySuffix}`;
}
valueContent = `${this.formatNumber(breakdown.displayCups, decimals)} ${unitLabel}${itemSuffix}`;
} else {
valueContent = `<span class="dog-calculator-warning" title="Cups only available for foods with kcal/cup measurement">N/A</span>`;
}
} else {
// For other units (g, kg, oz, lb)
if (breakdown.hasRange && breakdown.displayGramsMin !== breakdown.displayGramsMax) {
const minConverted = this.convertUnits(breakdown.displayGramsMin, unit);
const maxConverted = this.convertUnits(breakdown.displayGramsMax, unit);
valueContent = `${this.formatNumber(minConverted, decimals)}-${this.formatNumber(maxConverted, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
valueContent = `${this.formatNumber(this.convertUnits(breakdown.displayGrams, unit), decimals)} ${unitLabel}${frequencySuffix}`;
}
valueContent = `${this.formatNumber(this.convertUnits(breakdown.displayGrams, unit), decimals)} ${unitLabel}${itemSuffix}`;
}
} else {
valueContent = `<span class="dog-calculator-warning" title="Enter energy content to calculate amount">⚠️</span>`;
@ -3586,7 +3591,8 @@ const CALCULATOR_CONFIG = {
// Generate individual food amount breakdown
// Update daily food value with correct units
const displayTotal = this.showPerMeal ? totalDailyGrams / this.mealsPerDay : totalDailyGrams;
// When per-meal view is enabled, split-only items divide by meals/day; daily-only items remain as daily totals
const displayTotal = this.showPerMeal ? (splitDailyTotal / this.mealsPerDay + dailyOnlyTotal) : (splitDailyTotal + dailyOnlyTotal);
let convertedTotal;
let totalDisplayText;
@ -3653,7 +3659,8 @@ const CALCULATOR_CONFIG = {
dailyFoodValue.textContent = totalDisplayText;
// Build HTML for individual food amounts
const foodAmountsHTML = foodBreakdowns.map(breakdown => {
const foodAmountsVisible = foodBreakdowns.filter(b => b.percentage > 0);
const foodAmountsHTML = foodAmountsVisible.map(breakdown => {
const lockIndicator = breakdown.isLocked ? '<span class="dog-calculator-lock-indicator">🔒</span>' : '';
if (!breakdown.hasEnergyContent) {

View File

@ -106,7 +106,8 @@
energyUnit: 'kcal100g',
percentage: 0,
isLocked: false,
chartType: null
chartType: null,
splitByMeals: false
};
this.foodSources.push(treats);
this.renderFoodSource(treats);
@ -558,6 +559,11 @@
const container = document.getElementById('foodSources');
if (!container) return;
const isChart = foodSource.chartType === 'gc' || foodSource.chartType === 'kibble';
const energyReadonlyAttr = isChart ? 'readonly' : '';
const energyTitle = isChart ? 'Chart-based food: kcal locked' : 'Enter energy content';
const unitDisabledAttr = isChart ? 'disabled' : '';
const cardHTML = `
<div class="dog-calculator-food-source-card" id="foodSource-${foodSource.id}">
<div class="dog-calculator-food-source-header">
@ -568,11 +574,11 @@
<div class="dog-calculator-input-group">
<div class="dog-calculator-form-group">
<label for="energy-${foodSource.id}">Energy Content:</label>
<input type="number" id="energy-${foodSource.id}" min="1" step="1" placeholder="Enter energy content" value="${foodSource.energy}">
<input type="number" id="energy-${foodSource.id}" ${energyReadonlyAttr} title="${energyTitle}" min="1" step="1" placeholder="Enter energy content" value="${foodSource.energy}">
</div>
<div class="dog-calculator-form-group">
<label for="energy-unit-${foodSource.id}">Unit:</label>
<select id="energy-unit-${foodSource.id}" class="dog-calculator-unit-select">
<select id="energy-unit-${foodSource.id}" class="dog-calculator-unit-select" ${unitDisabledAttr} title="${isChart ? 'Chart-based food: unit locked' : 'Select energy unit'}">
<option value="kcal100g" ${foodSource.energyUnit === 'kcal100g' ? 'selected' : ''}>kcal/100g</option>
<option value="kcalkg" ${foodSource.energyUnit === 'kcalkg' ? 'selected' : ''}>kcal/kg</option>
<option value="kcalcup" ${foodSource.energyUnit === 'kcalcup' ? 'selected' : ''}>kcal/cup</option>
@ -633,7 +639,7 @@
});
}
if (energyInput) {
if (energyInput && !energyInput.hasAttribute('readonly')) {
energyInput.addEventListener('input', () => {
this.updateFoodSourceData(id, 'energy', energyInput.value);
// If kibble reference changed, recompute daily target
@ -657,7 +663,7 @@
energyInput.addEventListener('blur', () => this.validateFoodSourceEnergy(id));
}
if (energyUnitSelect) {
if (energyUnitSelect && !energyUnitSelect.hasAttribute('disabled')) {
energyUnitSelect.addEventListener('change', () => {
this.updateFoodSourceData(id, 'energyUnit', energyUnitSelect.value);
if (id === this.kibbleRefId) {
@ -1269,7 +1275,7 @@
// Debug: log what unit is being used
console.log('UpdateFoodCalculations - unit:', unit, 'unitLabel:', unitLabel);
// Determine frequency suffix for display
// Determine frequency suffix for display (will adjust per-item below)
const frequencySuffix = this.showPerMeal ? '/meal' : '/day';
// Clear all food source errors first
@ -1333,6 +1339,8 @@
const kcalPerPercent = chartedPercent > 0 ? (chartedKcal / chartedPercent) : null;
// Second pass: finalize amounts
let splitDailyTotal = 0;
let dailyOnlyTotal = 0;
firstPass.forEach(({ fs, energyPer100g, gramsPortion }) => {
let dailyGramsForThisFood = 0;
let hasEnergyContent = !!(energyPer100g && energyPer100g > 0);
@ -1351,12 +1359,14 @@
}
}
const displayGrams = this.showPerMeal ? dailyGramsForThisFood / this.mealsPerDay : dailyGramsForThisFood;
const isDailyOnly = fs.splitByMeals === false;
const displayGrams = (this.showPerMeal && !isDailyOnly) ? (dailyGramsForThisFood / this.mealsPerDay) : dailyGramsForThisFood;
foodBreakdowns.push({
name: fs.name,
percentage: fs.percentage,
dailyGrams: dailyGramsForThisFood,
isDailyOnly: isDailyOnly,
displayGrams: displayGrams,
dailyCups: null,
displayCups: null,
@ -1367,6 +1377,7 @@
foodSource: fs
});
totalDailyGrams += dailyGramsForThisFood;
if (isDailyOnly) dailyOnlyTotal += dailyGramsForThisFood; else splitDailyTotal += dailyGramsForThisFood;
if (dailyGramsForThisFood > 0) hasValidFoods = true;
});
@ -1387,12 +1398,13 @@
const unitButtons = document.getElementById('unitButtons');
if (unitButtons) unitButtons.style.display = 'none';
// If we have any food sources without energy content, still show the breakdown section
if (foodBreakdowns.length > 0) {
// If we have any foods with >0% but missing energy, show warnings only for those
const visibleBreakdownsMissing = foodBreakdowns.filter(b => b.percentage > 0);
if (visibleBreakdownsMissing.length > 0) {
// Show food amounts section with warnings for missing energy content
const unitLabel = unit === 'g' ? 'g' : unit === 'kg' ? 'kg' : unit === 'oz' ? 'oz' : 'lb';
const foodAmountsHTML = foodBreakdowns.map(breakdown => {
const foodAmountsHTML = visibleBreakdownsMissing.map(breakdown => {
const lockIndicator = breakdown.isLocked ? '<span class="dog-calculator-lock-indicator">🔒</span>' : '';
return `
@ -1443,31 +1455,24 @@
const unitButtons = document.getElementById('unitButtons');
if (unitButtons) unitButtons.style.display = 'flex';
// Update per-food breakdown
if (foodBreakdownList && foodBreakdowns.length > 1) {
const breakdownHTML = foodBreakdowns.map(breakdown => {
// Update per-food breakdown (show only items with >0%)
const visibleBreakdowns = foodBreakdowns.filter(b => b.percentage > 0);
if (foodBreakdownList && visibleBreakdowns.length > 0) {
const breakdownHTML = visibleBreakdowns.map(breakdown => {
let valueContent;
// Choose per-item frequency suffix: daily-only items stay /day even in per-meal view
const itemSuffix = (this.showPerMeal && !breakdown.isDailyOnly) ? '/meal' : '/day';
if (breakdown.hasEnergyContent) {
if (unit === 'cups') {
// For cups, use the pre-calculated cups value if available
if (breakdown.displayCups !== null) {
if (breakdown.hasRange && breakdown.displayCupsMin !== breakdown.displayCupsMax) {
valueContent = `${this.formatNumber(breakdown.displayCupsMin, decimals)}-${this.formatNumber(breakdown.displayCupsMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
valueContent = `${this.formatNumber(breakdown.displayCups, decimals)} ${unitLabel}${frequencySuffix}`;
}
valueContent = `${this.formatNumber(breakdown.displayCups, decimals)} ${unitLabel}${itemSuffix}`;
} else {
valueContent = `<span class="dog-calculator-warning" title="Cups only available for foods with kcal/cup measurement">N/A</span>`;
}
} else {
// For other units (g, kg, oz, lb)
if (breakdown.hasRange && breakdown.displayGramsMin !== breakdown.displayGramsMax) {
const minConverted = this.convertUnits(breakdown.displayGramsMin, unit);
const maxConverted = this.convertUnits(breakdown.displayGramsMax, unit);
valueContent = `${this.formatNumber(minConverted, decimals)}-${this.formatNumber(maxConverted, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
valueContent = `${this.formatNumber(this.convertUnits(breakdown.displayGrams, unit), decimals)} ${unitLabel}${frequencySuffix}`;
}
valueContent = `${this.formatNumber(this.convertUnits(breakdown.displayGrams, unit), decimals)} ${unitLabel}${itemSuffix}`;
}
} else {
valueContent = `<span class="dog-calculator-warning" title="Enter energy content to calculate amount">⚠️</span>`;
@ -1490,7 +1495,8 @@
// Generate individual food amount breakdown
// Update daily food value with correct units
const displayTotal = this.showPerMeal ? totalDailyGrams / this.mealsPerDay : totalDailyGrams;
// When per-meal view is enabled, split-only items divide by meals/day; daily-only items remain as daily totals
const displayTotal = this.showPerMeal ? (splitDailyTotal / this.mealsPerDay + dailyOnlyTotal) : (splitDailyTotal + dailyOnlyTotal);
let convertedTotal;
let totalDisplayText;
@ -1557,7 +1563,8 @@
dailyFoodValue.textContent = totalDisplayText;
// Build HTML for individual food amounts
const foodAmountsHTML = foodBreakdowns.map(breakdown => {
const foodAmountsVisible = foodBreakdowns.filter(b => b.percentage > 0);
const foodAmountsHTML = foodAmountsVisible.map(breakdown => {
const lockIndicator = breakdown.isLocked ? '<span class="dog-calculator-lock-indicator">🔒</span>' : '';
if (!breakdown.hasEnergyContent) {