diff --git a/package.json b/package.json index 3cb192a..9b44e8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homepage", - "version": "1.19.0", + "version": "1.20.0", "private": true, "type": "module", "scripts": { diff --git a/src/lib/components/fitness/FoodSearch.svelte b/src/lib/components/fitness/FoodSearch.svelte index 94db09f..e04c93c 100644 --- a/src/lib/components/fitness/FoodSearch.svelte +++ b/src/lib/components/fitness/FoodSearch.svelte @@ -434,7 +434,7 @@ {item.calories} kcal - {#if showDetailLinks && (item.source === 'bls' || item.source === 'usda')} + {#if showDetailLinks && (item.source === 'bls' || item.source === 'usda' || item.source === 'off')} diff --git a/src/routes/fitness/[nutrition=fitnessNutrition]/+page.svelte b/src/routes/fitness/[nutrition=fitnessNutrition]/+page.svelte index 161fd84..2ab5102 100644 --- a/src/routes/fitness/[nutrition=fitnessNutrition]/+page.svelte +++ b/src/routes/fitness/[nutrition=fitnessNutrition]/+page.svelte @@ -1565,10 +1565,10 @@
{/if}
- {#if entry.source === 'bls' || entry.source === 'usda'} + {#if entry.source === 'bls' || entry.source === 'usda' || entry.source === 'off'} {entry.name} - {:else if entry.source === 'recipe' && entry.sourceId} - {entry.name} + {:else if (entry.source === 'recipe' || entry.source === 'custom') && entry.sourceId} + {entry.name} {:else} {entry.name} {/if} diff --git a/src/routes/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]/+page.server.ts b/src/routes/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]/+page.server.ts index d51eff0..214d403 100644 --- a/src/routes/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]/+page.server.ts +++ b/src/routes/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]/+page.server.ts @@ -4,6 +4,8 @@ import { NUTRITION_DB } from '$lib/data/nutritionDb'; import { BLS_DB } from '$lib/data/blsDb'; import { DRI_MALE } from '$lib/data/dailyReferenceIntake'; import { Recipe } from '$models/Recipe'; +import { OpenFoodFact } from '$models/OpenFoodFact'; +import { CustomMeal } from '$models/CustomMeal'; import { dbConnect } from '$utils/db'; import { computeRecipeNutritionTotals, resolvePer100g, parseAmount, resolveReferencedNutrition } from '$lib/server/nutritionMatcher'; import { FoodLogEntry } from '$models/FoodLogEntry'; @@ -61,7 +63,7 @@ async function computeRecipePer100g(id: string): Promise> export const load: PageServerLoad = async ({ params, url }) => { const { source, id } = params; - if (source !== 'bls' && source !== 'usda' && source !== 'recipe') { + if (source !== 'bls' && source !== 'usda' && source !== 'recipe' && source !== 'off' && source !== 'custom') { throw error(404, 'Invalid source'); } @@ -126,6 +128,86 @@ export const load: PageServerLoad = async ({ params, url }) => { }; } + if (source === 'off') { + await dbConnect(); + const entry = await OpenFoodFact.findOne({ barcode: id }).lean(); + if (!entry) throw error(404, 'Food not found'); + const portions: { description: string; grams: number }[] = []; + if (entry.serving?.grams) { + portions.push(entry.serving as { description: string; grams: number }); + } + return { + food: { + source: 'off' as const, + id: entry.barcode, + name: entry.nameDe || entry.name, + nameDe: entry.nameDe, + nameEn: entry.nameDe ? entry.name : undefined, + category: entry.category || '', + per100g: entry.per100g as unknown as Record, + brands: entry.brands, + nutriscore: entry.nutriscore, + portions, + }, + dri: DRI_MALE, + }; + } + + if (source === 'custom') { + await dbConnect(); + const meal = await CustomMeal.findById(id).lean(); + if (!meal) throw error(404, 'Meal not found'); + + // Aggregate per100g from ingredients + const totals: Record = {}; + let totalGrams = 0; + for (const ing of meal.ingredients) { + const r = ing.amountGrams / 100; + totalGrams += ing.amountGrams; + for (const [k, v] of Object.entries(ing.per100g)) { + if (typeof v === 'number') { + totals[k] = (totals[k] || 0) + v * r; + } + } + } + const per100g: Record = {}; + const scale = totalGrams > 0 ? 100 / totalGrams : 0; + for (const [k, v] of Object.entries(totals)) { + per100g[k] = v * scale; + } + + // Use logged per100g snapshot if provided + const logEntryId = url.searchParams.get('logEntry'); + if (logEntryId) { + const logEntry = await FoodLogEntry.findById(logEntryId).select('per100g').lean(); + if (logEntry?.per100g) { + Object.assign(per100g, logEntry.per100g); + } + } + + return { + food: { + source: 'custom' as const, + id: String(meal._id), + name: meal.name, + category: 'Custom Meal', + per100g, + totalGrams, + ingredients: meal.ingredients.map((ing: any) => ({ + name: ing.name, + source: ing.source, + sourceId: ing.sourceId, + amountGrams: ing.amountGrams, + calories: (ing.per100g?.calories ?? 0) * ing.amountGrams / 100, + protein: (ing.per100g?.protein ?? 0) * ing.amountGrams / 100, + fat: (ing.per100g?.fat ?? 0) * ing.amountGrams / 100, + carbs: (ing.per100g?.carbs ?? 0) * ing.amountGrams / 100, + })), + }, + dri: DRI_MALE, + }; + } + // USDA const fdcId = Number(id); const entry = NUTRITION_DB.find(e => e.fdcId === fdcId); diff --git a/src/routes/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]/+page.svelte b/src/routes/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]/+page.svelte index b3bb541..614bbd9 100644 --- a/src/routes/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]/+page.svelte +++ b/src/routes/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]/+page.svelte @@ -182,8 +182,14 @@

{food.nameEn}

{/if}
- {food.source === 'bls' ? 'BLS' : food.source === 'usda' ? 'USDA' : isEn ? 'Recipe' : 'Rezept'} + {food.source === 'bls' ? 'BLS' : food.source === 'usda' ? 'USDA' : food.source === 'off' ? 'OFF' : food.source === 'custom' ? (isEn ? 'Custom Meal' : 'Eigene Mahlzeit') : isEn ? 'Recipe' : 'Rezept'} {food.category} + {#if food.brands} + {food.brands} + {/if} + {#if food.nutriscore} + Nutri-Score {food.nutriscore.toUpperCase()} + {/if} {#if food.recipeSlug} {isEn ? 'View recipe' : 'Zum Rezept'} @@ -256,6 +262,33 @@
+ + {#if food.ingredients?.length} +
+

{isEn ? 'Ingredients' : 'Zutaten'} ({food.totalGrams}g {isEn ? 'total' : 'gesamt'})

+
+ {#each food.ingredients as ing} +
+ +
+ {Math.round(ing.calories)} kcal + {fmt(ing.protein)}P + {fmt(ing.fat)}F + {fmt(ing.carbs)}C +
+
+ {/each} +
+
+ {/if} +