diff --git a/package.json b/package.json index 12580b3..3b60341 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homepage", - "version": "1.12.0", + "version": "1.13.0", "private": true, "type": "module", "scripts": { diff --git a/src/models/FoodLogEntry.ts b/src/models/FoodLogEntry.ts index 8bee99c..ada215e 100644 --- a/src/models/FoodLogEntry.ts +++ b/src/models/FoodLogEntry.ts @@ -22,6 +22,7 @@ interface IFoodLogEntry { cysteine?: number; glutamicAcid?: number; glycine?: number; proline?: number; serine?: number; tyrosine?: number; }; + liquidMl?: number; createdBy: string; createdAt?: Date; updatedAt?: Date; @@ -51,6 +52,7 @@ const FoodLogEntrySchema = new mongoose.Schema( sourceId: { type: String }, amountGrams: { type: Number, required: true, min: 0 }, per100g: { type: NutritionSnapshotSchema, required: true }, + liquidMl: { type: Number, min: 0 }, createdBy: { type: String, required: true }, }, { timestamps: true } diff --git a/src/routes/api/fitness/food-log/+server.ts b/src/routes/api/fitness/food-log/+server.ts index 1855cfd..21ec8e7 100644 --- a/src/routes/api/fitness/food-log/+server.ts +++ b/src/routes/api/fitness/food-log/+server.ts @@ -40,7 +40,7 @@ export const POST: RequestHandler = async ({ request, locals }) => { await dbConnect(); const body = await request.json(); - const { date, mealType, name, source, sourceId, amountGrams, per100g } = body; + const { date, mealType, name, source, sourceId, amountGrams, per100g, liquidMl } = body; if (!date || !name?.trim()) throw error(400, 'date and name are required'); if (!VALID_MEALS.includes(mealType)) throw error(400, 'Invalid mealType'); @@ -55,6 +55,7 @@ export const POST: RequestHandler = async ({ request, locals }) => { sourceId, amountGrams, per100g, + ...(liquidMl > 0 && { liquidMl }), createdBy: user.nickname, }); diff --git a/src/routes/fitness/[nutrition=fitnessNutrition]/+page.svelte b/src/routes/fitness/[nutrition=fitnessNutrition]/+page.svelte index 2fa91d6..2ba4be5 100644 --- a/src/routes/fitness/[nutrition=fitnessNutrition]/+page.svelte +++ b/src/routes/fitness/[nutrition=fitnessNutrition]/+page.svelte @@ -242,6 +242,12 @@ return DRINK_PATTERNS.test(e.name); } + /** Detect if a custom meal ingredient is a liquid (for hydration auto-logging) */ + function isLiquidIngredient(ing) { + if (ing.source === 'bls' && ing.sourceId?.startsWith('N')) return true; + return DRINK_PATTERNS.test(ing.name) || /^(wasser|water|trinkwasser)/i.test(ing.name); + } + let waterGoalMl = $state(2000); let editingGoal = $state(false); let goalInputL = $state(2); @@ -265,10 +271,12 @@ let beverageEntries = $derived(entries.filter(isBeverage)); let waterMl = $derived(waterEntries.reduce((s, e) => s + e.amountGrams, 0)); let beverageMl = $derived(beverageEntries.reduce((s, e) => s + e.amountGrams, 0)); - let totalLiquidMl = $derived(waterMl + beverageMl); + let mealLiquidMl = $derived(entries.reduce((s, e) => s + (e.liquidMl ?? 0), 0)); + let totalLiquidMl = $derived(waterMl + beverageMl + mealLiquidMl); let beverageCups = $derived(Math.round(beverageMl / WATER_CUP_ML)); let waterCups = $derived(Math.round(waterMl / WATER_CUP_ML)); - let totalCups = $derived(beverageCups + waterCups); + let mealLiquidCups = $derived(Math.round(mealLiquidMl / WATER_CUP_ML)); + let totalCups = $derived(beverageCups + waterCups + mealLiquidCups); let goalCups = $derived(Math.round(waterGoalMl / WATER_CUP_ML)); let displayCups = $derived(Math.max(goalCups, totalCups + 1)); @@ -578,6 +586,10 @@ const scale = totalGrams > 0 ? 100 / totalGrams : 0; for (const k of nutrientKeys) per100g[k] = totals[k] * scale; + const liquidMl = meal.ingredients + .filter(isLiquidIngredient) + .reduce((sum, ing) => sum + ing.amountGrams, 0); + await fetch('/api/fitness/food-log', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -589,8 +601,10 @@ sourceId: meal._id, amountGrams: totalGrams, per100g, + ...(liquidMl > 0 && { liquidMl }), }) }); + await goto(`/fitness/${s.nutrition}?date=${currentDate}`, { replaceState: true, noScroll: true }); closeFabModal(); toast.success(isEn ? `Logged "${meal.name}"` : `"${meal.name}" eingetragen`); @@ -631,6 +645,10 @@ const scale = totalGrams > 0 ? 100 / totalGrams : 0; for (const k of nutrientKeys) per100g[k] = totals[k] * scale; + const liquidMl = meal.ingredients + .filter(isLiquidIngredient) + .reduce((sum, ing) => sum + ing.amountGrams, 0); + await fetch('/api/fitness/food-log', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -642,8 +660,10 @@ sourceId: meal._id, amountGrams: totalGrams, per100g, + ...(liquidMl > 0 && { liquidMl }), }) }); + await goto(`/fitness/${s.nutrition}?date=${currentDate}`, { replaceState: true, noScroll: true }); cancelAdd(); toast.success(isEn ? `Logged "${meal.name}"` : `"${meal.name}" eingetragen`); @@ -1191,6 +1211,8 @@
{#each Array(displayCups) as _, i} {@const isBev = i < beverageCups} + {@const isMealLiquid = !isBev && i < beverageCups + mealLiquidCups} + {@const isAuto = isBev || isMealLiquid} {@const isFilled = i < totalCups} {@const showWater = isFilled || drainingCups.has(i)} {@const isNextEmpty = i === totalCups && !drainingCups.has(i)} @@ -1198,16 +1220,18 @@ class="water-cup" class:filled={isFilled} class:beverage={isBev} + class:meal-liquid={isMealLiquid} class:filling={fillingCups.has(i)} class:draining={drainingCups.has(i)} class:next-empty={isNextEmpty} - disabled={isBev} + disabled={isAuto} onclick={() => { - if (isBev) return; - const waterTarget = i < totalCups ? i - beverageCups : i - beverageCups + 1; + if (isAuto) return; + const autoOffset = beverageCups + mealLiquidCups; + const waterTarget = i < totalCups ? i - autoOffset : i - autoOffset + 1; setWaterCups(Math.max(0, waterTarget)); }} - title="{isBev ? (isEn ? 'Beverage' : 'Getränk') : (i + 1) * WATER_CUP_ML + ' ml'}" + title="{isAuto ? (isEn ? (isBev ? 'Beverage' : 'From meal') : (isBev ? 'Getränk' : 'Aus Mahlzeit')) : (i + 1) * WATER_CUP_ML + ' ml'}" > @@ -1218,9 +1242,9 @@ {#if showWater} - - - + + + {/if} {#if isNextEmpty} @@ -1230,7 +1254,7 @@ {/each}
- {#if beverageEntries.length > 0} + {#if beverageEntries.length > 0 || mealLiquidMl > 0}
{#each beverageEntries as bev}
@@ -1238,6 +1262,12 @@ {Math.round(bev.amountGrams)} ml
{/each} + {#each entries.filter(e => (e.liquidMl ?? 0) > 0) as e} +
+ {e.name} + {Math.round(e.liquidMl)} ml +
+ {/each}
{/if}