baustelle-pwa/lib/router.js
Eddy 64ebbf7850 Rauswurf aus dem Auftrag behoben, wartende Fotos am Auftrag sichtbar
Gemeldet 18.09.2026: nach "Fertig" in der Kamera stand die App auf der
Auftragsliste, und im Auftrag war von den Fotos nichts zu sehen.

Ursache Rauswurf (Prod-Log 17.09., Auftrag 111): closeModal() ruft
history.back() (asynchron), direkt danach setzt router.go() den Hash.
Der Ruecksprung laeuft erst nach dem Hash-Wechsel und springt hinter
den neuen Eintrag zurueck - angezeigt wird der Auftrag, in der Adresse
steht '#/orders'. Das naechste router.navigate() (Kamera "Fertig")
zeichnet dann die Liste. Betraf nur in derselben Sitzung ueber den
Plus-Knopf angelegte Auftraege.

- closeModal(): Zaehler _pendingBacks statt Boolean (KB #1209), History-
  Eintrag wird vor dem Cleanup zurueckgenommen
- router.go() und pushModal() warten ueber historySettled() auf
  ausstehende Ruecksprunge (Schutzzeit 1,5 s)
- Kamera und Galerie-Auswahl steuern nach dem Sichern ausdruecklich
  '#/orders/<id>' an statt den Hash der Adresszeile
- Auftragsseite: Block "Warten auf Upload (n)" mit Vorschaubildern aus
  der Warteschlange und Klartext; zieht nach photo-uploaded selbst nach
- Hinweis-Toast beim Schliessen der Kamera, wenn noch Fotos warten

Verifiziert lokal in Chromium mit echtem Offline-Modus; die Kamera
selbst (getUserMedia) am Handy ist noch nicht getestet. Kein Deploy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 23:36:14 +02:00

71 lines
2.5 KiB
JavaScript

/* Mini Hash-Router für die PWA.
* Routen:
* #/login
* #/orders
* #/orders/<id>
* #/orders/<id>/upload
* #/settings
*/
(function () {
const routes = {};
let currentRoute = null;
function on(pattern, handler) {
// Pattern: '/orders/:id'
const re = new RegExp('^' + pattern.replace(/:[a-z]+/gi, '([^/]+)') + '$');
const params = (pattern.match(/:([a-z]+)/gi) || []).map(s => s.slice(1));
routes[pattern] = { re, params, handler };
}
async function navigate(hash) {
if (!hash) hash = window.location.hash || '#/orders';
const path = hash.replace(/^#/, '') || '/orders';
for (const key of Object.keys(routes)) {
const r = routes[key];
const m = path.match(r.re);
if (m) {
const args = {};
r.params.forEach((p, i) => { args[p] = decodeURIComponent(m[i + 1]); });
currentRoute = { pattern: key, args };
// Das Offline-Band gilt immer nur fuer die gerade gezeichnete Ansicht.
// Ohne dieses Zuruecksetzen bliebe es auf Seiten stehen, die gar keinen
// Spiegel haben (Einstellungen, Lieferungen).
if (typeof window.setStale === 'function') window.setStale(false);
try {
await r.handler(args);
} catch (e) {
console.error(e);
showToast('Fehler: ' + e.message, 'error');
}
return;
}
}
// Default
navigate('#/orders');
}
function go(hash) {
// Erst ausstehende history.back() (geschlossene Modals) durchlaufen lassen. Ein
// Hash-Wechsel davor wird vom Ruecksprung ueberholt, die Adresse steht danach auf
// der ALTEN Route, waehrend die neue angezeigt wird (app.js: historySettled).
const settled = (typeof window.historySettled === 'function')
? window.historySettled() : Promise.resolve();
settled.then(() => {
if (hash !== window.location.hash) {
window.location.hash = hash;
} else {
navigate(hash);
}
});
}
window.addEventListener('hashchange', () => navigate());
document.addEventListener('DOMContentLoaded', async () => {
if (typeof window.appBoot === 'function') {
try { await window.appBoot(); } catch (e) {}
}
navigate();
});
window.router = { on, navigate, go };
})();