import { useId, useState } from "react"; import { Alert, Button, Form, ToggleButton, ToggleButtonGroup } from "react-bootstrap"; import { MealTemplate, RecipeFlavor, saveMealTemplate } from "../../../types"; import { RECIPE_FLAVOR_NAMES } from "../enums"; import { deriveRecipe, formatCalories, formatPrice, formatWeight, parseIntInput } from "../Utils"; import SourceInput from "./SourceInput"; import RecipeCategoryInput from "./RecipeCategoryInput"; import RecipeIngredientsEditor, { DraftIngredient, emptyIngredient, toDraftIngredient, toIngredient, } from "./RecipeIngredientsEditor"; type Props = { /** Upravovaný recept. Pokud chybí, zakládá se nový. */ template?: MealTemplate, /** * Jídlo ze seznamu jídel, ke kterému recept patří. Název a zdroj pak nejdou * měnit — mění se přejmenováním jídla, aby recept zůstal u svých záznamů. */ meal?: { name: string, source?: string }, onSaved: (templates: MealTemplate[]) => void, onCancel: () => void, }; /** Naplní formulář z receptu, nebo ho nechá prázdný pro nový. */ const createForm = (template?: MealTemplate, meal?: Props['meal']) => ({ name: meal?.name ?? template?.name ?? '', source: meal?.source ?? template?.source ?? '', category: template?.category ?? '', flavor: template?.flavor ?? RecipeFlavor.SLANE, note: template?.note ?? '', servings: template?.servings != null ? String(template.servings) : '', cookedWeight: template?.cookedWeight != null ? String(template.cookedWeight) : '', }); /** * Formulář receptu — vařeného jídla. Zapisují se suroviny a postup a hodnoty * porce se z nich dopočtou — stejně jako na serveru, tady jen jako živý náhled. */ const MealTemplateForm = ({ template, meal, onSaved, onCancel }: Readonly) => { const [form, setForm] = useState(() => createForm(template, meal)); const [ingredients, setIngredients] = useState(() => template?.ingredients?.length ? template.ingredients.map(toDraftIngredient) : [emptyIngredient()]); const [error, setError] = useState(); const [busy, setBusy] = useState(false); const flavorId = useId(); const update = (changes: Partial>) => setForm(current => ({ ...current, ...changes })); const filledIngredients = ingredients.filter(item => item.name.trim().length > 0).map(toIngredient); const recipe = deriveRecipe(filledIngredients, parseIntInput(form.servings), parseIntInput(form.cookedWeight)); const ingredientWeight = filledIngredients.reduce((sum, item) => sum + (item.weight ?? 0), 0); const canSave = form.name.trim().length > 0 && filledIngredients.length > 0 && !busy; const save = async () => { if (!canSave) return; setBusy(true); setError(undefined); // Hodnoty porce si server dopočte ze surovin sám const body = { ...(template ? { id: template.id } : {}), name: form.name.trim(), ...(form.source.trim() ? { source: form.source.trim() } : {}), ...(form.category.trim() ? { category: form.category.trim() } : {}), flavor: form.flavor, ...(form.note.trim() ? { note: form.note.trim() } : {}), ingredients: filledIngredients, servings: parseIntInput(form.servings), cookedWeight: parseIntInput(form.cookedWeight), }; const response = await saveMealTemplate({ body }); setBusy(false); if (response.data) { onSaved(response.data); } else { setError((response.error as { error?: string })?.error ?? 'Recept se nepodařilo uložit'); } }; return (

{template ? 'Úprava receptu' : 'Nový recept'}

{error && {error}} {!meal && (
Název update({ name: event.target.value })} />
Zdroj update({ source })} />
)}
Typ update({ category })} />
Chuť update({ flavor })} > {Object.values(RecipeFlavor).map(flavor => ( {RECIPE_FLAVOR_NAMES[flavor]} ))}
Suroviny
Výsledek
Počet porcí update({ servings: event.target.value.replace(/\D/g, '') })} />
Hmotnost hotového jídla
update({ cookedWeight: event.target.value.replace(/\D/g, '') })} /> g
vařením se mění, bez ní se vezme součet surovin
Celkem / jedna porce
{formatCalories(recipe.totalCalories)} {recipe.totalWeight != null && <> · {formatWeight(recipe.totalWeight)}} {recipe.caloriesPer100g != null && <> · {recipe.caloriesPer100g} kcal/100 g}
porce {formatWeight(recipe.weight)} · {formatCalories(recipe.calories)} {recipe.price != null && <> · {formatPrice(recipe.price)}}
Postup update({ note: event.target.value })} />
{recipe.calories != null && ( porce {formatCalories(recipe.calories)} )}
); }; export default MealTemplateForm;