Phase 4: Terminal im Gerätehaus mit Offline-Betrieb #5
@@ -128,6 +128,15 @@ bedienbar ist und weiterarbeitet, wenn das Netz weg ist.
|
|||||||
Offline-E2E-Test, der genau darauf wartet.
|
Offline-E2E-Test, der genau darauf wartet.
|
||||||
- `/kiosk/einrichten` ließ sich nicht vorab erzeugen, weil `useSearchParams()`
|
- `/kiosk/einrichten` ließ sich nicht vorab erzeugen, weil `useSearchParams()`
|
||||||
ohne Suspense-Grenze stand. Der Produktionsbuild brach dabei ab.
|
ohne Suspense-Grenze stand. Der Produktionsbuild brach dabei ab.
|
||||||
|
- Der Anwesenheits- und der Fahrten-Wizard zeigten erst die eine, nach dem
|
||||||
|
Laden des eigenen Stands die andere Fassung — wer zügig tippte, drückte ins
|
||||||
|
Leere. Bis der Stand bekannt ist, steht dort jetzt ein Platzhalter.
|
||||||
|
- Die Offline-Seite blieb stehen, wenn nur eine einzelne Anfrage abgebrochen
|
||||||
|
war. Am Hallenmonitor, den niemand bedient, hieße das: bis jemand vorbeikommt.
|
||||||
|
Sie versucht es jetzt alle zehn Sekunden von selbst.
|
||||||
|
- Der Hallenmonitor stapelte alle laufenden Einsätze übereinander und schob
|
||||||
|
damit Anwesenheit, Fahrzeuge und Prüfungen aus dem Bild. Zwei stehen groß,
|
||||||
|
der Rest als Zeile.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -13,7 +13,17 @@ export default defineConfig({
|
|||||||
testDir: "./tests/e2e",
|
testDir: "./tests/e2e",
|
||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
forbidOnly: Boolean(process.env.CI),
|
forbidOnly: Boolean(process.env.CI),
|
||||||
retries: process.env.CI ? 2 : 0,
|
/**
|
||||||
|
* Ein Wiederholungsversuch auch lokal.
|
||||||
|
*
|
||||||
|
* Der Grund ist derselbe wie bei den großzügigen Zeitlimits unten: "next
|
||||||
|
* dev" übersetzt jede Route beim ersten Aufruf, und mit vier gleichzeitigen
|
||||||
|
* Arbeitern kommt dazu die Anmeldung mit bcrypt (12 Runden, wie in
|
||||||
|
* Produktion). Unter dieser Last läuft gelegentlich ein Test ins Zeitlimit,
|
||||||
|
* der einzeln zuverlässig grün ist — verstreut über alle Dateien, nicht an
|
||||||
|
* einer Stelle. Ein echter Fehler fällt zweimal durch und bleibt sichtbar.
|
||||||
|
*/
|
||||||
|
retries: process.env.CI ? 2 : 1,
|
||||||
/**
|
/**
|
||||||
* Bewusst wenige Worker: "next dev" übersetzt jede Route erst beim ersten
|
* Bewusst wenige Worker: "next dev" übersetzt jede Route erst beim ersten
|
||||||
* Aufruf. Zu viele gleichzeitige Erstaufrufe lassen den Server so lange
|
* Aufruf. Zu viele gleichzeitige Erstaufrufe lassen den Server so lange
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { CloudOff } from "lucide-react";
|
import { CloudOff } from "lucide-react";
|
||||||
|
import { ErneutVersuchen } from "@/app/kiosk/offline/wiederholen";
|
||||||
|
|
||||||
export const metadata = { title: "Offline" };
|
export const metadata = { title: "Offline" };
|
||||||
|
|
||||||
@@ -9,6 +10,9 @@ export const metadata = { title: "Offline" };
|
|||||||
* Sie ist bewusst statisch und ohne Datenzugriff — sie muss auch dann noch
|
* Sie ist bewusst statisch und ohne Datenzugriff — sie muss auch dann noch
|
||||||
* erscheinen, wenn vom Server nichts mehr kommt. Und sie sagt das Wichtigste:
|
* erscheinen, wenn vom Server nichts mehr kommt. Und sie sagt das Wichtigste:
|
||||||
* das Erfasste ist nicht weg.
|
* das Erfasste ist nicht weg.
|
||||||
|
*
|
||||||
|
* Zurück findet sie von selbst: ein einzelner abgebrochener Aufruf genügt, um
|
||||||
|
* hier zu landen, und dann darf das Terminal nicht warten, bis jemand tippt.
|
||||||
*/
|
*/
|
||||||
export default function OfflineSeite() {
|
export default function OfflineSeite() {
|
||||||
return (
|
return (
|
||||||
@@ -23,6 +27,8 @@ export default function OfflineSeite() {
|
|||||||
verloren.
|
verloren.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<ErneutVersuchen />
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
href="/kiosk"
|
href="/kiosk"
|
||||||
className="flex h-16 items-center rounded-md border-2 border-[var(--border)] px-8 text-lg"
|
className="flex h-16 items-center rounded-md border-2 border-[var(--border)] px-8 text-lg"
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Es allein noch einmal versuchen.
|
||||||
|
*
|
||||||
|
* Die Offline-Seite erscheint auch dann, wenn nur eine einzelne Anfrage
|
||||||
|
* unterwegs abgebrochen ist — ein kurzer Aussetzer des Repeaters genügt.
|
||||||
|
* Ohne diesen Baustein bliebe das Terminal danach auf dieser Seite stehen,
|
||||||
|
* bis jemand vorbeikommt und tippt. Am Hallenmonitor, den niemand bedient,
|
||||||
|
* hieße das: bis zum nächsten Dienstabend.
|
||||||
|
*
|
||||||
|
* Also versucht die Seite es selbst — sofort, wenn der Browser wieder Netz
|
||||||
|
* meldet, und sonst alle paar Sekunden.
|
||||||
|
*/
|
||||||
|
const ABSTAND_MS = 10_000;
|
||||||
|
|
||||||
|
export function ErneutVersuchen() {
|
||||||
|
const [versuche, setVersuche] = React.useState(0);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const nochmal = () => {
|
||||||
|
setVersuche((wert) => wert + 1);
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("online", nochmal);
|
||||||
|
const zeitgeber = setInterval(() => {
|
||||||
|
// Ohne Netz spart der Versuch nichts — dann wartet die Seite auf das
|
||||||
|
// "online"-Ereignis darüber.
|
||||||
|
if (navigator.onLine) nochmal();
|
||||||
|
}, ABSTAND_MS);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("online", nochmal);
|
||||||
|
clearInterval(zeitgeber);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p className="text-[var(--muted-foreground)]" role="status" data-testid="offline-wiederholung">
|
||||||
|
Das Terminal versucht es alle {ABSTAND_MS / 1000} Sekunden von selbst
|
||||||
|
{versuche > 0 ? ` (${versuche}. Versuch)` : ""}.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user