Make amount for an ingredient optional.
This commit is contained in:
parent
3c9c94957f
commit
0dc2eb2e3c
4 changed files with 122 additions and 96 deletions
|
|
@ -3,25 +3,48 @@ import type {IngredientModel} from "../../models/IngredientModel"
|
||||||
import Button from "../basics/Button"
|
import Button from "../basics/Button"
|
||||||
import {ButtonType} from "../basics/BasicButtonDefinitions"
|
import {ButtonType} from "../basics/BasicButtonDefinitions"
|
||||||
|
|
||||||
/**
|
|
||||||
* Editor for handling the ingredient list
|
|
||||||
* Ingredients can be edited, added and removed
|
|
||||||
*/
|
|
||||||
type IngredientListEditorProps = {
|
type IngredientListEditorProps = {
|
||||||
ingredients: IngredientModel[]
|
ingredients: IngredientModel[]
|
||||||
onChange: (ingredients: IngredientModel[]) => void
|
onChange: (ingredients: IngredientModel[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editor for handling the ingredient list
|
||||||
|
*
|
||||||
|
* Ingredients can be edited, added and removed
|
||||||
|
* @param ingredient List of IngredientModels describing the ingredient
|
||||||
|
* @param onChange Method to call on changing an ingredient.
|
||||||
|
*/
|
||||||
export function IngredientListEditor({ingredients, onChange}: IngredientListEditorProps) {
|
export function IngredientListEditor({ingredients, onChange}: IngredientListEditorProps) {
|
||||||
const handleUpdate = (index: number, field: keyof IngredientModel, value: string | number) => {
|
const handleUpdate = (
|
||||||
const updated = ingredients.map((ing, i) =>
|
index: number,
|
||||||
i === index ? {...ing, [field]: field === "amount" ? Number(value) : value} : ing
|
field: keyof IngredientModel,
|
||||||
)
|
value: string | number | undefined
|
||||||
onChange(updated)
|
) => {
|
||||||
}
|
const updated = ingredients.map((ing, i) => {
|
||||||
|
if (i !== index) return ing;
|
||||||
|
|
||||||
|
if (field === "amount") {
|
||||||
|
// Handle optional numeric value
|
||||||
|
if (value === undefined || value === "") {
|
||||||
|
const {amount, ...rest} = ing; // remove the field
|
||||||
|
return rest as IngredientModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid number
|
||||||
|
return {...ing, amount: Number(value)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle other fields normally
|
||||||
|
return {...ing, [field]: value};
|
||||||
|
});
|
||||||
|
|
||||||
|
onChange(updated);
|
||||||
|
};
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
onChange([...ingredients, {name: "", amount: 0, unit: ""}])
|
onChange([...ingredients, {name: "", unit: ""}])
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRemove = (index: number) => {
|
const handleRemove = (index: number) => {
|
||||||
|
|
@ -36,8 +59,11 @@ export function IngredientListEditor({ingredients, onChange}: IngredientListEdit
|
||||||
type="number"
|
type="number"
|
||||||
className="input-field"
|
className="input-field"
|
||||||
placeholder="Menge"
|
placeholder="Menge"
|
||||||
value={ing.amount}
|
value={ing.amount === undefined || ing.amount === null ? "" : ing.amount}
|
||||||
onChange={e => handleUpdate(index, "amount", e.target.value)}
|
onChange={e => {
|
||||||
|
const value = e.target.value;
|
||||||
|
handleUpdate(index, "amount", value === "" ? undefined : Number(value));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
className="input-field w-20"
|
className="input-field w-20"
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ export default function RecipeDetailPage() {
|
||||||
...ingGrp,
|
...ingGrp,
|
||||||
ingredientList: ingGrp.ingredientList.map((ing) => ({
|
ingredientList: ingGrp.ingredientList.map((ing) => ({
|
||||||
...ing,
|
...ing,
|
||||||
amount: ing.amount * factor,
|
amount: ing.amount !== undefined ? ing.amount * factor : ing.amount,
|
||||||
}))
|
}))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
@ -121,7 +121,7 @@ export default function RecipeDetailPage() {
|
||||||
<ul className="default-list">
|
<ul className="default-list">
|
||||||
{group.ingredientList.map((ing, j) => (
|
{group.ingredientList.map((ing, j) => (
|
||||||
<li key={j}>
|
<li key={j}>
|
||||||
{ing.amount} {ing.unit ?? ""} {ing.name}
|
{ing.amount ?? ""} {ing.unit ?? ""} {ing.name}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,52 @@
|
||||||
import type { RecipeModel } from "../models/RecipeModel"
|
import type {RecipeModel} from "../models/RecipeModel"
|
||||||
import type { RecipeDto } from "../api/dtos/RecipeDto"
|
import type {RecipeDto} from "../api/dtos/RecipeDto"
|
||||||
import type { RecipeIngredientGroupDto } from "../api/dtos/RecipeIngredientGroupDto"
|
import type {RecipeIngredientGroupDto} from "../api/dtos/RecipeIngredientGroupDto"
|
||||||
import type { RecipeInstructionStepDto } from "../api/dtos/RecipeInstructionStepDto"
|
import type {RecipeInstructionStepDto} from "../api/dtos/RecipeInstructionStepDto"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps a RecipeDto (as returned by the backend) to the Recipe model
|
* Maps a RecipeDto (as returned by the backend) to the Recipe model
|
||||||
* used in the frontend application.
|
* used in the frontend application.
|
||||||
*/
|
*/
|
||||||
export function mapRecipeDtoToModel(dto: RecipeDto): RecipeModel {
|
export function mapRecipeDtoToModel(dto: RecipeDto): RecipeModel {
|
||||||
return {
|
return {
|
||||||
id: dto.id,
|
id: dto.id,
|
||||||
title: dto.title,
|
title: dto.title,
|
||||||
|
|
||||||
servings: {
|
servings: {
|
||||||
amount: dto.amount ?? 1,
|
amount: dto.amount ?? 1,
|
||||||
unit: dto.amountDescription ?? "",
|
unit: dto.amountDescription ?? "",
|
||||||
},
|
},
|
||||||
// join all instruction step texts into a single string for display
|
// join all instruction step texts into a single string for display
|
||||||
instructionStepList: dto.instructions
|
instructionStepList: dto.instructions
|
||||||
.sort((a, b) => a.sortOrder - b.sortOrder) // ensure correct order
|
.sort((a, b) => a.sortOrder - b.sortOrder) // ensure correct order
|
||||||
.map(step => ({
|
.map(step => ({
|
||||||
text: step.text,
|
text: step.text,
|
||||||
id: step.id,
|
id: step.id,
|
||||||
/* When mapping a stepDTO, it should already contain a UUID. If
|
/* When mapping a stepDTO, it should already contain a UUID. If
|
||||||
* If, however, for some reason, it does not, add a UUID as sorting
|
* If, however, for some reason, it does not, add a UUID as sorting
|
||||||
* steps in teh GUI requires a unique identifier for each item.
|
* steps in teh GUI requires a unique identifier for each item.
|
||||||
*/
|
*/
|
||||||
internalId: step.id !== undefined? step.id : crypto.randomUUID()
|
internalId: step.id !== undefined ? step.id : crypto.randomUUID()
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
|
||||||
ingredientGroupList: dto.ingredientGroups
|
ingredientGroupList: dto.ingredientGroups
|
||||||
.sort((a, b) => a.sortOrder - b.sortOrder) // ensure groups are ordered
|
.sort((a, b) => a.sortOrder - b.sortOrder) // ensure groups are ordered
|
||||||
.map(group => ({
|
.map(group => ({
|
||||||
id: group.id,
|
id: group.id,
|
||||||
title: group.title,
|
title: group.title,
|
||||||
ingredientList: group.ingredients
|
ingredientList: group.ingredients
|
||||||
.sort((a, b) => a.sortOrder - b.sortOrder) // ensure ingredients are ordered
|
.sort((a, b) => a.sortOrder - b.sortOrder) // ensure ingredients are ordered
|
||||||
.map(ing => ({
|
.map(ing => ({
|
||||||
id: ing.id,
|
id: ing.id,
|
||||||
name: ing.name ?? "", // @todo ensure that name and amount are indeed present
|
name: ing.name ?? "", // @todo ensure that name and amount are indeed present
|
||||||
amount: ing.amount ?? 0,
|
amount: ing.amount,
|
||||||
unit: ing.unit,
|
unit: ing.unit,
|
||||||
//subtext: ing.subtext ?? undefined,
|
//subtext: ing.subtext ?? undefined,
|
||||||
})),
|
})),
|
||||||
})),
|
})),
|
||||||
imageUrl: undefined, // not part of DTO yet, placeholder
|
imageUrl: undefined, // not part of DTO yet, placeholder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -54,37 +54,37 @@ export function mapRecipeDtoToModel(dto: RecipeDto): RecipeModel {
|
||||||
* for sending updates or creations to the backend.
|
* for sending updates or creations to the backend.
|
||||||
*/
|
*/
|
||||||
export function mapRecipeModelToDto(model: RecipeModel): RecipeDto {
|
export function mapRecipeModelToDto(model: RecipeModel): RecipeDto {
|
||||||
// Map instructions
|
// Map instructions
|
||||||
const instructionDtos: RecipeInstructionStepDto[] = model.instructionStepList.map(
|
const instructionDtos: RecipeInstructionStepDto[] = model.instructionStepList.map(
|
||||||
(step, index) => ({
|
(step, index) => ({
|
||||||
id: step.id,
|
id: step.id,
|
||||||
text: step.text,
|
text: step.text,
|
||||||
sortOrder: index + 1,
|
sortOrder: index + 1,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
// Map ingredients
|
// Map ingredients
|
||||||
const ingredientGroupDtos: RecipeIngredientGroupDto[] =
|
const ingredientGroupDtos: RecipeIngredientGroupDto[] =
|
||||||
model.ingredientGroupList.map((group, groupIndex) => ({
|
model.ingredientGroupList.map((group, groupIndex) => ({
|
||||||
id: group.id,
|
id: group.id,
|
||||||
title: group.title,
|
title: group.title,
|
||||||
sortOrder: groupIndex + 1, // sortOrder from list index
|
sortOrder: groupIndex + 1, // sortOrder from list index
|
||||||
ingredients: group.ingredientList.map((ing, ingIndex) => ({
|
ingredients: group.ingredientList.map((ing, ingIndex) => ({
|
||||||
id: ing.id,
|
id: ing.id,
|
||||||
name: ing.name,
|
name: ing.name,
|
||||||
amount: ing.amount,
|
amount: ing.amount,
|
||||||
unit: ing.unit,
|
unit: ing.unit,
|
||||||
sortOrder: ingIndex + 1, // sortOrder from index
|
sortOrder: ingIndex + 1, // sortOrder from index
|
||||||
//subtext: ing.subtext ?? null,
|
//subtext: ing.subtext ?? null,
|
||||||
})),
|
})),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: model.id,
|
id: model.id,
|
||||||
title: model.title,
|
title: model.title,
|
||||||
amount: model.servings.amount,
|
amount: model.servings.amount,
|
||||||
amountDescription: model.servings.unit,
|
amountDescription: model.servings.unit,
|
||||||
instructions: instructionDtos,
|
instructions: instructionDtos,
|
||||||
ingredientGroups: ingredientGroupDtos,
|
ingredientGroups: ingredientGroupDtos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,15 @@
|
||||||
* Represents a single ingredient in a recipe.
|
* Represents a single ingredient in a recipe.
|
||||||
*/
|
*/
|
||||||
export interface IngredientModel {
|
export interface IngredientModel {
|
||||||
id?: string
|
id?: string
|
||||||
/** Name of the ingredient (e.g. "Spaghetti") */
|
/** Name of the ingredient (e.g. "Spaghetti") */
|
||||||
name: string
|
name: string
|
||||||
|
|
||||||
/** Quantity required (e.g. 200, 1.5) */
|
/** Quantity required (e.g. 200, 1.5) */
|
||||||
amount: number
|
amount?: number
|
||||||
|
|
||||||
/** Unit of measurement (e.g. "g", "tbsp", "cups").
|
/** Unit of measurement (e.g. "g", "tbsp", "cups").
|
||||||
* Optional for cases like "1 egg".
|
* Optional for cases like "1 egg".
|
||||||
*/
|
*/
|
||||||
unit?: string
|
unit?: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue