Mobile PWA für Baustellen-Doku — Foto-Upload, Sprachnotizen, Skizzen, offline-fähig. SvelteKit + Workbox.
|
All checks were successful
Deploy baustelle-pwa / deploy (push) Successful in 13s
Drei Sachen, die zusammengehoeren — alle drei Ursachen dafuer, dass Fotos
fehlten oder nichts drauf zu erkennen war.
1. BACKGROUND SYNC (sw.js)
Nachliefern lief bisher NUR in der offenen Seite: online-Ereignis,
„wieder sichtbar", 15-Sekunden-Takt. Display aus oder App weggewischt =
eingefrorene Timer, es passierte nichts. Der Service Worker haengt sich
jetzt an dieselbe IndexedDB-Queue und laedt selbst hoch; die Anmeldung
steckt im HttpOnly-Cookie awl_sso und geht bei same-origin automatisch mit,
also braucht es dort kein Token-Handling.
- geloescht wird nur bei bestaetigtem Upload: 2xx UND application/json UND
relpath. 2xx mit HTML (Proxy/abgelaufene Anmeldung) gilt als Fehlschlag
- bleibt etwas offen, wird das Sync-Versprechen abgelehnt -> der Browser
stellt erneut zu
- Erfolg meldet der SW per postMessage an die App (Badge/Miniaturen)
- periodicSync zusaetzlich, falls der Browser ihn gewaehrt
2. VOLLE KAMERAQUALITAET (app.js)
Ausgeloest wird jetzt ueber ImageCapture.takePhoto() — das liefert die
native JPEG-Datei der Kamera in Sensoraufloesung samt EXIF, statt ein
Standbild aus dem Video-Stream abzugreifen. Kann der Browser das nicht,
bleibt das Standbild als Rueckfallebene (dann mit Qualitaet 0.95 statt 0.92).
Verkleinert wird nur noch, wenn es im Admin eingestellt ist (Vorgabe: nein).
3. PERSIST-FIRST STRENG (app.js)
Das Foto geht unveraendert und als allererstes in die Queue; Verkleinern
passiert erst DANACH und ersetzt den Eintrag nur bei Erfolg
(offline.replaceQueuedBlob). Vorher lag das Verkleinern davor — und weil es
bei ausgeschaltetem Display haengen bleibt, war das Foto in dem Moment
nirgends gesichert. Genau so gingen am 28.08. acht Fotos verloren. Jetzt
kann nach dem Sichern nichts mehr passieren, was ein Foto kostet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .forgejo/workflows | ||
| icons | ||
| lib | ||
| .gitignore | ||
| app.css | ||
| app.js | ||
| index.php | ||
| manifest.webmanifest | ||
| README.md | ||
| ROADMAP.md | ||
| share.html | ||
| sw.js | ||
Baustelle PWA
Mobile Progressive Web App für die Baustellen-Doku — Foto-Upload, Sprach- und Textnotizen, Skizzen, offline-fähig. Spricht die REST-API des Dolibarr-Bericht-Moduls.
Stack
- Vanilla JavaScript — kein Build, keine Framework-Abhängigkeit
- Service Worker für App-Shell-Cache (Workbox-frei, eigene Implementierung)
- IndexedDB für die Offline-Upload-Queue (Auth-Session steckt im HttpOnly-Cookie, nicht in IndexedDB)
- REST API des
bericht-Dolibarr-Moduls unter/custom/bericht/api/
Features
Aufträge
- ✅ Login mit Dolibarr-Credentials → zentrales SSO (HttpOnly-Cookie
awl_sso, same-origin; Single-Sign-On/-Logout über alle AWL-Apps). WebAuthn/Passkey-Login möglich - ✅ Auftragsliste mit Suche, gefiltert auf eigene Aufträge (Multi-User)
- ✅ Toggle "Auch abgeschlossene" (Filter in localStorage persistiert)
- ✅ Auftragsdetail mit Kunde, Adresse, Telefon (Click-to-Call)
- ✅ Neuer Auftrag (FAB): Kunde per Suche/Zuletzt-verwendet wählen, dann Ihr Zeichen (Pflicht), Auftragsbeschreibung (optional), Geplanter Liefertermin (Datum + Uhrzeit) und Checkbox Auftrag direkt freigeben. Kunden-Defaults (Zahlungsbedingung/Zahlart, Ansprechpartner) werden übernommen. Bei „direkt freigeben" wird der Auftrag ohne Position validiert — die Leistungen kommen aus dem Stundenzettel. (Bis Bericht 1.3.1 hängte das Backend hier eine Std-Lohn-Zeile mit Menge 1 an; die verfälschte den Auftragswert, stand als offene Restmenge in der Lieferauflistung und im Kunden-PDF.)
Foto-Upload
- ✅ Live-Kamera-Modal (getUserMedia): bleibt für Serien-Aufnahmen offen — Auslöser + Filmstreifen mit Upload-Status je Foto (💾 gesichert → ⏳ lädt hoch → ✓ fertig), Front-/Rückkamera-Umschaltung, Fehlschuss löschbar
- ✅ Alternativ Aufnahme aus der Galerie
- ✅ Clientseitige Bild-Verkleinerung auf 2000px (JPEG q=0.85)
- ✅ Persist-First / Datenverlust-Schutz: jedes Foto wird beim Auslösen ZUERST in IndexedDB gesichert, Upload erst danach; Queue-Item wird nur nach bestätigtem Upload (HTTP 2xx mit gültigem
relpath) gelöscht — überlebt fehlendes/schwaches Netz, hängende Uploads (45s-Timeout) und App-Kill. Erkennt 2xx-HTML (abgelaufene Session/Proxy-Loginseite) als Fehler statt als Erfolg - ✅ Auto-Sync bei "online", periodisch (15s) und bei App-Fokus; Status-Badge (🟢 alles gesichert / 🟡 lädt hoch / 🔴 offline / ⚠️ fehlgeschlagen)
- ✅ Recovery: Tipp auf das Status-Badge öffnet die Warteschlange → erneut senden / teilen; Warnung beim Schließen mit noch ungesicherten Fotos
- ✅ Kacheln laden serverseitige Thumbnails (
photo.php?size=thumb) statt der Originale — rund 10 KB statt mehrerer hundert KB je Kachel, parallel und mitloading="lazy", Wiederholaufrufe enden mit 304 - ✅ Foto-Viewer mit Zoom + Swipe
- ✅ Foto-Skizze: Annotationen mit Pfeilen, Kreisen, Rechtecken, Text
Notizen
- ✅ Getippte Notiz zum Auftrag: Betreff + Text, Liste mit Vorschau und Zeitstempel, jederzeit wieder les- und änderbar
- ✅ Ablage als
notiz_<betreff>_<datum>.txtim Auftragsordner — damit auch im Dolibarr-Auftrag und in der Anhänge-Spalte des Bericht-Editors sichtbar - ✅ Löschen mit Rückfrage; in „Weitere Dokumente" tauchen Notizen nicht doppelt auf
Sprachnotizen
- ✅ Sprachaufnahme via MediaRecorder (webm)
- ✅ Whisper-Transkription (serverseitig, API-Aufruf)
Berichte
- ✅ Berichte pro Auftrag anzeigen
- ✅ Seitenkacheln zeigen das im Editor gebaute Seitenbild (
composite_path) samt Anmerkungen — nicht mehr nur das Rohbild; Raster-Layouts sind mit „⊞ 4" gekennzeichnet - ✅ Bericht erstellen mit Template-Auswahl
- ✅ Seiten umordnen per Drag&Drop
- ✅ Seite löschen, Notiz bearbeiten
- ✅ Touch-Unterschrift abnehmen (mit GPS + Name)
- ✅ Bericht finalisieren → PDF-Vorschau
- ✅ PDF-Download
Lieferungen + Kundenunterschrift
- ✅ Lieferungen-Liste pro Auftrag mit Status + Signed-Badge
- ✅ Lieferschein-PDF inline (PDF.js, alle Seiten)
- ✅ Vollbild-Querformat-Unterschrift mit HiDPI-Canvas, transparent (kein Hintergrund)
- ✅ Trimmt Canvas automatisch auf bemalte Fläche
- ✅ Name des Unterzeichners aus Kundendaten vorausgefüllt
- ✅ GPS-Koordinaten (optional, timeout 3s)
- ✅ Unterschrift wird via ODT-Hook in das Lieferschein-PDF gestempelt
- ✅ Expedition wird nach Unterschrift automatisch validiert + geschlossen
- ✅ Signed-PDF direkt nach Bestätigung als Vorschau
Materialliste
- ✅ Material pro Auftrag erfassen (Label, Menge, Einheit, Notiz)
- ✅ Material löschen
PWA
- ✅ Service Worker für Offline-Start
- ✅ Installierbar als PWA (Home-Screen-Icon)
- ✅ Web Share Target API (Fotos aus anderer App teilen)
- ✅ Nativer Android-Share-Sheet für Fotos, PDFs und Dokumente (
navigator.share) — direkt an WhatsApp / Signal / Mail - ✅ Mehrfachauswahl in Foto- und Dokument-Liste mit Batch-Teilen
- ✅ Android-Hardware-Back-Button schließt Modale/Ansichten sauber (History-Modal-Stack), bricht Mehrfachauswahl ab statt App zu verlassen
- ✅ „Nochmal drücken zum Beenden"-Toast auf Top-Level-Routen
- ✅ PIN-Schutz (optional)
- ✅ Bild-Cache
baustelle-media(cache-first, LRU 300): schon angesehene Fotos sind auch ohne Netz da; überlebt Deploys, wird beim Abmelden geleert - ✅ Keine Browser-Dialoge —
confirm/alert/promptsind durch eigene Modale ersetzt (confirmDialog,alertDialog,inputDialog); der Hardware-Zurück-Button schließt sie als Abbruch
Geplant
- Push-Notifications für neue Aufträge
Hosting
Deploy per Forgejo-Pipeline (Commit-Tag [deploy]) nach /mnt/appdata/firma/dolibarr-202509/modules/baustelle/. Erreichbar unter https://awl.data-it-solution.de/custom/baustelle/. Cache-Busting automatisch via filemtime() in index.php (?v=<mtime> an JS/CSS) — kein manuelles Versions-Hochzählen nötig, die PWA aktualisiert sich beim nächsten Laden.
Verzeichnis-Struktur
baustelle-pwa/
├── index.php App-Shell (Cache-Busting via filemtime → ?v=<mtime>)
├── share.html Web-Share-Target-Empfänger
├── manifest.webmanifest PWA Manifest
├── sw.js Service Worker (Network-First für Assets, API pass-through,
│ cache-first für photo.php?size=thumb → Cache baustelle-media)
├── app.js Hauptlogik (Routes, Views, Dialoge, Notizen)
├── app.css Mobile-first Styling
├── lib/
│ ├── idb.js IndexedDB-Wrapper (Foto-Queue)
│ ├── api.js REST-API-Client (SSO-Cookie same-origin, JSON-Guard, Timeout)
│ ├── offline.js Offline-Queue + Sync (Persist-First)
│ ├── router.js Hash-Router
│ ├── pdf.min.js PDF.js (Lieferschein-Vorschau)
│ └── pdf.worker.min.js PDF.js Worker
└── icons/
├── icon-192.png
└── icon-512.png
API
Die App spricht die Bericht-API unter /custom/bericht/api/:
Auth
POST /auth.php— Login mit Dolibarr-Credentials → setzt HttpOnly-Cookieawl_sso(zentrales SSO via awlauth). Folge-Requests authentisieren same-origin über das Cookie (kein Bearer-Token)
Aufträge
GET /orders.php— Aufträge des Users (Filter:?open=1,?q=suche)GET /orders.php?id=X— Auftrag-Detail inkl. verknüpfte BerichteGET /orders.php?id=X&action=photos— Anhang-Liste (je Dateitaken_at= EXIF-Aufnahmezeit, danach sortiert)POST /orders.php?id=X&action=upload_photo— Foto-Upload (multipart)
Kunden
GET /customers.php— Kundenliste (Filter:?q=suche)GET /customers.php?id=X— Kundendetail
Berichte
GET /reports.php— Alle Berichte des UsersGET /reports.php?id=X— Bericht-Detail mit Seiten (je Seitecomposite_path,title,layout—composite_pathanzeigen, wenn gesetzt)POST /reports.php?action=create— Neuen Bericht anlegenPOST /reports.php?id=X&action=finalize— Bericht finalisierenDELETE /reports.php?id=X— Bericht löschen
Seiten
DELETE /pages.php?id=X— Seite löschenPOST /pages.php?id=X— Seite aktualisieren (note, rotation)POST /pages.php?action=signature&bericht_id=X— Unterschrift hinzufügenPOST /pages.php?action=reorder— Seiten umordnen
Material
GET /materials.php?element_type=X&element_id=Y— MateriallistePOST /materials.php?element_type=X&element_id=Y— Material hinzufügenPOST /materials.php?id=X&delete=1— Material löschen
Lieferungen
GET /shipments.php?order_id=X— Lieferungen zu einem AuftragGET /shipments.php?id=X— Lieferung-Detail (inkl. bericht_id falls Unterschrift vorhanden)GET /shipments.php?id=X&action=pdf[&variant=auto|signed|unsigned]— Lieferschein-PDFPOST /shipments.php?id=X&action=confirm— Kunden-Unterschrift einstempeln (FormData mit signature_png, signer_name, gps_lat, gps_lon, signed_at). Backend setzt signed_status=1 und schließt die Expedition
Notizen
GET /note.php?order_id=X— Liste (Betreff, Vorschau, Zeitstempel)GET /note.php?order_id=X&file=<name>— eine Notiz mit vollem TextPOST /note.php?order_id=X— anlegen{ subject, text }POST /note.php?order_id=X&file=<name>— ändernPOST /note.php?order_id=X&file=<name>&delete=1— löschen
Medien
GET /photo.php?relpath=X— Datei abrufen; Auth überawl_sso-Cookie (same-origin, auch für<img>-Tags — kein?jwt=mehr nötig)GET /photo.php?relpath=X&size=thumb&w=<px>— serverseitiges Thumbnail (GD, gecacht, ETag → 304). Der Weg für Kachel-Ansichten.size=smallist etwas anderes: das liefert nur ein von Dolibarr vorgefertigtes Thumb, das es für PWA-Uploads gar nicht gibt — dort kam bis Modul-Version 1.5.1 immer das Original zurückPOST /delete_photo.php— Foto löschenPOST /voice.php?order_id=X— Sprachnotiz hochladenPOST /transcribe.php— Whisper-Transkription anfordernGET /pdf.php?id=X— PDF abrufen (Final oder Preview); Auth überawl_sso-Cookie (same-origin)
Templates
GET /templates.php— Bericht-VorlagenGET /odt_templates.php— ODT-Deckblatt-Vorlagen
Lizenz
GPL v3+