Files
Speisekarte/components/PublicMobileMenu.tsx
T
florian.wilde 1db89a54bf Öffentliche Speisekarte: Heute-Button, mobile Tages-/Wochenansicht
- "Heute" springt direkt zur aktuellen Woche
- Unter dem sm-Breakpoint umschaltbar zwischen Tag (Standard) und
  Woche statt der Tabelle; Desktop bleibt vollständig serverseitig
  gerendert, nur der mobile Umschalter ist eine Client-Komponente
2026-07-18 00:46:39 +02:00

170 lines
5.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
// Mobile Ansicht der öffentlichen Speisekarte: Tag (Standard) oder Woche,
// umschaltbar ohne Seiten-Reload. Ab dem "sm"-Breakpoint übernimmt die
// normale Wochentabelle (siehe app/page.tsx), diese Komponente blendet
// sich dort per CSS aus.
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
import {
addDays,
dishCodes,
entriesFor,
formatDateLong,
formatPrice,
mondayOf,
todayISO,
weekdayIndex,
WEEKDAYS,
WEEKDAYS_SHORT,
} from "@/lib/menu";
import type { MenuEntry, Section } from "@/lib/types";
export default function PublicMobileMenu({
monday,
sections,
entries,
}: {
monday: string;
sections: Section[];
entries: MenuEntry[];
}) {
const router = useRouter();
const days = WEEKDAYS.map((weekday, i) => ({
weekday,
short: WEEKDAYS_SHORT[i],
iso: addDays(monday, i),
}));
const isCurrentWeek = monday === mondayOf(todayISO());
const todayIdx = isCurrentWeek ? weekdayIndex(todayISO()) : -1;
const [tab, setTab] = useState<"tag" | "woche">("tag");
const [selectedIdx, setSelectedIdx] = useState(todayIdx >= 0 ? todayIdx : 0);
function goToday() {
const currentMonday = mondayOf(todayISO());
if (currentMonday === monday) {
const idx = weekdayIndex(todayISO());
setSelectedIdx(idx >= 0 ? idx : 0);
setTab("tag");
} else {
router.push(`/?woche=${currentMonday}`);
}
}
function renderDay(day: { weekday: string; iso: string }) {
return (
<div key={day.iso} className="mb-4 last:mb-0">
<h3 className="font-bold text-[var(--c-primary)] mb-2">
{day.weekday} · {formatDateLong(day.iso)}
</h3>
<div className="space-y-3">
{sections.map((s) => {
const items = entriesFor(entries, day.iso, s.key);
return (
<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">{s.label}</span>{" "}
<span className="opacity-80">· {s.time_range}</span>
</div>
<div className="bg-[var(--c-row-bg)] px-3 py-2 text-sm">
{items.length === 0 ? (
<p className="text-gray-500">–</p>
) : (
items.map((e) => {
const codes = dishCodes(e.dish);
return (
<p key={e.id} className="mb-1 last:mb-0">
<span className="font-semibold">{e.dish.name}</span>
{codes && <sup className="dish-codes">{codes}</sup>}{" "}
– {formatPrice(e.dish.price)}
</p>
);
})
)}
</div>
</div>
);
})}
</div>
</div>
);
}
return (
<div className="sm:hidden mb-6">
<div className="flex gap-2 mb-3">
<Link
href={`/?woche=${addDays(monday, -7)}`}
className="btn btn-add flex-1 justify-center"
>
← Woche
</Link>
<button
type="button"
onClick={goToday}
className="btn btn-primary flex-1"
>
Heute
</button>
<Link
href={`/?woche=${addDays(monday, 7)}`}
className="btn btn-add flex-1 justify-center"
>
Woche →
</Link>
</div>
<div className="flex gap-2 mb-3" role="tablist" aria-label="Ansicht">
<button
type="button"
role="tab"
aria-selected={tab === "tag"}
onClick={() => setTab("tag")}
className={`btn flex-1 ${tab === "tag" ? "btn-primary" : "btn-neutral"}`}
>
Tag
</button>
<button
type="button"
role="tab"
aria-selected={tab === "woche"}
onClick={() => setTab("woche")}
className={`btn flex-1 ${tab === "woche" ? "btn-primary" : "btn-neutral"}`}
>
Woche
</button>
</div>
{tab === "tag" ? (
<>
<div className="flex gap-1 mb-3">
{days.map((d, i) => (
<button
key={d.iso}
type="button"
onClick={() => setSelectedIdx(i)}
className={`flex-1 rounded-md py-1.5 text-sm font-semibold border transition-colors ${
i === selectedIdx
? "bg-[var(--c-primary)] text-white border-[var(--c-primary)]"
: "bg-white text-gray-600 border-gray-200"
} ${i === todayIdx ? "ring-2 ring-[var(--c-accent)] ring-offset-1" : ""}`}
>
{d.short}
</button>
))}
</div>
{renderDay(days[selectedIdx])}
</>
) : (
<div>{days.map((d) => renderDay(d))}</div>
)}
</div>
);
}