fix(ui): Seitenmenue auf dem Handy schliessbar machen (#034)

Das Markup stammt von AdminLTE, dessen JavaScript wir bewusst nicht
einbinden. Die Klasse sidebar-open liess sich damit setzen, aber nichts nahm
sie wieder weg: keine Flaeche zum Antippen, kein Schliessen beim Navigieren,
kein Escape. Auf dem Telefon blieb das Menue ueber dem Inhalt stehen.

Jetzt gibt es einen eigenen Zustand, auf den Kopfzeile und Menue gemeinsam
hoeren, eine abdunkelnde Flaeche dahinter, einen Schliessen-Knopf im Menuekopf
und das Schliessen beim Navigieren. Der Inhalt dahinter scrollt nicht mehr mit.

Ausserdem zeigt die Dashboard-Tabelle auf dem Telefon nur Host und Ablauf, den
Kunden klein darunter - vorher war rechts die Haelfte abgeschnitten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-03 15:55:59 +02:00
co-authored by Claude Opus 5
parent 4bcf3daedd
commit 09bb98235b
34 changed files with 286 additions and 25 deletions
+66
View File
@@ -0,0 +1,66 @@
"use client";
import { useSyncExternalStore } from "react";
/**
* Zustand des Seitenmenüs auf schmalen Bildschirmen (#034).
*
* AdminLTE bringt dafür eigenes JavaScript mit, das wir bewusst nicht einbinden – die Klasse
* `sidebar-open` am `body` allein lässt sich aber nicht wieder loswerden: Kein Hintergrund zum
* Antippen, kein Schließen beim Navigieren. Deshalb ein eigener, winziger Zustand, auf den
* Kopfzeile und Menü gemeinsam hören.
*/
const SCHMAL = 992;
let offen = false;
const zuhoerer = new Set<() => void>();
function melden() {
for (const ruf of zuhoerer) ruf();
}
/** Setzt die Klassen, die das AdminLTE-Stylesheet auswertet. */
function klassenSetzen() {
const body = document.body;
body.classList.toggle("sidebar-open", offen);
// Auf schmalen Bildschirmen liegt das Menü über dem Inhalt; dahinter soll nichts mitscrollen.
body.style.overflow = offen && window.innerWidth < SCHMAL ? "hidden" : "";
}
export function menuUmschalten() {
if (window.innerWidth >= SCHMAL) {
// Breiter Bildschirm: Das Menü bleibt stehen und wird nur schmaler.
document.body.classList.toggle("sidebar-collapse");
document.body.classList.remove("sidebar-open");
return;
}
offen = !offen;
document.body.classList.remove("sidebar-collapse");
klassenSetzen();
melden();
}
export function menuSchliessen() {
if (!offen) return;
offen = false;
klassenSetzen();
melden();
}
function abonnieren(ruf: () => void) {
zuhoerer.add(ruf);
return () => {
zuhoerer.delete(ruf);
};
}
export function useMenuOffen(): boolean {
// Auf dem Server ist das Menü nie offen – sonst käme es beim ersten Rendern zu einem Unterschied.
return useSyncExternalStore(
abonnieren,
() => offen,
() => false,
);
}