fix: Schloss-Symbol statt Text + durchgehendes Geschlossen-Banner

Ersetzt den "Tag schließen"-Textlink durch ein Schloss-Symbol
(🔓/🔒) und lässt den "Geschlossen"-Hinweis eines geschlossenen
Tages über alle Essenszeiten-Zeilen durchlaufen (per rowSpan), statt
ihn in jeder Zeile einzeln zu wiederholen – sowohl im Wochenplan als
auch auf der öffentlichen Speisekarte.
This commit is contained in:
2026-07-20 12:49:04 +02:00
parent e9d4b51fce
commit 63870ba2e6
6 changed files with 149 additions and 93 deletions
+83 -47
View File
@@ -208,38 +208,49 @@ export default function WeekEditor({
? dishes.filter((d) => localized(d, locale).toLowerCase().includes(query))
: dishes;
// Kleine Anzeige/Umschaltung unter dem Tagesdatum: "Geschlossen" +
// Wieder-öffnen-Button, sonst ein dezenter "Tag schließen"-Link.
function renderClosureBadge(iso: string) {
const closed = closedDayFor(iso);
if (closed) {
return (
<div className="mt-1 flex flex-wrap items-center justify-center gap-1 text-xs">
<span className="rounded-full bg-red-100 text-red-700 px-2 py-0.5 font-semibold">
{dict.weekEditor.closedLabel}
</span>
<button
type="button"
className="text-gray-400 hover:text-[var(--c-primary)] underline"
onClick={() => reopenDay(iso)}
disabled={closeBusy}
>
{dict.weekEditor.reopenDay}
</button>
</div>
);
}
// Kleines Schloss-Symbol neben dem Tagesdatum: 🔓 zum Schließen
// anklicken, 🔒 zeigt einen geschlossenen Tag und öffnet ihn per Klick
// wieder – der Zustand des Symbols selbst ist die Beschriftung.
function renderClosureToggle(iso: string) {
const closed = Boolean(closedDayFor(iso));
return (
<button
type="button"
className="mt-1 text-xs text-gray-400 hover:text-[var(--c-primary)] underline"
onClick={() => startClosing(iso)}
className="mt-0.5 text-base leading-none align-middle opacity-70 hover:opacity-100 disabled:opacity-40"
onClick={() => (closed ? reopenDay(iso) : startClosing(iso))}
disabled={closeBusy}
title={closed ? dict.weekEditor.reopenDay : dict.weekEditor.closeDay}
aria-label={closed ? dict.weekEditor.reopenDay : dict.weekEditor.closeDay}
>
{dict.weekEditor.closeDay}
{closed ? "🔒" : "🔓"}
</button>
);
}
// Banner, das bei einem geschlossenen Tag alle Essenszeiten-Zellen
// dieses Tages ersetzt (Desktop: eine über rowSpan verbundene Zelle,
// Mobil: anstelle der Essenszeiten-Karten) – "Bearbeiten" öffnet das
// Formular erneut, um nur den Grund anzupassen, ohne den Tag über das
// Symbol im Header erst wieder öffnen zu müssen.
function renderClosedBanner(iso: string, closed: ClosedDay) {
return (
<div className="flex flex-col items-center justify-center gap-1 py-4 text-red-700">
<span className="text-2xl" aria-hidden>
🔒
</span>
<span className="font-semibold">{dict.weekEditor.closedLabel}</span>
{closed.reason && <span className="text-sm px-2 text-center">{closed.reason}</span>}
<button
type="button"
className="mt-1 text-xs text-gray-500 hover:text-[var(--c-primary)] underline"
onClick={() => startClosing(iso)}
>
{dict.common.edit}
</button>
</div>
);
}
// Formular zum Schließen/Bearbeiten eines Tages – ein gemeinsames
// Formular für Desktop-Tabelle und mobile Tagesansicht, da immer nur
// ein Tag gleichzeitig bearbeitet werden kann.
@@ -330,7 +341,6 @@ export default function WeekEditor({
// unabhängig davon, welche Ansicht gerade rendert.
function renderCell(iso: string, sectionKey: string) {
const isAdding = adding?.iso === iso && adding?.section === sectionKey;
const closed = Boolean(closedDayFor(iso));
return (
<>
{entriesFor(entries, iso, sectionKey).map((e) => {
@@ -353,7 +363,7 @@ export default function WeekEditor({
</div>
);
})}
{closed ? null : isAdding ? (
{isAdding ? (
<div className="mt-2 space-y-1 text-left">
{selectedDishObj ? (
<div className="flex items-center gap-1 text-sm">
@@ -484,22 +494,34 @@ export default function WeekEditor({
))}
</div>
<h2 className="font-bold text-[var(--c-primary)] mb-1">
{selectedDay.weekday} · {formatDateLong(selectedDay.iso, dict.dateLocale)}
{selectedDay.weekday} · {formatDateLong(selectedDay.iso, dict.dateLocale)}{" "}
{renderClosureToggle(selectedDay.iso)}
</h2>
<div className="mb-2">{renderClosureBadge(selectedDay.iso)}</div>
<div className="space-y-3">
{sections.map((s) => (
<div key={s.key} className="rounded-md border border-gray-200 overflow-hidden">
<div className="bg-[var(--c-primary)] text-[var(--c-header-text)] px-3 py-1.5 text-sm">
<span className="font-semibold">{localized(s, locale)}</span>{" "}
<span className="opacity-80">· {s.time_range}</span>
</div>
<div className="bg-[var(--c-row-bg)] px-3 py-2 text-sm text-center">
{renderCell(selectedDay.iso, s.key)}
{(() => {
const closed = closedDayFor(selectedDay.iso);
if (closed) {
return (
<div className="rounded-md border border-red-200 bg-red-50">
{renderClosedBanner(selectedDay.iso, closed)}
</div>
);
}
return (
<div className="space-y-3">
{sections.map((s) => (
<div key={s.key} className="rounded-md border border-gray-200 overflow-hidden">
<div className="bg-[var(--c-primary)] text-[var(--c-header-text)] px-3 py-1.5 text-sm">
<span className="font-semibold">{localized(s, locale)}</span>{" "}
<span className="opacity-80">· {s.time_range}</span>
</div>
<div className="bg-[var(--c-row-bg)] px-3 py-2 text-sm text-center">
{renderCell(selectedDay.iso, s.key)}
</div>
</div>
))}
</div>
))}
</div>
);
})()}
</div>
{/* Ab "sm": klassische Wochentabelle */}
@@ -512,24 +534,38 @@ export default function WeekEditor({
<th key={d.iso} className="day-head">
<span className="font-bold">{d.weekday}</span>
<br />
{formatDateLong(d.iso, dict.dateLocale)}
{renderClosureBadge(d.iso)}
{formatDateLong(d.iso, dict.dateLocale)} {renderClosureToggle(d.iso)}
</th>
))}
</tr>
</thead>
<tbody>
{sections.map((s) => (
{sections.map((s, sIdx) => (
<tr key={s.key}>
<th className="section-head">
<div>{localized(s, locale)}</div>
<div className="time">{s.time_range}</div>
</th>
{days.map((d) => (
<td key={d.iso} className="cell">
{renderCell(d.iso, s.key)}
</td>
))}
{days.map((d) => {
const closed = closedDayFor(d.iso);
if (closed) {
if (sIdx > 0) return null;
return (
<td
key={d.iso}
className="cell closed-cell"
rowSpan={sections.length}
>
{renderClosedBanner(d.iso, closed)}
</td>
);
}
return (
<td key={d.iso} className="cell">
{renderCell(d.iso, s.key)}
</td>
);
})}
</tr>
))}
</tbody>