"use client"; import * as React from "react"; import Link from "next/link"; import { AlertTriangle, CloudRain, Flame, Truck, Users } from "lucide-react"; import { trpc } from "@/lib/trpc/client"; import { WARNSTUFE_TEXT } from "@/lib/integrations/wetter-texte"; import { alsDatum, alsUhrzeit } from "@/lib/utils"; /** * Hallenmonitor. * * Hängt an der Wand und wird im Vorbeigehen gelesen — aus drei, vier Metern * Entfernung. Daraus folgt alles Weitere: * * * **Große Schrift, wenige Zahlen.** Wer stehen bleiben muss, um es zu * lesen, liest es nicht. * * **Keine Bedienung.** Kein Knopf, kein Menü, keine Abmeldeuhr. * * **Was dringend ist, steht oben.** Ein laufender Einsatz verdrängt alles * andere; danach kommen Warnungen, dann der Alltag. * * Die Daten kommen im Minutentakt nach. Fällt das Netz aus, bleibt der letzte * Stand stehen — mit sichtbarer Uhrzeit, damit niemand einem alten Bild traut. */ const AKTUALISIERUNG_MS = 60_000; /** Wie viele laufende Einsätze groß angezeigt werden, bevor der Rest zur Zeile wird. */ const GROSS_ANGEZEIGTE_EINSAETZE = 2; export function Hallenmonitor() { const [uhrzeit, setUhrzeit] = React.useState(new Date()); const daten = trpc.kiosk.monitor.useQuery(undefined, { refetchInterval: AKTUALISIERUNG_MS, retry: false, }); const wetter = trpc.kiosk.wetterwarnungen.useQuery(undefined, { refetchInterval: 5 * AKTUALISIERUNG_MS, retry: false, }); React.useEffect(() => { const zeitgeber = setInterval(() => setUhrzeit(new Date()), 1000); return () => clearInterval(zeitgeber); }, []); const stand = daten.data; const warnungen = wetter.data?.warnungen ?? []; const hoechsteWarnstufe = warnungen.reduce((hoechste, warnung) => Math.max(hoechste, warnung.stufe), 0); return (
{/* Kopfzeile: Uhr und Stand */}
Gerätehaus
{alsUhrzeit(uhrzeit)}
{alsDatum(uhrzeit)}
{/* Laufender Einsatz verdrängt alles — aber höchstens zwei. Mehr als zwei gleichzeitig laufende Einsätze gibt es selten, und wenn, dann ist die Halle ohnehin leer. Ungebremst schöbe die Liste alles Übrige aus dem Bild, und genau das darf auf einer Wandanzeige nicht passieren: was nicht sichtbar ist, hilft niemandem. Der Rest steht als Zeile darunter. */} {stand?.laufendeEinsaetze.slice(0, GROSS_ANGEZEIGTE_EINSAETZE).map((einsatz) => (

Einsatz läuft

{einsatz.stichwort}

{einsatz.ort ?? "ohne Ortsangabe"} · alarmiert{" "} {einsatz.alarmiertUm ? alsUhrzeit(einsatz.alarmiertUm) : "—"} ·{" "} {einsatz.einsatznummer}

))} {(stand?.laufendeEinsaetze.length ?? 0) > GROSS_ANGEZEIGTE_EINSAETZE && (

{(stand?.laufendeEinsaetze.length ?? 0) - GROSS_ANGEZEIGTE_EINSAETZE} weitere Einsätze laufen:{" "} {stand?.laufendeEinsaetze .slice(GROSS_ANGEZEIGTE_EINSAETZE) .map((einsatz) => `${einsatz.einsatznummer} ${einsatz.stichwort}`) .join(" · ")}

)} {/* Wetterwarnung */} {hoechsteWarnstufe >= 2 && (

{WARNSTUFE_TEXT[hoechsteWarnstufe] ?? "Wetterwarnung"} des DWD

{warnungen .filter((warnung) => warnung.stufe === hoechsteWarnstufe) .slice(0, 2) .map((warnung) => (

{warnung.ueberschrift || warnung.ereignis} {warnung.bis && ( {" "} bis {alsUhrzeit(warnung.bis)} )}

))}
)}
{/* Anwesend */}

Im Haus

{stand?.anwesend.length ?? 0}

    {stand?.anwesend.slice(0, 10).map((eintrag) => (
  • {eintrag.person.nachname}, {eintrag.person.vorname} seit {alsUhrzeit(eintrag.angemeldetUm)}
  • ))}
{/* Fahrzeuge */}

Fahrzeuge

    {stand?.fahrzeuge.map((fahrzeug) => (
  • {fahrzeug.funkrufname} {fahrzeug.unterwegs ? "unterwegs" : "im Haus"}
  • ))}
{stand && stand.unterwegs.length > 0 && (
    {stand.unterwegs.map((fahrt) => (
  • {fahrt.fahrzeug.funkrufname}: {fahrt.fahrer.nachname} seit{" "} {alsUhrzeit(fahrt.abfahrtUm)} {fahrt.ziel ? ` → ${fahrt.ziel}` : ""}
  • ))}
)}
{/* Prüfungen und Termine */}

Prüfungen

0 ? "text-[var(--gesperrt)]" : "" }`} > {stand?.ueberfaelligePruefungen ?? 0}

überfällig

{stand?.gesperrteGeraete ?? 0}

gesperrt

    {stand?.naechsteTermine.slice(0, 5).map((termin) => (
  • {alsDatum(termin.faelligAm)}{" "} {termin.geraet.inventarnummer} — {termin.vorlage?.name ?? "Prüfung"}
  • ))}
{/* Übungen */} {stand && stand.uebungen.length > 0 && (

Diese Woche

    {stand.uebungen.map((uebung) => (
  • {alsDatum(uebung.datum)} {uebung.beginnUm && ( {alsUhrzeit(uebung.beginnUm)} )}{" "} {uebung.thema} {uebung.ort && ( · {uebung.ort} )}
  • ))}
)}
); }