fix: recepty přesnuty k jídlům

feat: přidána podpora fotek
This commit is contained in:
Ondřej Anděl committed 2026-10-12 10:20:12 +02:00
1 parent efed5e6e86
commit e13f7ce8e5
39 files changed
+1266 -511

No files matched your search

+31 -43
View File
@@ -1,7 +1,6 @@
import { useState } from "react";
import { Alert, Button, Form } from "react-bootstrap";
import { MealTemplate, MealType, saveMealTemplate } from "../../../types";
import { MEAL_TYPES_IN_ORDER, MEAL_TYPE_ICONS, MEAL_TYPE_NAMES } from "../enums";
import { MealTemplate, saveMealTemplate } from "../../../types";
import { deriveRecipe, formatCalories, formatPrice, formatWeight, parseIntInput } from "../Utils";
import SourceInput from "./SourceInput";
import RecipeIngredientsEditor, {
@@ -11,15 +10,19 @@ import 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) => ({
name: template?.name ?? '',
source: template?.source ?? '',
mealType: (template?.mealType ?? '') as MealType | '',
const createForm = (template?: MealTemplate, meal?: Props['meal']) => ({
name: meal?.name ?? template?.name ?? '',
source: meal?.source ?? template?.source ?? '',
note: template?.note ?? '',
servings: template?.servings != null ? String(template.servings) : '',
cookedWeight: template?.cookedWeight != null ? String(template.cookedWeight) : '',
@@ -29,8 +32,8 @@ const createForm = (template?: MealTemplate) => ({
* 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, onSaved, onCancel }: Readonly<Props>) => {
const [form, setForm] = useState(() => createForm(template));
const MealTemplateForm = ({ template, meal, onSaved, onCancel }: Readonly<Props>) => {
const [form, setForm] = useState(() => createForm(template, meal));
const [ingredients, setIngredients] = useState<DraftIngredient[]>(() =>
template?.ingredients?.length ? template.ingredients.map(toDraftIngredient) : [emptyIngredient()]);
const [error, setError] = useState<string | undefined>();
@@ -54,7 +57,6 @@ const MealTemplateForm = ({ template, onSaved, onCancel }: Readonly<Props>) => {
...(template ? { id: template.id } : {}),
name: form.name.trim(),
...(form.source.trim() ? { source: form.source.trim() } : {}),
...(form.mealType ? { mealType: form.mealType } : {}),
...(form.note.trim() ? { note: form.note.trim() } : {}),
ingredients: filledIngredients,
servings: parseIntInput(form.servings),
@@ -75,38 +77,26 @@ const MealTemplateForm = ({ template, onSaved, onCancel }: Readonly<Props>) => {
{error && <Alert variant="danger" className="py-2 small">{error}</Alert>}
<div className="row g-3 mb-3">
<div className="col-sm-5">
<Form.Label>Název</Form.Label>
<Form.Control
value={form.name}
placeholder="Např. Kuřecí rizoto"
onChange={event => update({ name: event.target.value })}
/>
{!meal && (
<div className="row g-3 mb-3">
<div className="col-sm-7">
<Form.Label>Název</Form.Label>
<Form.Control
value={form.name}
placeholder="Např. Kuřecí rizoto"
onChange={event => update({ name: event.target.value })}
/>
</div>
<div className="col-sm-5">
<Form.Label>Zdroj</Form.Label>
<SourceInput
value={form.source}
placeholder="doma"
onChange={source => update({ source })}
/>
</div>
</div>
<div className="col-sm-4">
<Form.Label>Zdroj</Form.Label>
<SourceInput
value={form.source}
placeholder="doma"
onChange={source => update({ source })}
/>
</div>
<div className="col-sm-3">
<Form.Label>Chod</Form.Label>
<Form.Select
value={form.mealType}
onChange={event => update({ mealType: event.target.value as MealType | '' })}
>
<option value="">— vybrat při použití —</option>
{MEAL_TYPES_IN_ORDER.map(type => (
<option key={type} value={type}>
{MEAL_TYPE_ICONS[type]} {MEAL_TYPE_NAMES[type]}
</option>
))}
</Form.Select>
</div>
</div>
)}
<fieldset className="meal-fieldset mb-3">
<legend>Suroviny</legend>
@@ -168,9 +158,7 @@ const MealTemplateForm = ({ template, onSaved, onCancel }: Readonly<Props>) => {
{recipe.calories != null && (
<span className="workout-draft-calories">porce {formatCalories(recipe.calories)}</span>
)}
{template && (
<Button variant="outline-secondary" size="sm" onClick={() => onCancel()}>Zrušit úpravu</Button>
)}
<Button variant="outline-secondary" size="sm" onClick={() => onCancel()}>Zrušit</Button>
<Button size="sm" onClick={() => save()} disabled={!canSave}>
{template ? 'Uložit změny' : 'Vytvořit recept'}
</Button>