import { useEffect, useRef, useState } from 'react' import { api } from '../utils/api' import FoodSearchModal from './FoodSearchModal' function suggestQuery(raw) { let s = (raw || '').replace(/^\s*[!]?\s*\d+(?:[.,]\d+)?\s*(?:g|kg|ml|l|stück|stk)?\s*/i, '').trim() s = s.replace(/^!+/, '').trim() const comma = s.indexOf(',') if (comma > 2) s = s.slice(0, comma).trim() return s } function RecipePickModal({ recipes, sourceName, onPick, onClose }) { const [q, setQ] = useState(suggestQuery(sourceName)) const filtered = recipes.filter((r) => { const hay = `${r.name_raw || ''} ${r.name_normalized || ''}`.toLowerCase() return !q.trim() || hay.includes(q.trim().toLowerCase()) }) return (
e.stopPropagation()} style={{ width: '100%', maxWidth: 480, maxHeight: 'min(88vh, 560px)', background: 'var(--surface)', borderRadius: 16, boxShadow: '0 8px 32px rgba(0,0,0,0.18)', display: 'flex', flexDirection: 'column', }} >

Eigenes Rezept wählen

setQ(e.target.value)} />
{filtered.length === 0 &&

Kein passendes Rezept. Zuerst Listen-CSV importieren.

} {filtered.map((r) => ( ))}
) } export default function NutritionFoodMap({ onChanged }) { const [unmapped, setUnmapped] = useState([]) const [learned, setLearned] = useState([]) const [recipes, setRecipes] = useState([]) const [error, setError] = useState(null) const [notice, setNotice] = useState(null) const [saving, setSaving] = useState(null) const [searchFor, setSearchFor] = useState(null) const [recipeFor, setRecipeFor] = useState(null) const [importing, setImporting] = useState(false) const listRef = useRef(null) const load = async () => { try { const [u, m, r] = await Promise.all([ api.listUnmappedFoods(), api.listMyFoodMappings(), api.listNutritionRecipes().catch(() => []), ]) setUnmapped(u) setLearned(m) setRecipes(Array.isArray(r) ? r : []) } catch (e) { setError(e.message) } } useEffect(() => { load() }, []) const assign = async (sourceName, foodId) => { setSaving(sourceName) setError(null) try { await api.upsertMyFoodMapping({ source_name: sourceName, food_id: foodId }) setSearchFor(null) await load() onChanged?.() } catch (e) { setError(e.message) } finally { setSaving(null) } } const applyRecipe = async (sourceName, recipeId) => { setSaving(sourceName) setError(null) try { await api.applyNutritionRecipe(recipeId, sourceName) setRecipeFor(null) await load() onChanged?.() } catch (e) { setError(e.message) } finally { setSaving(null) } } const remove = async (id) => { if (!confirm('Zuordnung wirklich löschen?')) return try { await api.deleteMyFoodMapping(id) await load() onChanged?.() } catch (e) { setError(e.message) } } const importLists = async (file) => { if (!file) return setImporting(true) setError(null) setNotice(null) try { const res = await api.importFddbLists(file) await load() onChanged?.() setNotice(`${res.recipes} Listen importiert, ${res.items_linked || 0} Tagebuchzeilen als Rezept verknüpft. Offene Zeilen sind jetzt die Zutaten.`) } catch (e) { setError(e.message) } finally { setImporting(false) } } return (
Lebensmittel zuordnen

Tippe auf „Im Katalog suchen“ — du suchst nach dem Namen, nicht nach einem Code. Eigene FDDB-Listen zuerst importieren, dann wird die Tagebuchzeile in Zutaten aufgelöst.

{error &&
{error}
} {notice &&
{notice}
} { const f = e.target.files?.[0] e.target.value = '' if (f) importLists(f) }} /> {recipes.length > 0 && (

{recipes.length} eigene Listen geladen

)}

Offen ({unmapped.length})

{unmapped.length === 0 &&

Keine offenen Bezeichner.

} {unmapped.map((u) => { const key = `${u.kind || 'diary'}-${u.source_name_normalized}` return (
{u.source_name_raw}
{u.kind === 'recipe_ingredient' ? 'Rezeptzutat' : `${u.count}×`} {u.first_date ? ` · ${u.first_date} – ${u.last_date}` : ''}
{u.kind !== 'recipe_ingredient' && u.matching_recipe_id && ( )} {u.kind !== 'recipe_ingredient' && recipes.length > 0 && ( )}
) })}

Gelernt ({learned.length})

{learned.map((m) => (
{m.source_name_raw}
→ {m.food_name_de}{m.bls_code ? ` · ${m.bls_code}` : ''} {m.catalog_kind !== 'official_bls' ? ' (manuell)' : ''}
))} {searchFor && ( setSearchFor(null)} onSelect={(food) => assign(searchFor.source_name_raw, food.id)} /> )} {recipeFor && ( setRecipeFor(null)} onPick={(id) => applyRecipe(recipeFor.source_name_raw, id)} /> )}
) } export function DayMarkButtons({ date, markType, onChanged }) { const setMark = async (type) => { try { if (markType === type) await api.deleteNutritionDayMark(date) else await api.putNutritionDayMark(date, { mark_type: type }) onChanged?.() } catch (e) { alert(e.message) } } return ( ) }