import { useState } from 'react' import { api } from '../utils/api' function emptyIng() { return { source_name_raw: '', quantity_g: '' } } function mappingHint(name, learned) { const n = (name || '').trim().toLowerCase() if (!n) return null return (learned || []).find((m) => { const raw = (m.source_name_raw || '').toLowerCase() const norm = (m.source_name_normalized || '').toLowerCase() return raw === n || norm === n || raw.includes(n) || n.includes(norm) }) || null } export function RecipeForm({ defaultName, defaultPortions, defaultIngredients, learned, disabled, submitLabel, onSave, onCancel }) { const [name, setName] = useState(defaultName || '') const [portions, setPortions] = useState(defaultPortions != null ? String(defaultPortions) : '1') const [ings, setIngs] = useState( (defaultIngredients || []).length ? defaultIngredients.map((i) => ({ source_name_raw: i.source_name_raw || '', quantity_g: i.quantity_g ?? '' })) : [emptyIng()] ) const [error, setError] = useState(null) const [saving, setSaving] = useState(false) const save = async () => { const ingredients = ings .map((i) => ({ source_name_raw: (i.source_name_raw || '').trim(), quantity_g: i.quantity_g === '' ? null : parseFloat(String(i.quantity_g).replace(',', '.')), })) .filter((i) => i.source_name_raw) if (!name.trim()) { setError('Rezeptname fehlt') return } if (!ingredients.length) { setError('Mindestens eine Zutat angeben') return } setSaving(true) setError(null) try { await onSave({ name_raw: name.trim(), portions: parseFloat(String(portions).replace(',', '.')) || 1, ingredients, }) } catch (e) { setError(e.message) } finally { setSaving(false) } } return (

Zutaten, die schon zugeordnet sind, werden übernommen. Offene erscheinen danach in der Liste zum Zuordnen.

{error &&
{error}
} setName(e.target.value)} />

Zutaten (Name + Gramm)

{ings.map((ing, i) => { const mapped = mappingHint(ing.source_name_raw, learned) return (
setIngs((list) => list.map((x, j) => j === i ? { ...x, source_name_raw: e.target.value } : x))} /> setIngs((list) => list.map((x, j) => j === i ? { ...x, quantity_g: e.target.value } : x))} />
{ing.source_name_raw.trim() && (
{mapped ? `zugeordnet: ${mapped.food_name_de || mapped.source_name_raw}` : 'noch offen — nach dem Speichern in der Liste zuordnen'}
)}
) })}
{onCancel && ( )}
) } export default function FoodRecipeEditor({ recipes, learned, onChanged }) { const [open, setOpen] = useState(false) const [editing, setEditing] = useState(null) const [draft, setDraft] = useState(null) const [error, setError] = useState(null) const startNew = () => { setEditing('new') setDraft({ name: '', portions: '1' }) setError(null) setOpen(true) } const startEdit = (r) => { setEditing(r) setDraft(r) setError(null) setOpen(true) } const save = async (body) => { if (editing === 'new') await api.createNutritionRecipe(body) else await api.updateNutritionRecipe(editing.id, body) setEditing(null) setDraft(null) onChanged?.() } const remove = async (id) => { if (!confirm('Rezept wirklich löschen?')) return try { await api.deleteNutritionRecipe(id) if (editing && editing !== 'new' && editing.id === id) setEditing(null) onChanged?.() } catch (e) { setError(e.message) } } return (

Eigene Rezepte / Listen ({recipes.length})

{open && (
{error &&
{error}
} {recipes.map((r) => (
{r.name_raw}
{(r.ingredients || []).length} Zutaten · {r.portions || 1} Portionen {r.source === 'manual' ? ' · selbst angelegt' : ''}
))} {editing === 'new' && ( setEditing(null)} /> )} {editing && editing !== 'new' && ( setEditing(null)} /> )}
)}
) }