From e467ca7e9214a46b4167169819327bf4e975dff1 Mon Sep 17 00:00:00 2001 From: Anika Raemer Date: Sun, 7 Sep 2025 16:28:46 +0200 Subject: [PATCH] add calculation of ingredients based on servings --- frontend/src/App.css | 4 +- frontend/src/components/RecipeDetailView.tsx | 61 ++++++++++++++++---- frontend/src/components/RecipeEditor.tsx | 1 + frontend/src/mock_data/recipes.ts | 4 +- frontend/src/types/recipe.ts | 7 +++ 5 files changed, 63 insertions(+), 14 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index 4efbe93..240b32e 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -59,7 +59,7 @@ /* input field */ .input-field { - @apply border p-2 w-full mb-2 rounded placeholder-gray-400; + @apply border p-2 w-full rounded placeholder-gray-400; } .text-area { @@ -72,7 +72,7 @@ } /* lists */ - .default-list-item { + .default-list { @apply list-disc pl-6 mb-6 } diff --git a/frontend/src/components/RecipeDetailView.tsx b/frontend/src/components/RecipeDetailView.tsx index 5b9e7c9..e92e24f 100644 --- a/frontend/src/components/RecipeDetailView.tsx +++ b/frontend/src/components/RecipeDetailView.tsx @@ -1,5 +1,8 @@ import { useParams, Link } from "react-router-dom" import { recipes } from "../mock_data/recipes" +import type { Recipe } from "../types/recipe" +import { useState } from "react" + /** * Displays the full detail of a single recipe, @@ -10,28 +13,66 @@ export default function RecipeDetailView() { const { id } = useParams<{ id: string }>() const recipe = recipes.find((r) => r.id === id) + if (!recipe) { return

Recipe not found.

} - return ( + // Working copy for re-calculating ingredients + const [recipeWorkingCopy, updateRecipeWorkingCopy] = useState(recipe) + + const recalculateIngredients = (newAmount: number) => { + // Always calculate factor from the *original recipe*, not the working copy + const factor = newAmount / recipe.servings.amount + + // Create a new ingredient list with updated amounts + const updatedIngredients = recipe.ingredients.map((ing) => ({ + ...ing, + amount: ing.amount * factor, + })) + + // Update working copy with new servings + recalculated ingredients + updateRecipeWorkingCopy({ + ...recipeWorkingCopy, + servings: { + ...recipeWorkingCopy.servings, + amount: newAmount, + }, + ingredients: updatedIngredients, + }) + } + // @todo add a feature to recalculate ingredients based on servings + return (
-

{recipe.title}

+

{recipeWorkingCopy.title}

{/* Recipe image */} - {recipe.imageUrl && ( + {recipeWorkingCopy.imageUrl && ( {recipe.title} )} + {/* Servings */} +
+

For {recipeWorkingCopy.servings.amount} {recipeWorkingCopy.servings.unit}

+ { + recalculateIngredients(Number(e.target.value)) + } + } + /> +
{/* Ingredients */}

Zutaten

-

For {recipe.servings.amount} {recipe.servings.unit}

-
    - {recipe.ingredients.map((ing, i) => ( +
      + {recipeWorkingCopy.ingredients.map((ing, i) => (
    • {ing.amount} {ing.unit ?? ""} {ing.name}
    • @@ -40,7 +81,7 @@ export default function RecipeDetailView() { {/* Instructions */}

      Zubereitung

      -

      {recipe.instructions}

      +

      {recipeWorkingCopy.instructions}

      {/* Action buttons */}
      diff --git a/frontend/src/components/RecipeEditor.tsx b/frontend/src/components/RecipeEditor.tsx index 53d47cb..d52ab7c 100644 --- a/frontend/src/components/RecipeEditor.tsx +++ b/frontend/src/components/RecipeEditor.tsx @@ -20,6 +20,7 @@ export default function RecipeEditor({ recipe, onSave, onCancel }: RecipeEditorP setDraft({ ...draft, ingredients }) } if (!recipe) return
      Oops, there's no recipe in RecipeEditor...
      + // @todo add handling of images return (

      diff --git a/frontend/src/mock_data/recipes.ts b/frontend/src/mock_data/recipes.ts index 06e6203..48b6747 100644 --- a/frontend/src/mock_data/recipes.ts +++ b/frontend/src/mock_data/recipes.ts @@ -15,7 +15,7 @@ export const recipes: Recipe[] = [ { name: "Tomato Sauce", amount: 400, unit: "ml" } ], instructions: "Cook pasta. Prepare sauce. Mix together. Serve hot.", - imageUrl: "https://source.unsplash.com/400x300/?spaghetti" + //imageUrl: "https://source.unsplash.com/400x300/?spaghetti" }, { id: "2", @@ -30,6 +30,6 @@ export const recipes: Recipe[] = [ { name: "Olives", amount: 100, unit: "g"} ], instructions: "Cook pasta. Prepare sauce. Mix together. Serve hot.", - imageUrl: "https://source.unsplash.com/400x300/?spaghetti" + //imageUrl: "https://source.unsplash.com/400x300/?spaghetti" }, ] diff --git a/frontend/src/types/recipe.ts b/frontend/src/types/recipe.ts index 4681a2a..6589e58 100644 --- a/frontend/src/types/recipe.ts +++ b/frontend/src/types/recipe.ts @@ -4,6 +4,13 @@ import type { Servings } from "./servings" /** * Represents a recipe object in the application. */ +/* + * @todo ingredient groups! There may be serveral ingredient lists, each with a title. + * e.g. for the dough, for the filling, for the icing,... + * - add type ingredient group with an optional title and a list of ingredients + * - adapt RecipeDetailView + * - add an IngredientGroupListEditor for handling IngredientGroups +*/ export interface Recipe { /** Unique identifier for the recipe */ id: string