Merge fix/closed-day-icon-and-banner: Schloss-Symbol + durchgehendes Geschlossen-Banner
This commit is contained in:
@@ -4,6 +4,17 @@ Alle nennenswerten Änderungen an diesem Projekt werden hier dokumentiert.
|
||||
Format angelehnt an [Keep a Changelog](https://keepachangelog.com/de/1.1.0/),
|
||||
Versionierung nach [Semantic Versioning](https://semver.org/lang/de/).
|
||||
|
||||
## [1.12.1] – 2026-07-20
|
||||
|
||||
### Geändert
|
||||
- Bedienung geschlossener Tage im Wochenplan überarbeitet: Statt eines
|
||||
Text-Links am Datum markiert jetzt ein Schloss-Symbol (🔓 zum
|
||||
Schließen, 🔒 zeigt „geschlossen" an und öffnet per Klick wieder) den
|
||||
Tag. Ein geschlossener Tag zeigt sowohl im Wochenplan als auch auf
|
||||
der öffentlichen Speisekarte (Desktop-Tabelle) einen einzigen, über
|
||||
alle Essenszeiten-Zeilen durchgehenden „Geschlossen"-Hinweis statt
|
||||
einer Wiederholung in jeder einzelnen Zeile.
|
||||
|
||||
## [1.12.0] – 2026-07-20
|
||||
|
||||
### Hinzugefügt
|
||||
|
||||
@@ -102,6 +102,10 @@ body {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.menu-table tr:nth-child(even) td.cell { background: var(--c-row-bg-alt); }
|
||||
.menu-table td.cell.closed-cell {
|
||||
vertical-align: middle;
|
||||
background: #fee2e2;
|
||||
}
|
||||
.menu-table td.cell .dish { margin-bottom: 0.35rem; }
|
||||
.menu-table td.cell .dish sup {
|
||||
font-size: 0.6rem;
|
||||
|
||||
+27
-23
@@ -160,42 +160,45 @@ export default async function Home({
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="section-head w-56" aria-hidden />
|
||||
{days.map((d) => {
|
||||
const closed = closedDayFor(d.iso);
|
||||
return (
|
||||
{days.map((d) => (
|
||||
<th key={d.iso} className="day-head">
|
||||
<span className="font-bold">{d.weekday}</span>
|
||||
<br />
|
||||
{formatDateLong(d.iso, dict.dateLocale)}
|
||||
{closed && (
|
||||
<div className="mt-1 text-xs font-normal">
|
||||
<span className="rounded-full bg-red-100 text-red-700 px-2 py-0.5 font-semibold">
|
||||
{dict.publicMenu.closedLabel}
|
||||
</span>
|
||||
{localized(closed, locale) && (
|
||||
<div className="mt-0.5 opacity-90">
|
||||
{localized(closed, locale)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</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) => (
|
||||
{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}>
|
||||
<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.publicMenu.closedLabel}</span>
|
||||
{localized(closed, locale) && (
|
||||
<span className="text-sm px-2 text-center">
|
||||
{localized(closed, locale)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<td key={d.iso} className="cell">
|
||||
{closedDayFor(d.iso)
|
||||
? null
|
||||
: entriesFor(entries, d.iso, s.key).map((e) => {
|
||||
{entriesFor(entries, d.iso, s.key).map((e) => {
|
||||
const codes = dishCodes(e.dish);
|
||||
return (
|
||||
<div key={e.id} className="dish">
|
||||
@@ -211,7 +214,8 @@ export default async function Home({
|
||||
);
|
||||
})}
|
||||
</td>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -69,7 +69,8 @@ export default function PublicMobileMenu({
|
||||
{day.weekday} · {formatDateLong(day.iso, dict.dateLocale)}
|
||||
</h3>
|
||||
{closed ? (
|
||||
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">
|
||||
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-3 text-sm text-red-700 text-center">
|
||||
<span aria-hidden>🔒</span>{" "}
|
||||
<span className="font-semibold">{dict.publicMenu.closedLabel}</span>
|
||||
{localized(closed, locale) && <span> – {localized(closed, locale)}</span>}
|
||||
</div>
|
||||
|
||||
+60
-24
@@ -208,35 +208,46 @@ 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) {
|
||||
// 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 (
|
||||
<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)}
|
||||
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.reopenDay}
|
||||
{closed ? "🔒" : "🔓"}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 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-400 hover:text-[var(--c-primary)] underline"
|
||||
className="mt-1 text-xs text-gray-500 hover:text-[var(--c-primary)] underline"
|
||||
onClick={() => startClosing(iso)}
|
||||
>
|
||||
{dict.weekEditor.closeDay}
|
||||
{dict.common.edit}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,9 +494,19 @@ 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>
|
||||
{(() => {
|
||||
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">
|
||||
@@ -500,6 +520,8 @@ export default function WeekEditor({
|
||||
</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) => (
|
||||
{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>
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "canteen-menu",
|
||||
"version": "1.12.0",
|
||||
"version": "1.12.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"predev": "node --env-file-if-exists=.env.local db/migrate.mjs && node --env-file-if-exists=.env.local scripts/bootstrap-admin.mjs",
|
||||
|
||||
Reference in New Issue
Block a user