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>
This commit is contained in:
2026-07-19 23:57:43 +02:00
co-authored by Claude Sonnet 5
parent 96eaf3989e
commit de8914f9cc
12 changed files with 376 additions and 56 deletions
+25 -13
View File
@@ -46,6 +46,7 @@ export default function DishManager({
const [selected, setSelected] = useState<Set<string>>(new Set());
const [bulkBusy, setBulkBusy] = useState(false);
const [search, setSearch] = useState("");
const [labelingDetailed, setLabelingDetailed] = useState(true);
const [translateState, setTranslateState] = useState<"idle" | "busy" | "done">(
"idle"
);
@@ -57,10 +58,11 @@ export default function DishManager({
}, [dishes, search]);
const load = useCallback(async () => {
const [dishesRes, allergensRes, additivesRes] = await Promise.all([
const [dishesRes, allergensRes, additivesRes, settingsRes] = await Promise.all([
fetch("/api/dishes"),
fetch("/api/allergens"),
fetch("/api/additives"),
fetch("/api/settings"),
]);
if (!dishesRes.ok || !allergensRes.ok || !additivesRes.ok) {
setError(formatMessage(dict.dishManager.loadError, { message: "load failed" }));
@@ -69,6 +71,10 @@ export default function DishManager({
setAllergens(await allergensRes.json());
setAdditives(await additivesRes.json());
}
if (settingsRes.ok) {
const settings = await settingsRes.json();
setLabelingDetailed((settings?.labeling_mode ?? "detailed") !== "notice");
}
setLoading(false);
}, [dict]);
@@ -390,18 +396,24 @@ export default function DishManager({
</div>
)}
<ChipPicker
label={dict.codeList.allergensTitle}
items={allergens}
selected={form.allergens}
onToggle={toggleAllergen}
/>
<ChipPicker
label={dict.codeList.additivesTitle}
items={additives}
selected={form.additives}
onToggle={toggleAdditive}
/>
{labelingDetailed ? (
<>
<ChipPicker
label={dict.codeList.allergensTitle}
items={allergens}
selected={form.allergens}
onToggle={toggleAllergen}
/>
<ChipPicker
label={dict.codeList.additivesTitle}
items={additives}
selected={form.additives}
onToggle={toggleAdditive}
/>
</>
) : (
<p className="text-sm text-gray-500">{dict.dishManager.labelingDisabledHint}</p>
)}
<div className="flex gap-2">
<button type="submit" className="btn btn-add">
{dict.common.save}