renamed models, added mapper for recipes
This commit is contained in:
parent
7a6f5b5bcd
commit
8027fce80d
21 changed files with 164 additions and 61 deletions
5
frontend/src/api/dtos/AbstractDto.ts
Normal file
5
frontend/src/api/dtos/AbstractDto.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export abstract class AbstractDto {
|
||||
id?: string;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
import { AbstractDto } from "./AbstractDto.js";
|
||||
import { AbstractDto } from "./AbstractDto.ts";
|
||||
import { RecipeIngredientGroupDto } from "./RecipeIngredientGroupDto.js";
|
||||
import { RecipeInstructionStepDto } from "./RecipeInstructionStepDto.js";
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { AbstractDto } from "./AbstractDto.js";
|
||||
import { AbstractDto } from "./AbstractDto.ts";
|
||||
|
||||
export class RecipeIngredientDto extends AbstractDto{
|
||||
name!: string;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { AbstractDto } from "./AbstractDto.js";
|
||||
import { AbstractDto } from "./AbstractDto.ts";
|
||||
import { RecipeIngredientDto } from "./RecipeIngredientDto.js";
|
||||
|
||||
export class RecipeIngredientGroupDto extends AbstractDto{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { UUID } from "crypto";
|
||||
import { AbstractDto } from "./AbstractDto.js";
|
||||
import { AbstractDto } from "./AbstractDto.ts";
|
||||
|
||||
export class RecipeInstructionStepDto extends AbstractDto{
|
||||
text!: string;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { AbstractDto } from "./AbstractDto.js";
|
||||
import { AbstractDto } from "./AbstractDto.ts";
|
||||
|
||||
export class UserDto extends AbstractDto {
|
||||
firstName?: string;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import type { Recipe } from "../../types/recipe"
|
||||
import type { LoginRequestDto } from "../dtos/LoginRequestDto";
|
||||
import type { LoginResponseDto } from "../dtos/LoginResponseDto";
|
||||
import { get, postJson, putJson } from "../utils/requests";
|
||||
import { postJson } from "../utils/requests";
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Recipe } from "../../types/recipe"
|
||||
import { get, postJson, putJson } from "../utils/requests";
|
||||
import type { RecipeModel } from "../../models/RecipeModel"
|
||||
import { get } from "../utils/requests";
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -18,7 +18,7 @@ const RECIPE_URL = `${BASE_URL}/compact-recipe`
|
|||
* @param searchString Search string for filtering recipeList
|
||||
* @returns Array of recipe
|
||||
*/
|
||||
export async function fetchRecipeList(searchString : string): Promise<Recipe[]> {
|
||||
export async function fetchRecipeList(searchString : string): Promise<RecipeModel[]> {
|
||||
let url : string = RECIPE_URL;
|
||||
// if there's a search string add it as query parameter
|
||||
if(searchString && searchString !== ""){
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Recipe } from "../../types/recipe"
|
||||
import type { RecipeDto } from "../dtos/RecipeDto";
|
||||
import { get, postJson, putJson } from "../utils/requests";
|
||||
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ const RECIPE_URL = `${BASE_URL}/recipe`
|
|||
* @param id ID of the recipe to load
|
||||
* @returns A single recipe
|
||||
*/
|
||||
export async function fetchRecipe(id: string): Promise<Recipe> {
|
||||
export async function fetchRecipe(id: string): Promise<RecipeDto> {
|
||||
const res = await get(`${RECIPE_URL}/${id}`)
|
||||
return res.json()
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ export async function fetchRecipe(id: string): Promise<Recipe> {
|
|||
* @param recipe Recipe to create
|
||||
* @returns Saved recipe
|
||||
*/
|
||||
export async function createRecipe(recipe: Recipe): Promise<Recipe> {
|
||||
export async function createRecipe(recipe: RecipeDto): Promise<RecipeDto> {
|
||||
const res = await postJson(RECIPE_URL, JSON.stringify(recipe));
|
||||
return res.json();
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ export async function createRecipe(recipe: Recipe): Promise<Recipe> {
|
|||
* @param recipe Recipe to save. This recipe must have an ID!
|
||||
* @returns Saved recipe
|
||||
*/
|
||||
export async function updateRecipe(recipe: Recipe): Promise<Recipe> {
|
||||
export async function updateRecipe(recipe: RecipeDto): Promise<RecipeDto> {
|
||||
const res = await putJson(`${RECIPE_URL}/${recipe.id}`, JSON.stringify(recipe));
|
||||
return res.json();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
|
||||
const BASE_URL = import.meta.env.VITE_API_BASE;
|
||||
|
||||
export function createBasicHeader() : Headers {
|
||||
const headers = new Headers();
|
||||
//headers.set('Access-Control-Allow-Origin', '*');
|
||||
|
|
|
|||
|
|
@ -2,25 +2,25 @@
|
|||
* Editor for ingredient groups
|
||||
*/
|
||||
|
||||
import type { Ingredient } from "../../types/ingredient"
|
||||
import type { IngredientGroup } from "../../types/ingredientGroup"
|
||||
import type { IngredientModel } from "../../models/IngredientModel"
|
||||
import type { IngredientGroupModel } from "../../models/IngredientGroupModel"
|
||||
import Button, { ButtonType } from "../basics/Button"
|
||||
import SvgIcon, { Icon } from "../basics/SvgIcon"
|
||||
import { IngredientListEditor } from "./IngredientListEditor"
|
||||
|
||||
type IngredientGroupListEditorProps = {
|
||||
ingredientGroupList: IngredientGroup[]
|
||||
onChange: (ingredientGroupList: IngredientGroup[]) => void
|
||||
ingredientGroupList: IngredientGroupModel[]
|
||||
onChange: (ingredientGroupList: IngredientGroupModel[]) => void
|
||||
}
|
||||
|
||||
export function IngredientGroupListEditor({ ingredientGroupList, onChange }: IngredientGroupListEditorProps) {
|
||||
const handleUpdate = (index: number, field: keyof IngredientGroup, value: string|Ingredient[] ) => {
|
||||
const handleUpdate = (index: number, field: keyof IngredientGroupModel, value: string|IngredientModel[] ) => {
|
||||
const updated = ingredientGroupList.map((ingGrp, i) =>
|
||||
i === index ? { ...ingGrp, [field]: value} : ingGrp
|
||||
)
|
||||
onChange(updated)
|
||||
}
|
||||
const updateIngredientList = (index: number, ingredientList: Ingredient[]) => {
|
||||
const updateIngredientList = (index: number, ingredientList: IngredientModel[]) => {
|
||||
handleUpdate(index, "ingredientList", ingredientList)
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ export function IngredientGroupListEditor({ ingredientGroupList, onChange }: Ing
|
|||
}
|
||||
return (
|
||||
<div>
|
||||
{/* remove bottom margin from this headingas the group card has a top padding */}
|
||||
{/* remove bottom margin from this headings the group card has a top padding */}
|
||||
<h3 className="subsection-heading" >Ingredient Groups</h3>
|
||||
{ingredientGroupList.map((ingGrp, index) => (
|
||||
<div key={index} className="ingredient-group-card mb-4">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Ingredient } from "../../types/ingredient"
|
||||
import type { IngredientModel } from "../../models/IngredientModel"
|
||||
import Button, { ButtonType } from "../basics/Button"
|
||||
import SvgIcon, { Icon } from "../basics/SvgIcon"
|
||||
|
||||
|
|
@ -7,12 +7,12 @@ import SvgIcon, { Icon } from "../basics/SvgIcon"
|
|||
* Ingredients can be edited, added and removed
|
||||
*/
|
||||
type IngredientListEditorProps = {
|
||||
ingredients: Ingredient[]
|
||||
onChange: (ingredients: Ingredient[]) => void
|
||||
ingredients: IngredientModel[]
|
||||
onChange: (ingredients: IngredientModel[]) => void
|
||||
}
|
||||
|
||||
export function IngredientListEditor({ ingredients, onChange }: IngredientListEditorProps) {
|
||||
const handleUpdate = (index: number, field: keyof Ingredient, value: string | number) => {
|
||||
const handleUpdate = (index: number, field: keyof IngredientModel, value: string | number) => {
|
||||
const updated = ingredients.map((ing, i) =>
|
||||
i === index ? { ...ing, [field]: field === "amount" ? Number(value) : value } : ing
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { useParams } from "react-router-dom"
|
||||
import type { Recipe } from "../../types/recipe"
|
||||
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"
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -14,9 +15,9 @@ 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<Recipe | null>(null)
|
||||
const [recipe, setRecipe] = useState<RecipeModel | null>(null)
|
||||
// Working copy for re-calculating ingredients
|
||||
const [recipeWorkingCopy, setRecipeWorkingCopy] = useState<Recipe|null>(null)
|
||||
const [recipeWorkingCopy, setRecipeWorkingCopy] = useState<RecipeModel|null>(null)
|
||||
// load recipe data whenever id changes
|
||||
useEffect(() => {
|
||||
const loadRecipe = async () => {
|
||||
|
|
@ -25,7 +26,10 @@ export default function RecipeDetailPage() {
|
|||
// Fetch recipe data when editing an existing one
|
||||
console.log("loading recipe with id", id)
|
||||
const data = await fetchRecipe(id)
|
||||
setRecipe(data)
|
||||
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)
|
||||
}
|
||||
|
|
@ -125,7 +129,7 @@ export default function RecipeDetailPage() {
|
|||
{/* Action buttons */}
|
||||
<div className="button-group">
|
||||
<ButtonLink
|
||||
to={getRecipeEditUrl(recipe.id)}
|
||||
to={recipe.id !== undefined ? getRecipeEditUrl(recipe.id) : getRecipeListUrl()} // @todo show error instead
|
||||
className="basic-button primary-button-bg primary-button-text"
|
||||
text="Bearbeiten"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import { useParams, useNavigate } from "react-router-dom"
|
||||
import { useEffect, useState } from "react"
|
||||
import type { Recipe } from "../../types/recipe"
|
||||
import type { RecipeModel } from "../../models/RecipeModel"
|
||||
import RecipeEditor from "./RecipeEditor"
|
||||
import { fetchRecipe, createRecipe, updateRecipe } from "../../api/points/RecipePoint"
|
||||
import { getRecipeDetailUrl, getRecipeListUrl } from "../../routes"
|
||||
import { mapRecipeDtoToModel, mapRecipeModelToDto } from "../../mappers/recipeMapper"
|
||||
import type { RecipeDto } from "../../api/dtos/RecipeDto"
|
||||
|
||||
export default function RecipeEditPage() {
|
||||
// Extract recipe ID from route params
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const [recipe, setRecipe] = useState<Recipe | null>(null)
|
||||
const [recipe, setRecipe] = useState<RecipeModel | null>(null)
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -16,8 +18,8 @@ export default function RecipeEditPage() {
|
|||
if (id) {
|
||||
try {
|
||||
// Fetch recipe data when editing an existing one
|
||||
const data = await fetchRecipe(id)
|
||||
setRecipe(data)
|
||||
const data : RecipeDto = await fetchRecipe(id);
|
||||
setRecipe(mapRecipeDtoToModel(data));
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
|
|
@ -40,12 +42,13 @@ export default function RecipeEditPage() {
|
|||
loadRecipe()
|
||||
}, [id])
|
||||
|
||||
const handleSave = async (updated: Recipe) => {
|
||||
const handleSave = async (updated: RecipeModel) => {
|
||||
try {
|
||||
const dto = mapRecipeModelToDto(updated);
|
||||
if (updated.id) {
|
||||
await updateRecipe(updated)
|
||||
await updateRecipe(dto)
|
||||
} else {
|
||||
await createRecipe(updated)
|
||||
await createRecipe(dto)
|
||||
}
|
||||
navigateBack();
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { useState } from "react"
|
||||
import type { Recipe } from "../../types/recipe"
|
||||
import type { IngredientGroup } from "../../types/ingredientGroup"
|
||||
import type { RecipeModel } from "../../models/RecipeModel"
|
||||
import type { IngredientGroupModel } from "../../models/IngredientGroupModel"
|
||||
import { IngredientGroupListEditor } from "./IngredientGroupListEditor"
|
||||
import Button, { ButtonType } from "../basics/Button"
|
||||
|
||||
type RecipeEditorProps = {
|
||||
recipe: Recipe
|
||||
onSave: (recipe: Recipe) => void
|
||||
recipe: RecipeModel
|
||||
onSave: (recipe: RecipeModel) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ type RecipeEditorProps = {
|
|||
*/
|
||||
export default function RecipeEditor({ recipe, onSave, onCancel }: RecipeEditorProps) {
|
||||
/** draft of the new recipe */
|
||||
const [draft, setDraft] = useState<Recipe>(recipe)
|
||||
const [draft, setDraft] = useState<RecipeModel>(recipe)
|
||||
/** Error list */
|
||||
const [errors, setErrors] = useState<{ title?: boolean; ingredients?: boolean }>({})
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ export default function RecipeEditor({ recipe, onSave, onCancel }: RecipeEditorP
|
|||
* Update ingredients
|
||||
* @param ingredients new ingredients
|
||||
*/
|
||||
const updateIngredientGroupList = (ingredientGroupList: IngredientGroup[]) => {
|
||||
const updateIngredientGroupList = (ingredientGroupList: IngredientGroupModel[]) => {
|
||||
setDraft({ ...draft, ingredientGroupList })
|
||||
}
|
||||
/**
|
||||
|
|
@ -63,7 +63,7 @@ export default function RecipeEditor({ recipe, onSave, onCancel }: RecipeEditorP
|
|||
return Object.keys(newErrors).length === 0
|
||||
}
|
||||
/** Handles saving and ensures that the draft is only saved if valid */
|
||||
const handleSave = (draft: Recipe) => {
|
||||
const handleSave = (draft: RecipeModel) => {
|
||||
if (validate()) {
|
||||
onSave(draft)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useEffect, useState } from "react"
|
||||
import RecipeListItem from "./RecipeListItem"
|
||||
import type { Recipe } from "../../types/recipe"
|
||||
import type { RecipeModel } from "../../models/RecipeModel"
|
||||
import { fetchRecipeList } from "../../api/points/CompactRecipePoint"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { getRecipeAddUrl, getRecipeDetailUrl } from "../../routes"
|
||||
import { getRecipeAddUrl, getRecipeDetailUrl, getRecipeListUrl } from "../../routes"
|
||||
import RecipeListToolbar from "./RecipeListToolbar"
|
||||
|
||||
/**
|
||||
|
|
@ -13,7 +13,7 @@ import RecipeListToolbar from "./RecipeListToolbar"
|
|||
export default function RecipeListPage() {
|
||||
|
||||
const navigate = useNavigate()
|
||||
const [recipeList, setRecipeList] = useState<Recipe[]|null>(null)
|
||||
const [recipeList, setRecipeList] = useState<RecipeModel[]|null>(null)
|
||||
const [searchString, setSearchString] = useState<string>("")
|
||||
// load recipes once on render and whenever search string changes
|
||||
// @todo add delay. Only reload list if the search string hasn't changed for ~200 ms
|
||||
|
|
@ -23,6 +23,7 @@ export default function RecipeListPage() {
|
|||
try {
|
||||
// Fetch recipe list
|
||||
const data = await fetchRecipeList(searchString)
|
||||
// @todo add and use compact recipe mapper
|
||||
setRecipeList(data)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
|
|
@ -57,7 +58,7 @@ export default function RecipeListPage() {
|
|||
<RecipeListItem
|
||||
key={recipe.id}
|
||||
title={recipe.title}
|
||||
targetPath={getRecipeDetailUrl(recipe.id)}
|
||||
targetPath={recipe.id !== undefined ? getRecipeDetailUrl(recipe.id) : getRecipeListUrl()} // @todo proper error handling
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
91
frontend/src/mappers/recipeMapper.ts
Normal file
91
frontend/src/mappers/recipeMapper.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import type { RecipeModel } from "../models/RecipeModel"
|
||||
import type { RecipeDto } from "../api/dtos/RecipeDto"
|
||||
import type { RecipeIngredientGroupDto } from "../api/dtos/RecipeIngredientGroupDto"
|
||||
|
||||
/**
|
||||
* Maps a RecipeDto (as returned by the backend) to the Recipe model
|
||||
* used in the frontend application.
|
||||
*/
|
||||
export function mapRecipeDtoToModel(dto: RecipeDto): RecipeModel {
|
||||
return {
|
||||
id: dto.id,
|
||||
title: dto.title,
|
||||
|
||||
servings: {
|
||||
amount: dto.amount ?? 1,
|
||||
unit: dto.amountDescription ?? "",
|
||||
},
|
||||
// @todo implement steps in frontend
|
||||
// join all instruction step texts into a single string for display
|
||||
instructions: dto.instructions
|
||||
.sort((a, b) => a.sortOrder - b.sortOrder) // ensure correct order
|
||||
.map(step => step.text)
|
||||
.join("\n"),
|
||||
|
||||
ingredientGroupList: dto.ingredientGroups
|
||||
.sort((a, b) => a.sortOrder - b.sortOrder) // ensure groups are ordered
|
||||
.map(group => ({
|
||||
id: group.id,
|
||||
title: group.title,
|
||||
ingredientList: group.ingredients
|
||||
.sort((a, b) => a.sortOrder - b.sortOrder) // ensure ingredients are ordered
|
||||
.map(ing => ({
|
||||
id: ing.id,
|
||||
name: ing.name ?? "", // @todo ensure that name and amount are indeed present
|
||||
amount: ing.amount ?? 0,
|
||||
unit: ing.unit,
|
||||
//subtext: ing.subtext ?? undefined,
|
||||
})),
|
||||
})),
|
||||
imageUrl: undefined, // not part of DTO yet, placeholder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a Recipe model (as used in the frontend) back to a RecipeDto
|
||||
* for sending updates or creations to the backend.
|
||||
*/
|
||||
export function mapRecipeModelToDto(model: RecipeModel): RecipeDto {
|
||||
// Split instructions string back into steps
|
||||
// @todo implement instructions properly...
|
||||
/* const instructionLines = model.instructions
|
||||
.split("\n")
|
||||
.map(line => line.trim())
|
||||
.filter(line => line.length > 0)
|
||||
|
||||
const instructionDtos: RecipeInstructionStepDto[] = instructionLines.map(
|
||||
(text, index) => ({
|
||||
id: crypto.randomUUID(), // or keep existing IDs if you track them in model
|
||||
text,
|
||||
sortOrder: index + 1,
|
||||
})
|
||||
) */
|
||||
const instructionDtos = [{
|
||||
text: model.instructions,
|
||||
sortOrder: 1
|
||||
}]
|
||||
|
||||
const ingredientGroupDtos: RecipeIngredientGroupDto[] =
|
||||
model.ingredientGroupList.map((group, groupIndex) => ({
|
||||
id: group.id,
|
||||
title: group.title,
|
||||
sortOrder: groupIndex + 1, // sortOrder from list index
|
||||
ingredients: group.ingredientList.map((ing, ingIndex) => ({
|
||||
id: ing.id,
|
||||
name: ing.name,
|
||||
amount: ing.amount,
|
||||
unit: ing.unit,
|
||||
sortOrder: ingIndex + 1, // sortOrder from index
|
||||
//subtext: ing.subtext ?? null,
|
||||
})),
|
||||
}))
|
||||
|
||||
return {
|
||||
id: model.id,
|
||||
title: model.title,
|
||||
amount: model.servings.amount,
|
||||
amountDescription: model.servings.unit,
|
||||
instructions: instructionDtos,
|
||||
ingredientGroups: ingredientGroupDtos,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Ingredient } from "./ingredient"
|
||||
import type { IngredientModel } from "./IngredientModel"
|
||||
/**
|
||||
* A group of ingredients
|
||||
* Consisting of title and ingredient list, this interface is used to group
|
||||
|
|
@ -6,7 +6,8 @@ import type { Ingredient } from "./ingredient"
|
|||
* icing of a cake
|
||||
*/
|
||||
|
||||
export interface IngredientGroup {
|
||||
export interface IngredientGroupModel {
|
||||
id?: string
|
||||
/**
|
||||
* Title of the group describing its purpose
|
||||
* The title is optional as recipes consisting of a single ingredient group usually don't
|
||||
|
|
@ -14,5 +15,5 @@ export interface IngredientGroup {
|
|||
*/
|
||||
title? : string
|
||||
/** Ingredients */
|
||||
ingredientList : Ingredient[]
|
||||
ingredientList : IngredientModel[]
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
/**
|
||||
* Represents a single ingredient in a recipe.
|
||||
*/
|
||||
export interface Ingredient {
|
||||
export interface IngredientModel {
|
||||
id?: string
|
||||
/** Name of the ingredient (e.g. "Spaghetti") */
|
||||
name: string
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type { IngredientGroup } from "./ingredientGroup"
|
||||
import type { Servings } from "./servings"
|
||||
import type { IngredientGroupModel } from "./IngredientGroupModel"
|
||||
import type { ServingsModel } from "./ServingsModel"
|
||||
|
||||
/**
|
||||
* Represents a recipe object in the application.
|
||||
|
|
@ -11,21 +11,21 @@ import type { Servings } from "./servings"
|
|||
* - adapt RecipeDetailView
|
||||
* - add an IngredientGroupListEditor for handling IngredientGroups
|
||||
*/
|
||||
export interface Recipe {
|
||||
export interface RecipeModel {
|
||||
/** Unique identifier for the recipe */
|
||||
id: string
|
||||
id?: string
|
||||
|
||||
/** Title of the recipe */
|
||||
title: string
|
||||
|
||||
/** List of ingredients groups containing the ingredients of the recipe */
|
||||
ingredientGroupList: IngredientGroup[]
|
||||
ingredientGroupList: IngredientGroupModel[]
|
||||
|
||||
/** Preparation instructions */
|
||||
instructions: string
|
||||
|
||||
/** Number of servings, e.g., for 4 person, 12 cupcakes, 2 glasses */
|
||||
servings: Servings
|
||||
servings: ServingsModel
|
||||
|
||||
/** Unit for the quantity */
|
||||
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
* Defines how many servings of the dish are prepared when following the recipe
|
||||
*/
|
||||
|
||||
export interface Servings{
|
||||
export interface ServingsModel{
|
||||
/** Amount of servings */
|
||||
amount: number,
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue