"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { createClient } from "@/lib/supabase/client"; import { dishCodes, formatPrice } from "@/lib/menu"; import { formatMessage } from "@/lib/i18n/shared"; import { localized } from "@/lib/types"; import type { CodeItem, Dish } from "@/lib/types"; import ChipPicker from "@/components/ChipPicker"; import { useDict, useLocale } from "@/components/i18n/LocaleProvider"; interface DishForm { id: string | null; // null = neues Gericht name: string; nameEn: string; namePl: string; price: string; allergens: string[]; additives: string[]; } const EMPTY_FORM: DishForm = { id: null, name: "", nameEn: "", namePl: "", price: "", allergens: [], additives: [], }; export default function DishManager() { const supabase = useMemo(() => createClient(), []); const dict = useDict(); const locale = useLocale(); 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(""); const [translateState, setTranslateState] = useState< "idle" | "busy" | "done" | "unavailable" >("idle"); // 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(formatMessage(dict.dishManager.loadError, { message: problem.message })); else { setDishes(dishesRes.data as Dish[]); setAllergens(allergensRes.data as CodeItem[]); setAdditives(additivesRes.data as CodeItem[]); } setLoading(false); }, [supabase, dict]); 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(dict.dishManager.invalidPrice); return; } const payload = { name: form.name.trim(), name_en: form.nameEn.trim() || null, name_pl: form.namePl.trim() || null, 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(formatMessage(dict.dishManager.saveError, { message: error.message })); return; } setForm(null); await load(); } async function remove(dish: Dish) { if ( !window.confirm( formatMessage(dict.dishManager.confirmDelete, { name: dish.name }) ) ) return; const { error } = await supabase.from("dishes").delete().eq("id", dish.id); if (error) setError(formatMessage(dict.dishManager.deleteError, { message: 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( formatMessage(dict.dishManager.confirmDeleteSelected, { count: selected.size, }) ) ) return; setBulkBusy(true); setError(null); const { error } = await supabase .from("dishes") .delete() .in("id", Array.from(selected)); if (error) setError(formatMessage(dict.dishManager.deleteError, { message: 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], }); } async function autoTranslate() { if (!form || !form.name.trim()) return; setTranslateState("busy"); setError(null); try { const res = await fetch("/api/translate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ table: "dishes", id: form.id, text: form.name.trim(), }), }); const data = await res.json(); if (!res.ok) throw new Error(data?.error ?? `Fehler (${res.status})`); if (data.configured === false) { setTranslateState("unavailable"); return; } setForm((prev) => prev ? { ...prev, nameEn: data.en ?? prev.nameEn, namePl: data.pl ?? prev.namePl } : prev ); setTranslateState("done"); } catch (err) { setError(err instanceof Error ? err.message : String(err)); setTranslateState("idle"); } } const allSelected = visible.length > 0 && visible.every((d) => selected.has(d.id)); return (

{dict.dishManager.title}

{error && (

{error}

)} {form && (

{form.id ? dict.dishManager.editDish : dict.dishManager.newDish}

setForm({ ...form, name: e.target.value })} required />
setForm({ ...form, price: e.target.value })} required />
setForm({ ...form, nameEn: e.target.value })} />
setForm({ ...form, namePl: e.target.value })} />
{translateState === "done" && ( {dict.dishManager.translated} )} {translateState === "unavailable" && ( {dict.dishManager.translateUnavailable} )}
)} {loading ? (

{dict.common.loading}

) : ( <> {dishes.length > 0 && ( setSearch(e.target.value)} aria-label={dict.dishManager.searchPlaceholder} /> )} {dishes.length > 0 && (
{formatMessage(dict.dishManager.selectedCount, { count: selected.size })}
)}
    {visible.map((dish) => { const codes = dishCodes(dish); return (
  • toggleOne(dish.id)} aria-label={dish.name} /> {localized(dish, locale)} {codes && {codes}}{" "} – {formatPrice(dish.price)}
  • ); })} {dishes.length === 0 && (
  • {dict.dishManager.empty}
  • )} {dishes.length > 0 && visible.length === 0 && (
  • {formatMessage(dict.dishManager.noResults, { query: search })}
  • )}
)}
); }