import { useEffect, useRef, useState } from 'react' import { api } from '../utils/api' import FoodRecipeDialog from './FoodRecipeDialog' export default function FoodRecipeEditor({ learned: learnedProp, highlightId, onChanged }) { const [recipes, setRecipes] = useState([]) const [learned, setLearned] = useState(learnedProp || []) const [q, setQ] = useState('') const [visible, setVisible] = useState(40) const [editing, setEditing] = useState(null) const [error, setError] = useState(null) const [notice, setNotice] = useState(null) const [importing, setImporting] = useState(false) const [loading, setLoading] = useState(true) const listRef = useRef(null) const highlightRef = useRef(null) const openedHighlight = useRef(null) const load = async () => { try { const [r, m] = await Promise.all([ api.listNutritionRecipes(true), learnedProp ? Promise.resolve(learnedProp) : api.listMyFoodMappings().catch(() => []), ]) setRecipes(Array.isArray(r) ? r : []) setLearned(Array.isArray(m) ? m : (learnedProp || [])) setError(null) } catch (e) { setError(e.message) } finally { setLoading(false) } } useEffect(() => { load() }, []) useEffect(() => { if (learnedProp) setLearned(learnedProp) }, [learnedProp]) const startNew = () => { setEditing('new') setError(null) } const startEdit = async (r) => { setError(null) try { const full = (r.ingredients && r.ingredients.length) ? r : await api.getNutritionRecipe(r.id) setEditing(full) } catch (e) { setError(e.message) } } useEffect(() => { if (!highlightId || !recipes.length) return highlightRef.current?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) if (openedHighlight.current === highlightId) return const r = recipes.find((x) => x.id === highlightId) if (!r) return openedHighlight.current = highlightId startEdit(r) }, [highlightId, recipes]) const filtered = recipes.filter((r) => { const term = q.trim().toLowerCase() if (!term) return true return `${r.name_raw || ''} ${r.name_normalized || ''}`.toLowerCase().includes(term) }) const shown = filtered.slice(0, visible) const save = async (body) => { if (editing === 'new') await api.createNutritionRecipe(body) else await api.updateNutritionRecipe(editing.id, body) setEditing(null) await load() onChanged?.() } const remove = async (id) => { if (!confirm('Liste wirklich löschen? Tagebuchzeilen fallen dann wieder unter Zuordnen.')) return try { await api.deleteNutritionRecipe(id) if (editing && editing !== 'new' && editing.id === id) setEditing(null) await load() onChanged?.() } catch (e) { setError(e.message) } } const importLists = async (file) => { if (!file) return setImporting(true) setError(null) try { const res = await api.importFddbLists(file) await load() onChanged?.() const pending = res.rebuild_pending ? ' Tageswerte werden im Hintergrund nachgerechnet.' : '' setNotice(`${res.recipes} Listen importiert, ${res.items_linked || 0} Tagebuchzeilen verknüpft.${pending}`) } catch (e) { setError(e.message) } finally { setImporting(false) } } return (
Roh-Kombinationen (Müsli, Bowl, Smoothie) — nicht gekochte Gerichte. Import aus FDDB oder selbst anlegen. Zutaten danach unter Zuordnen mappen.
{error &&{loading ? 'Lade Listen…' : `${recipes.length} Listen`}
{recipes.length > 8 && ( { setQ(e.target.value); setVisible(40) }} /> )} {!loading && recipes.length === 0 && (Noch keine Listen. FDDB-Export (`lists_*.csv`) importieren oder eine neue anlegen.
)} {shown.map((r) => (