"use client"; // Pflege der globalen Allergen-/Zusatzstofflisten, die unterhalb // der öffentlichen Speisekarte angezeigt werden. import { useCallback, useEffect, useState } from "react"; import { formatMessage } from "@/lib/i18n/shared"; import { localized } from "@/lib/types"; import type { CodeItem } from "@/lib/types"; import { useDict, useLanguages, useLocale } from "@/components/i18n/LocaleProvider"; export default function CodeListManager({ table, translateEnabled, }: { table: "allergens" | "additives"; translateEnabled: boolean; }) { const dict = useDict(); const locale = useLocale(); const languages = useLanguages().filter((l) => l.code !== "de"); const title = table === "allergens" ? dict.codeList.allergensTitle : dict.codeList.additivesTitle; const codeLabel = table === "allergens" ? dict.codeList.allergenCodeLabel : dict.codeList.additiveCodeLabel; const [items, setItems] = useState([]); const [editing, setEditing] = useState<{ original: string | null; // code vor der Bearbeitung, null = neu code: string; name: string; translations: Record; } | null>(null); const [error, setError] = useState(null); const [notice, setNotice] = useState(null); const [ioBusy, setIoBusy] = useState(false); const [translateState, setTranslateState] = useState<"idle" | "busy" | "done">( "idle" ); const load = useCallback(async () => { const res = await fetch(`/api/${table}`); if (!res.ok) setError(formatMessage(dict.codeList.loadError, { message: String(res.status) })); else setItems(await res.json()); }, [table, dict]); useEffect(() => { load(); }, [load]); async function save(e: React.FormEvent) { e.preventDefault(); if (!editing) return; setError(null); const code = editing.code.trim(); const name = editing.name.trim(); if (!code || !name) return; const payload = { code, name, translations: editing.translations, }; const res = editing.original ? await fetch(`/api/${table}/${editing.original}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }) : await fetch(`/api/${table}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!res.ok) { const data = await res.json().catch(() => ({})); setError( data.error === "duplicate_code" ? formatMessage(dict.codeList.duplicateCode, { code }) : formatMessage(dict.codeList.saveError, { message: data.error ?? String(res.status) }) ); return; } setEditing(null); await load(); } async function remove(item: CodeItem) { if ( !window.confirm( formatMessage(dict.codeList.confirmDelete, { code: item.code, name: item.name }) ) ) return; const res = await fetch(`/api/${table}/${item.code}`, { method: "DELETE" }); if (!res.ok) setError(formatMessage(dict.codeList.deleteError, { message: String(res.status) })); await load(); } async function autoTranslate() { if (!editing || !editing.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, id: editing.original, text: editing.name.trim(), }), }); const data = await res.json(); if (!res.ok) throw new Error(data?.error ?? `Fehler (${res.status})`); setEditing((prev) => prev ? { ...prev, translations: { ...prev.translations, ...(data.translations ?? {}) } } : prev ); setTranslateState("done"); } catch (err) { setError(err instanceof Error ? err.message : String(err)); setTranslateState("idle"); } } async function exportItems() { setError(null); setNotice(null); const res = await fetch(`/api/${table}/export`); if (!res.ok) { setError(formatMessage(dict.codeList.exportError, { message: String(res.status) })); return; } const blob = await res.blob(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = table === "allergens" ? "allergene.csv" : "zusatzstoffe.csv"; a.click(); URL.revokeObjectURL(url); } async function importItems(e: React.ChangeEvent) { const file = e.target.files?.[0]; e.target.value = ""; if (!file) return; setError(null); setNotice(null); setIoBusy(true); const fd = new FormData(); fd.append("file", file); const res = await fetch(`/api/${table}/import`, { method: "POST", body: fd }); setIoBusy(false); if (!res.ok) { setError(formatMessage(dict.codeList.importError, { message: String(res.status) })); return; } const data = (await res.json()) as { created: number; updated: number; skipped: { row: number; reason: string }[]; }; let msg = formatMessage(dict.codeList.importSuccess, { created: data.created, updated: data.updated, }); if (data.skipped.length > 0) { const reasons = data.skipped .map((s) => formatMessage(dict.codeList.importSkippedRow, s)) .join("; "); msg += " " + formatMessage(dict.codeList.importSkipped, { count: data.skipped.length, reasons }); } setNotice(msg); await load(); } return (

{title}

{error && (

{error}

)} {notice &&

{notice}

} {editing && (
setEditing({ ...editing, code: e.target.value })} required />
setEditing({ ...editing, name: e.target.value })} required />
{languages.length > 0 && (
{languages.map((lang) => (
setEditing({ ...editing, translations: { ...editing.translations, [lang.code]: e.target.value }, }) } />
))}
)} {translateEnabled && languages.length > 0 && (
{translateState === "done" && ( {dict.dishManager.translated} )}
)}
)}
    {items.map((item) => (
  • {item.code} –{" "} {localized(item, locale)}
  • ))} {items.length === 0 && (
  • {dict.codeList.empty}
  • )}
); }