diff --git a/frontend/src/components/recipes/RecipeDetailPage.tsx b/frontend/src/components/recipes/RecipeDetailPage.tsx index fb0244c..4cf8eb1 100644 --- a/frontend/src/components/recipes/RecipeDetailPage.tsx +++ b/frontend/src/components/recipes/RecipeDetailPage.tsx @@ -1,10 +1,10 @@ -import { useParams } from "react-router-dom" -import type { RecipeModel } from "../../models/RecipeModel" -import { useEffect, useState } from "react" -import { fetchRecipe } from "../../api/points/RecipePoint" -import { getRecipeEditUrl, getRecipeListUrl } from "../../routes" +import {useParams} from "react-router-dom" +import type {RecipeModel} from "../../models/RecipeModel" +import {useEffect, useState} from "react" +import {fetchRecipe} from "../../api/points/RecipePoint" +import {getRecipeEditUrl, getRecipeListUrl} from "../../routes" import ButtonLink from "../basics/ButtonLink" -import { mapRecipeDtoToModel } from "../../mappers/RecipeMapper" +import {mapRecipeDtoToModel} from "../../mappers/RecipeMapper" /** @@ -12,153 +12,150 @@ import { mapRecipeDtoToModel } from "../../mappers/RecipeMapper" * including its ingredients, instructions, and image. */ export default function RecipeDetailPage() { - // Extract recipe ID from route params - const { id } = useParams<{ id: string }>() - // the recipe loaded from the backend, don't change this! it's required for scaling - const [recipe, setRecipe] = useState(null) - // Working copy for re-calculating ingredients - const [recipeWorkingCopy, setRecipeWorkingCopy] = useState(null) - // load recipe data whenever id changes - useEffect(() => { - const loadRecipe = async () => { - if (id) { - try { - // Fetch recipe data when editing an existing one - console.log("loading recipe with id", id) - const data = await fetchRecipe(id) - if (data.id != id) { - throw new Error("Id mismatch when loading recipes: " + id + " requested and " + data.id + " received!"); - } - setRecipe(mapRecipeDtoToModel(data)) - } catch (err) { - console.error(err) + // Extract recipe ID from route params + const {id} = useParams<{ id: string }>() + // the recipe loaded from the backend, don't change this! it's required for scaling + const [recipe, setRecipe] = useState(null) + // Working copy for re-calculating ingredients + const [recipeWorkingCopy, setRecipeWorkingCopy] = useState(null) + // load recipe data whenever id changes + useEffect(() => { + const loadRecipe = async () => { + if (id) { + // Fetch recipe data when editing an existing one + console.log("loading recipe with id", id) + const data = await fetchRecipe(id) + if (data.id != id) { + throw new Error("Id mismatch when loading recipes: " + id + " requested and " + data.id + " received!"); + } + setRecipe(mapRecipeDtoToModel(data)) + } } - } + + loadRecipe() + }, [id]) + + // set original recipe data and working copy when recipe changes + useEffect(() => { + setRecipeWorkingCopy(recipe); + }, [recipe]) + + + if (!recipe || !recipeWorkingCopy) { + return

Recipe not found.

} - loadRecipe() - }, [id]) - // set original recipe data and working copy when recipe changes - useEffect(() => { - setRecipeWorkingCopy(recipe); - }, [recipe]) + /** recalculate ingredients based on the amount of servings */ + 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 updatedIngredientGroupList = recipe.ingredientGroupList.map((ingGrp) => ({ + ...ingGrp, + ingredientList: ingGrp.ingredientList.map((ing) => ({ + ...ing, + amount: ing.amount * factor, + })) + })) - if (!recipe || !recipeWorkingCopy) { - return

Recipe not found.

- } + // Update working copy with new servings + recalculated ingredients + setRecipeWorkingCopy({ + ...recipeWorkingCopy, + servings: { + ...recipeWorkingCopy.servings, + amount: newAmount, + }, + ingredientGroupList: updatedIngredientGroupList, + }) + } + return ( + /*Container spanning entire screen used to center content horizontally */ +
+ {/* Container defining the maximum width of the content */} +
+ {/* Header - remains in position when scrolling */} +
+

{recipeWorkingCopy.title}

+
- /** recalculate ingredients based on the amount of servings */ - const recalculateIngredients = (newAmount: number) => { - // Always calculate factor from the *original recipe*, not the working copy - const factor = newAmount / recipe.servings.amount + {/* Content */} +
+ {/* Recipe image */} + {recipe.imageUrl && ( + {recipe.title} + )} - // Create a new ingredient list with updated amounts - const updatedIngredientGroupList = recipe.ingredientGroupList.map((ingGrp) => ({ - ...ingGrp, - ingredientList: ingGrp.ingredientList.map((ing) => ({ - ...ing, - amount: ing.amount * factor, - })) - })) + {/* Servings */} +
+

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

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

Zutaten

+
    + {recipeWorkingCopy.ingredientGroupList.map((group, i) => ( +
    + {/* the title is optional, only print if present */} + {group.title && group.title.trim() !== "" && ( +

    {group.title}

    + )} +
      + {group.ingredientList.map((ing, j) => ( +
    • + {ing.amount} {ing.unit ?? ""} {ing.name} +
    • + ))} +
    +
    + ))} +
- // Update working copy with new servings + recalculated ingredients - setRecipeWorkingCopy({ - ...recipeWorkingCopy, - servings: { - ...recipeWorkingCopy.servings, - amount: newAmount, - }, - ingredientGroupList: updatedIngredientGroupList, - }) - } + {/* Instructions - @todo add reasonable list delegate component*/} +
    + {recipe.instructionStepList.map((step, j) => ( +
  1. + {/* Step number circle */} +
    + {j + 1} +
    - return ( - /*Container spanning entire screen used to center content horizontally */ -
    - {/* Container defining the maximum width of the content */} -
    - {/* Header - remains in position when scrolling */} -
    -

    {recipeWorkingCopy.title}

    -
    + {/* Step text */} +

    {step.text}

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

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

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

Zutaten

-
    - {recipeWorkingCopy.ingredientGroupList.map((group, i) => ( -
    - {/* the title is optional, only print if present */} - {group.title && group.title.trim() !== "" && ( -

    {group.title}

    - )} -
      - {group.ingredientList.map((ing, j) => ( -
    • - {ing.amount} {ing.unit ?? ""} {ing.name} -
    • - ))} -
    + {/* Action buttons */} +
    + + +
    +
- ))} - - - {/* Instructions - @todo add reasonable list delegate component*/} -
    - {recipe.instructionStepList.map((step, j) => ( -
  1. - {/* Step number circle */} -
    - {j + 1} -
    - - {/* Step text */} -

    {step.text}

    -
  2. - ))} -
- - {/* Action buttons */} -
- -
-
-
-
- ) + ) }