"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { createClient } from "@/lib/supabase/client"; import { dishCodes, formatPrice } from "@/lib/menu"; import type { CodeItem, Dish } from "@/lib/types"; import ChipPicker from "@/components/ChipPicker"; interface DishForm { id: string | null; // null = neues Gericht name: string; price: string; allergens: string[]; additives: string[]; } const EMPTY_FORM: DishForm = { id: null, name: "", price: "", allergens: [], additives: [], }; export default function DishManager() { const supabase = useMemo(() => createClient(), []); const [dishes, setDishes] = useState([]); const [allergens, setAllergens] = useState([]); const [additives, setAdditives] = useState([]); const [form, setForm] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); const [selected, setSelected] = useState>(new Set()); const [bulkBusy, setBulkBusy] = useState(false); const [search, setSearch] = useState(""); // Live-Suche: filtert die Liste bereits während des Tippens const visible = useMemo(() => { const q = search.trim().toLowerCase(); return q ? dishes.filter((d) => d.name.toLowerCase().includes(q)) : dishes; }, [dishes, search]); const load = useCallback(async () => { const [dishesRes, allergensRes, additivesRes] = await Promise.all([ supabase.from("dishes").select("*").order("name"), supabase.from("allergens").select("*").order("sort"), supabase.from("additives").select("*").order("sort"), ]); const problem = dishesRes.error ?? allergensRes.error ?? additivesRes.error; if (problem) setError(`Laden fehlgeschlagen: ${problem.message}`); else { setDishes(dishesRes.data as Dish[]); setAllergens(allergensRes.data as CodeItem[]); setAdditives(additivesRes.data as CodeItem[]); } setLoading(false); }, [supabase]); useEffect(() => { load(); }, [load]); async function save(e: React.FormEvent) { e.preventDefault(); if (!form) return; setError(null); const price = Number.parseFloat(form.price.replace(",", ".")); if (Number.isNaN(price) || price < 0) { setError("Bitte einen gültigen Preis angeben, z. B. 3,50"); return; } const payload = { name: form.name.trim(), price, allergens: form.allergens, additives: form.additives, }; const { error } = form.id ? await supabase.from("dishes").update(payload).eq("id", form.id) : await supabase.from("dishes").insert(payload); if (error) { setError(`Speichern fehlgeschlagen: ${error.message}`); return; } setForm(null); await load(); } async function remove(dish: Dish) { if ( !window.confirm( `„${dish.name}“ wirklich löschen? Das Gericht wird auch aus allen Wochenplänen entfernt.` ) ) return; const { error } = await supabase.from("dishes").delete().eq("id", dish.id); if (error) setError(`Löschen fehlgeschlagen: ${error.message}`); setSelected((prev) => { const next = new Set(prev); next.delete(dish.id); return next; }); await load(); } function toggleOne(id: string) { setSelected((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); } // "Alle auswählen" bezieht sich auf die aktuell gefilterte Liste function toggleAll() { setSelected((prev) => { const next = new Set(prev); const everyVisibleSelected = visible.every((d) => next.has(d.id)); visible.forEach((d) => everyVisibleSelected ? next.delete(d.id) : next.add(d.id) ); return next; }); } async function removeSelected() { if (selected.size === 0) return; if ( !window.confirm( `${selected.size} Gericht(e) wirklich löschen? Sie werden auch aus allen Wochenplänen entfernt.` ) ) return; setBulkBusy(true); setError(null); const { error } = await supabase .from("dishes") .delete() .in("id", Array.from(selected)); if (error) setError(`Löschen fehlgeschlagen: ${error.message}`); setSelected(new Set()); setBulkBusy(false); await load(); } function toggleAllergen(code: string) { if (!form) return; setForm({ ...form, allergens: form.allergens.includes(code) ? form.allergens.filter((c) => c !== code) : [...form.allergens, code], }); } function toggleAdditive(code: string) { if (!form) return; setForm({ ...form, additives: form.additives.includes(code) ? form.additives.filter((c) => c !== code) : [...form.additives, code], }); } const allSelected = visible.length > 0 && visible.every((d) => selected.has(d.id)); return (

Gerichte

{error && (

{error}

)} {form && (

{form.id ? "Gericht bearbeiten" : "Neues Gericht"}

setForm({ ...form, name: e.target.value })} required />
setForm({ ...form, price: e.target.value })} required />
)} {loading ? (

Lade Gerichte…

) : ( <> {dishes.length > 0 && ( setSearch(e.target.value)} aria-label="Gerichte durchsuchen" /> )} {dishes.length > 0 && (
{selected.size} ausgewählt
)}
    {visible.map((dish) => { const codes = dishCodes(dish); return (
  • toggleOne(dish.id)} aria-label={`${dish.name} auswählen`} /> {dish.name} {codes && {codes}}{" "} – {formatPrice(dish.price)}
  • ); })} {dishes.length === 0 && (
  • Noch keine Gerichte angelegt.
  • )} {dishes.length > 0 && visible.length === 0 && (
  • Keine Treffer für „{search}“.
  • )}
)}
); }