Files
Speisekarte/components/LabelingNoticeEditor.tsx
florian.wildeandClaude Sonnet 5 de8914f9cc feat: Umschaltbarer Hinweismodus für Allergene/Zusatzstoffe
Neue Migration 0002_labeling_notice.sql: settings.labeling_mode
("detailed"/"notice") + labeling_notice(_en/_pl). Auf der Seite
"Allergene & Zusatzstoffe" (LabelingNoticeEditor) kann zwischen der
detaillierten Auswahl je Gericht und einem editierbaren Hinweistext
umgeschaltet werden. Im Hinweismodus entfällt auf der öffentlichen
Speisekarte (Desktop + Mobile) die Legende und die Kürzel an den
Gerichten, in der Gerichte-Verwaltung wird die Chip-Auswahl beim
Bearbeiten ausgeblendet. Umschalten/Bearbeiten ist für staff und admin
freigegeben, Farben in /api/settings bleiben admin-only.

Version 1.8.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 23:57:43 +02:00

143 lines
4.4 KiB
TypeScript

"use client";
// Umschaltung zwischen der detaillierten Allergen-/Zusatzstoff-Auswahl
// je Gericht und einem einfachen, editierbaren Hinweistext.
import { useCallback, useEffect, useState } from "react";
import { formatMessage } from "@/lib/i18n/shared";
import { useDict } from "@/components/i18n/LocaleProvider";
import type { LabelingMode } from "@/lib/db/settings";
export default function LabelingNoticeEditor() {
const dict = useDict();
const [mode, setMode] = useState<LabelingMode>("detailed");
const [noticeDe, setNoticeDe] = useState("");
const [noticeEn, setNoticeEn] = useState("");
const [noticePl, setNoticePl] = useState("");
const [loading, setLoading] = useState(true);
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const [info, setInfo] = useState<string | null>(null);
const load = useCallback(async () => {
const res = await fetch("/api/settings");
if (!res.ok) {
setError(formatMessage(dict.labelingNotice.loadError, { message: String(res.status) }));
setLoading(false);
return;
}
const data = await res.json();
setMode(data.labeling_mode ?? "detailed");
setNoticeDe(data.labeling_notice ?? "");
setNoticeEn(data.labeling_notice_en ?? "");
setNoticePl(data.labeling_notice_pl ?? "");
setLoading(false);
}, [dict]);
useEffect(() => {
load();
}, [load]);
async function save() {
setBusy(true);
setError(null);
setInfo(null);
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
labeling_mode: mode,
labeling_notice: noticeDe.trim(),
labeling_notice_en: noticeEn.trim() || null,
labeling_notice_pl: noticePl.trim() || null,
}),
});
setBusy(false);
if (!res.ok) {
setError(formatMessage(dict.labelingNotice.saveError, { message: String(res.status) }));
return;
}
setInfo(dict.labelingNotice.saved);
}
if (loading) return null;
return (
<section className="card">
<h2 className="font-semibold mb-2">{dict.labelingNotice.title}</h2>
<p className="text-sm text-gray-600 mb-3">{dict.labelingNotice.hint}</p>
{error && (
<p className="mb-3 text-sm text-[var(--c-del)]" role="alert">
{error}
</p>
)}
{info && <p className="mb-3 text-sm text-green-700">{info}</p>}
<div className="flex flex-col gap-2 mb-4">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="labeling-mode"
checked={mode === "detailed"}
onChange={() => setMode("detailed")}
/>
{dict.labelingNotice.modeDetailed}
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="labeling-mode"
checked={mode === "notice"}
onChange={() => setMode("notice")}
/>
{dict.labelingNotice.modeNotice}
</label>
</div>
<div className="space-y-3 mb-4">
<div>
<label className="label" htmlFor="labeling-notice-de">
{dict.labelingNotice.noticeDeLabel}
</label>
<textarea
id="labeling-notice-de"
className="input"
rows={2}
value={noticeDe}
onChange={(e) => setNoticeDe(e.target.value)}
/>
</div>
<div>
<label className="label" htmlFor="labeling-notice-en">
{dict.labelingNotice.noticeEnLabel}
</label>
<textarea
id="labeling-notice-en"
className="input"
rows={2}
value={noticeEn}
onChange={(e) => setNoticeEn(e.target.value)}
/>
</div>
<div>
<label className="label" htmlFor="labeling-notice-pl">
{dict.labelingNotice.noticePlLabel}
</label>
<textarea
id="labeling-notice-pl"
className="input"
rows={2}
value={noticePl}
onChange={(e) => setNoticePl(e.target.value)}
/>
</div>
</div>
<button type="button" className="btn btn-add" onClick={save} disabled={busy}>
{dict.labelingNotice.save}
</button>
</section>
);
}