Refactoring
This commit is contained in:
parent
34bbaa9df4
commit
9b53f6e676
11 changed files with 501 additions and 515 deletions
|
|
@ -1,16 +1,16 @@
|
|||
import { useState } from "react"
|
||||
import type { RecipeModel } from "../../models/RecipeModel"
|
||||
import type { IngredientGroupModel } from "../../models/IngredientGroupModel"
|
||||
import { IngredientGroupListEditor } from "./IngredientGroupListEditor"
|
||||
import {useState} from "react"
|
||||
import type {RecipeModel} from "../../models/RecipeModel"
|
||||
import type {IngredientGroupModel} from "../../models/IngredientGroupModel"
|
||||
import {IngredientGroupListEditor} from "./IngredientGroupListEditor"
|
||||
import Button from "../basics/Button"
|
||||
import { InstructionStepListEditor } from "./InstructionStepListEditor"
|
||||
import type { InstructionStepModel } from "../../models/InstructionStepModel"
|
||||
import { ButtonType } from "../basics/BasicButtonDefinitions"
|
||||
import {InstructionStepListEditor} from "./InstructionStepListEditor"
|
||||
import type {InstructionStepModel} from "../../models/InstructionStepModel"
|
||||
import {ButtonType} from "../basics/BasicButtonDefinitions"
|
||||
|
||||
type RecipeEditorProps = {
|
||||
recipe: RecipeModel
|
||||
onSave: (recipe: RecipeModel) => void
|
||||
onCancel: () => void
|
||||
recipe: RecipeModel
|
||||
onSave: (recipe: RecipeModel) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -18,138 +18,142 @@ type RecipeEditorProps = {
|
|||
* ingredients (with amount, unit, name), instructions, and image URL.
|
||||
* @todo adapt to ingredientGroups!
|
||||
*/
|
||||
export default function RecipeEditor({ recipe, onSave, onCancel }: RecipeEditorProps) {
|
||||
/** draft of the new recipe */
|
||||
const [draft, setDraft] = useState<RecipeModel>(recipe)
|
||||
/** Error list */
|
||||
const [errors, setErrors] = useState<{ title?: boolean; ingredients?: boolean }>({})
|
||||
export default function RecipeEditor({recipe, onSave, onCancel}: RecipeEditorProps) {
|
||||
/** draft of the new recipe */
|
||||
const [draft, setDraft] = useState<RecipeModel>(recipe)
|
||||
/** Error list */
|
||||
const [errors, setErrors] = useState<{ title?: boolean; ingredients?: boolean }>({})
|
||||
|
||||
/**
|
||||
* Update ingredients
|
||||
* @param ingredientGroupList updated ingredient groups and ingredients
|
||||
*/
|
||||
const updateIngredientGroupList = (ingredientGroupList: IngredientGroupModel[]) => {
|
||||
setDraft({ ...draft, ingredientGroupList })
|
||||
}
|
||||
|
||||
/**
|
||||
* Update instruction steps
|
||||
* @param instructionStepList updated instructions
|
||||
*/
|
||||
const updateInstructionList = (instructionStepList: InstructionStepModel[]) => {
|
||||
setDraft({ ...draft, instructionStepList })
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate recipe
|
||||
* @returns Information on the errors the validation encountered
|
||||
*/
|
||||
const validate = () => {
|
||||
const newErrors: { title?: boolean; ingredients?: boolean } = {}
|
||||
|
||||
// each recipe requires a title
|
||||
if (!draft.title.trim()) {
|
||||
newErrors.title = true
|
||||
}
|
||||
|
||||
/* there must be at least one ingredient group
|
||||
* no group may contain an empty ingredient list
|
||||
* @todo check whether all ingredients are valid
|
||||
* @todo enhance visualization of ingredient errors
|
||||
/**
|
||||
* Update ingredients
|
||||
* @param ingredientGroupList updated ingredient groups and ingredients
|
||||
*/
|
||||
if (!draft.ingredientGroupList || draft.ingredientGroupList.length === 0) {
|
||||
newErrors.ingredients = true
|
||||
} else {
|
||||
let isAnyIngredientListEmpty = draft.ingredientGroupList.some(
|
||||
ingGrp => {
|
||||
return !ingGrp.ingredientList || ingGrp.ingredientList.length === 0
|
||||
const updateIngredientGroupList = (ingredientGroupList: IngredientGroupModel[]) => {
|
||||
setDraft({...draft, ingredientGroupList})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update instruction steps
|
||||
* @param instructionStepList updated instructions
|
||||
*/
|
||||
const updateInstructionList = (instructionStepList: InstructionStepModel[]) => {
|
||||
setDraft({...draft, instructionStepList})
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate recipe
|
||||
* @returns Information on the errors the validation encountered
|
||||
*/
|
||||
const validate = () => {
|
||||
const newErrors: { title?: boolean; ingredients?: boolean } = {}
|
||||
|
||||
// each recipe requires a title
|
||||
if (!draft.title.trim()) {
|
||||
newErrors.title = true
|
||||
}
|
||||
)
|
||||
if(isAnyIngredientListEmpty){
|
||||
newErrors.ingredients = true
|
||||
}
|
||||
|
||||
/* there must be at least one ingredient group
|
||||
* no group may contain an empty ingredient list
|
||||
* @todo check whether all ingredients are valid
|
||||
* @todo enhance visualization of ingredient errors
|
||||
*/
|
||||
if (!draft.ingredientGroupList || draft.ingredientGroupList.length === 0) {
|
||||
newErrors.ingredients = true
|
||||
} else {
|
||||
const isAnyIngredientListEmpty = draft.ingredientGroupList.some(
|
||||
ingGrp => {
|
||||
return !ingGrp.ingredientList || ingGrp.ingredientList.length === 0
|
||||
}
|
||||
)
|
||||
if (isAnyIngredientListEmpty) {
|
||||
newErrors.ingredients = true
|
||||
}
|
||||
}
|
||||
|
||||
setErrors(newErrors)
|
||||
|
||||
return Object.keys(newErrors).length === 0
|
||||
}
|
||||
|
||||
setErrors(newErrors)
|
||||
|
||||
return Object.keys(newErrors).length === 0
|
||||
}
|
||||
/** Handles saving and ensures that the draft is only saved if valid */
|
||||
const handleSave = (draft: RecipeModel) => {
|
||||
if (validate()) {
|
||||
onSave(draft)
|
||||
/** Handles saving and ensures that the draft is only saved if valid */
|
||||
const handleSave = (draft: RecipeModel) => {
|
||||
if (validate()) {
|
||||
onSave(draft)
|
||||
}
|
||||
}
|
||||
}
|
||||
// ensure that there is a recipe and show error otherwise
|
||||
if (!recipe) return <div>Oops, there's no recipe in RecipeEditor...</div>
|
||||
// @todo add handling of images
|
||||
return (
|
||||
<div className="p-4 gap-10">
|
||||
<h2 className="content-title">
|
||||
{recipe.id ? "Edit Recipe" : "New Recipe"}
|
||||
</h2>
|
||||
// ensure that there is a recipe and show error otherwise
|
||||
if (!recipe) return <div>Oops, there's no recipe in RecipeEditor...</div>
|
||||
// @todo add handling of images
|
||||
return (
|
||||
/*Container spanning entire screen used to center content horizontally */
|
||||
<div className="app-bg">
|
||||
{/* Container defining the maximum width of the content */}
|
||||
<div className="content-container">
|
||||
<h1 className="content-title border-b-2 border-gray-300">
|
||||
{recipe.id ? "Rezept bearbeiten" : "Neues Rezept"}
|
||||
</h1>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="subsection-heading">Title</h3>
|
||||
<input
|
||||
className={`input-field ${errors.title ? "error-text" : ""}`}
|
||||
placeholder="Title"
|
||||
value={draft.title}
|
||||
onChange={e => setDraft({ ...draft, title: e.target.value })}
|
||||
/>
|
||||
{/* Servings */}
|
||||
<h3 className="subsection-heading">Servings</h3>
|
||||
<div className="columns-3 gap-2 flex items-center">
|
||||
<label>For</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input-field w-20"
|
||||
placeholder="1"
|
||||
value={draft.servings.amount}
|
||||
onChange={e => {
|
||||
const tempServings = draft.servings
|
||||
tempServings.amount = Number(e.target.value)
|
||||
setDraft({...draft, servings: tempServings})
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
className="input-field"
|
||||
placeholder="Persons"
|
||||
value={draft.servings.unit}
|
||||
onChange={e => {
|
||||
const tempServings = draft.servings
|
||||
tempServings.unit = e.target.value
|
||||
setDraft({...draft, servings: tempServings})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* Ingredient List - @todo better visualization of errors! */}
|
||||
<div className={errors.ingredients ? "border error-text rounded p-2" : ""}>
|
||||
<IngredientGroupListEditor
|
||||
ingredientGroupList={draft.ingredientGroupList}
|
||||
onChange={updateIngredientGroupList}
|
||||
/>
|
||||
</div>
|
||||
{/* Title */}
|
||||
<h2 className="section-heading">Titel</h2>
|
||||
<input
|
||||
className={`input-field ${errors.title ? "error-text" : ""}`}
|
||||
placeholder="Titel"
|
||||
value={draft.title}
|
||||
onChange={e => setDraft({...draft, title: e.target.value})}
|
||||
/>
|
||||
{/* Servings */}
|
||||
<h2 className="section-heading">Portionen</h2>
|
||||
<div className="columns-3 gap-2 flex items-center">
|
||||
<label>Für</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input-field w-20"
|
||||
placeholder="1"
|
||||
value={draft.servings.amount}
|
||||
onChange={e => {
|
||||
const tempServings = draft.servings
|
||||
tempServings.amount = Number(e.target.value)
|
||||
setDraft({...draft, servings: tempServings})
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
className="input-field"
|
||||
placeholder="Personen"
|
||||
value={draft.servings.unit}
|
||||
onChange={e => {
|
||||
const tempServings = draft.servings
|
||||
tempServings.unit = e.target.value
|
||||
setDraft({...draft, servings: tempServings})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* Ingredient List - @todo better visualization of errors! */}
|
||||
<div className={errors.ingredients ? "border error-text rounded p-2" : ""}>
|
||||
<IngredientGroupListEditor
|
||||
ingredientGroupList={draft.ingredientGroupList}
|
||||
onChange={updateIngredientGroupList}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Instruction List*/ }
|
||||
<InstructionStepListEditor
|
||||
instructionStepList={draft.instructionStepList}
|
||||
onChange={updateInstructionList}
|
||||
/>
|
||||
|
||||
{/* Instruction List*/}
|
||||
<InstructionStepListEditor
|
||||
instructionStepList={draft.instructionStepList}
|
||||
onChange={updateInstructionList}
|
||||
/>
|
||||
|
||||
<div className="button-group">
|
||||
{/* Save Button */}
|
||||
<Button
|
||||
onClick={() => handleSave(draft)}
|
||||
text={"Save"}
|
||||
buttonType={ButtonType.PrimaryButton}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => onCancel()}
|
||||
text={"Cancel"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="button-group">
|
||||
{/* Save Button */}
|
||||
<Button
|
||||
onClick={() => handleSave(draft)}
|
||||
text={"Speichern"}
|
||||
buttonType={ButtonType.PrimaryButton}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => onCancel()}
|
||||
text={"Abbrechen"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue