diff --git a/SparkyFitnessFrontend/src/components/EnhancedFoodSearch.tsx b/SparkyFitnessFrontend/src/components/EnhancedFoodSearch.tsx index a7fbf025..18e7651f 100644 --- a/SparkyFitnessFrontend/src/components/EnhancedFoodSearch.tsx +++ b/SparkyFitnessFrontend/src/components/EnhancedFoodSearch.tsx @@ -59,6 +59,7 @@ import { UserCustomNutrient } from "@/types/customNutrient"; // Add import interface OpenFoodFactsProduct { product_name: string; brands?: string; + serving_quantity?: number; nutriments: { "energy-kcal_100g"?: number; proteins_100g?: number; @@ -198,6 +199,7 @@ const EnhancedFoodSearch = ({ energyUnit, convertEnergy, getEnergyUnitString, + autoScaleOpenFoodFactsImports, // Add auto-scale preference } = usePreferences(); // Get loggingLevel, itemDisplayLimit, and foodDisplayLimit const isMobile = useIsMobile(); const platform = isMobile ? "mobile" : "desktop"; @@ -395,23 +397,29 @@ const EnhancedFoodSearch = ({ }; const convertOpenFoodFactsToFood = (product: OpenFoodFactsProduct): Food => { + // Calculate scaling factor based on serving_quantity (defaults to 100g if not provided) + // Only apply scaling if autoScaleOpenFoodFactsImports preference is enabled + const shouldScale = autoScaleOpenFoodFactsImports && product.serving_quantity && product.serving_quantity > 0; + const servingSize = shouldScale ? product.serving_quantity! : 100; + const scaleFactor = shouldScale ? servingSize / 100 : 1; // Scale from 100g to actual serving size, or 1 if disabled + const defaultVariant: FoodVariant = { id: "default", // Assign a default ID for now - serving_size: 100, + serving_size: servingSize, serving_unit: "g", - calories: Math.round(product.nutriments["energy-kcal_100g"] || 0), // Assumed to be in kcal - protein: Math.round((product.nutriments["proteins_100g"] || 0) * 10) / 10, + calories: Math.round((product.nutriments["energy-kcal_100g"] || 0) * scaleFactor), // Assumed to be in kcal, and scaled from 100g to serving + protein: Math.round((product.nutriments["proteins_100g"] || 0) * scaleFactor * 10) / 10, carbs: - Math.round((product.nutriments["carbohydrates_100g"] || 0) * 10) / 10, - fat: Math.round((product.nutriments["fat_100g"] || 0) * 10) / 10, + Math.round((product.nutriments["carbohydrates_100g"] || 0) * scaleFactor * 10) / 10, + fat: Math.round((product.nutriments["fat_100g"] || 0) * scaleFactor * 10) / 10, saturated_fat: - Math.round((product.nutriments["saturated-fat_100g"] || 0) * 10) / 10, + Math.round((product.nutriments["saturated-fat_100g"] || 0) * scaleFactor * 10) / 10, sodium: product.nutriments["sodium_100g"] - ? Math.round(product.nutriments["sodium_100g"] * 1000) + ? Math.round(product.nutriments["sodium_100g"] * 1000 * scaleFactor) : 0, dietary_fiber: - Math.round((product.nutriments["fiber_100g"] || 0) * 10) / 10, - sugars: Math.round((product.nutriments["sugars_100g"] || 0) * 10) / 10, + Math.round((product.nutriments["fiber_100g"] || 0) * scaleFactor * 10) / 10, + sugars: Math.round((product.nutriments["sugars_100g"] || 0) * scaleFactor * 10) / 10, // Initialize other nutrients to 0 or appropriate defaults polyunsaturated_fat: 0, monounsaturated_fat: 0, @@ -423,6 +431,7 @@ const EnhancedFoodSearch = ({ calcium: 0, iron: 0, is_default: true, + is_locked: shouldScale, // Enable auto-scale only if preference is enabled and scaling was applied glycemic_index: "None", // Default GI for OpenFoodFacts }; @@ -1329,49 +1338,56 @@ const EnhancedFoodSearch = ({ {activeTab === "online" && openFoodFactsResults.length > 0 && - openFoodFactsResults.map((product) => ( - - -
-
-
-

{product.product_name}

- {product.brands && ( - - {product.brands.split(",")[0]} + openFoodFactsResults.map((product) => { + // Calculate display values based on auto-scaling preference + const shouldScale = autoScaleOpenFoodFactsImports && product.serving_quantity && product.serving_quantity > 0; + const servingSize = shouldScale ? product.serving_quantity! : 100; + const scaleFactor = shouldScale ? servingSize / 100 : 1; + + return ( + + +
+
+
+

{product.product_name}

+ {product.brands && ( + + {product.brands.split(",")[0]} + + )} + + {t("enhancedFoodSearch.openFoodFacts", "OpenFoodFacts")} - )} - - {t("enhancedFoodSearch.openFoodFacts", "OpenFoodFacts")} - +
+ +

Per {servingSize}g

- -

Per 100g

+
- -
- - - ))} + + + ); + })} {activeTab === "online" && nutritionixResults.length > 0 && diff --git a/SparkyFitnessFrontend/src/components/Settings.tsx b/SparkyFitnessFrontend/src/components/Settings.tsx index 1be14fa9..da8bba69 100644 --- a/SparkyFitnessFrontend/src/components/Settings.tsx +++ b/SparkyFitnessFrontend/src/components/Settings.tsx @@ -18,6 +18,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; // Added import import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; import { Separator } from "@/components/ui/separator"; @@ -115,6 +116,7 @@ const Settings: React.FC = ({ onShowAboutDialog }) => { setLoggingLevel, itemDisplayLimit, setItemDisplayLimit, // Add itemDisplayLimit and setItemDisplayLimit + autoScaleOpenFoodFactsImports, setAutoScaleOpenFoodFactsImports, // Add auto-scale preference loadPreferences: loadUserPreferencesFromContext, // Rename to avoid conflict saveAllPreferences, // Add saveAllPreferences from context formatDate, // Destructure formatDate @@ -969,6 +971,19 @@ const Settings: React.FC = ({ onShowAboutDialog }) => {
+
+
+ +

+ {t('settings.preferences.autoScaleOpenFoodFactsHint', 'When enabled, nutrition values from OpenFoodFacts will be automatically scaled from per-100g to the product\'s serving size.')} +

+
+ +