Mobile PWA für Baustellen-Doku — Foto-Upload, Sprachnotizen, Skizzen, offline-fähig. SvelteKit + Workbox.
Find a file
Eddy b7e80fac8c Doku-Drift beseitigt: Push-Notifications, Heute-Route, drei API-Endpunkte fehlten
Beim Doku-Abgleich (/docs) gegen den Code aufgefallen:
- ntfy-Push-Notifications sind laengst fertig (app.js Einstellungen-Route),
  README fuehrte sie noch unter "Geplant" als offen
- "Heute"-Ansicht (Route /today, Google-Maps-Tagesroute) fehlte komplett
- API-Abschnitt: verify.php, logout.php, orders.php?action=create und
  config.php wurden von lib/api.js schon lange genutzt, standen aber nicht
  in der Endpunkt-Liste

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 20:12:31 +02:00
.forgejo/workflows CI: Ntfy-Benachrichtigungen (Start/Success/Failure) via data/ntfy-action 2026-05-28 06:54:44 +02:00
icons feat: Neues PWA-Logo — Klemmbrett mit Schraubenschlüssel (SVG + PNG) 2026-04-09 01:12:47 +02:00
lib Background Sync, volle Kameraqualitaet, Persist-First streng [deploy] 2026-08-28 19:07:06 +02:00
.gitignore feat: Initiales Release Baustelle PWA v1.0.0 [deploy] 2026-04-08 22:50:01 +02:00
app.css Teilen aus WhatsApp: Bilder UND Text landen jetzt wirklich im Auftrag [deploy] 2026-08-30 18:03:46 +02:00
app.js Teilen aus WhatsApp: Bilder UND Text landen jetzt wirklich im Auftrag [deploy] 2026-08-30 18:03:46 +02:00
index.php PWA-Login: Fingerabdruck-/Passkey-Anmeldung (WebAuthn) [deploy] 2026-07-07 15:41:37 +02:00
manifest.webmanifest Textnotizen, echte Thumbnails, eigene Dialoge, Bild-Cache 2026-08-22 18:12:09 +02:00
README.md Doku-Drift beseitigt: Push-Notifications, Heute-Route, drei API-Endpunkte fehlten 2026-08-30 20:12:31 +02:00
ROADMAP.md Teilen aus WhatsApp: Bilder UND Text landen jetzt wirklich im Auftrag [deploy] 2026-08-30 18:03:46 +02:00
share.html Teilen aus WhatsApp: Bilder UND Text landen jetzt wirklich im Auftrag [deploy] 2026-08-30 18:03:46 +02:00
sw.js Teilen aus WhatsApp: Bilder UND Text landen jetzt wirklich im Auftrag [deploy] 2026-08-30 18:03:46 +02:00

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
  • „Heute"-Ansicht: offene Aufträge des Tages, gefiltert aus der normalen Auftragsliste, mit Route für alle Termine auf einen Blick (öffnet Google Maps mit allen Adressen als Wegpunkten)
  • 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 mit loading="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>.txt im 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 — aus WhatsApp & Co. Bilder und Beschreibungstext an „Baustelle" teilen: anmelden (falls nötig, direkt in der Teilen-Seite), Auftrag suchen und wählen, ablegen. Bilder gehen über die normale Upload-Queue, der Text wird als Notiz (notiz_*.txt) im Auftrag gespeichert
  • 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)
  • Push-Benachrichtigungen via ntfy (statt klassischem Web-Push): eigener Server/Topic in den Einstellungen, Browser-Notification-Permission, Reconnect bei Verbindungsabbruch
  • Bild-Cache baustelle-media (cache-first, LRU 300): schon angesehene Fotos sind auch ohne Netz da; überlebt Deploys, wird beim Abmelden geleert
  • Keine Browser-Dialogeconfirm/alert/prompt sind durch eigene Modale ersetzt (confirmDialog, alertDialog, inputDialog); der Hardware-Zurück-Button schließt sie als Abbruch

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-Cookie awl_sso (zentrales SSO via awlauth). Folge-Requests authentisieren same-origin über das Cookie (kein Bearer-Token)
  • GET /verify.php — prüft das Cookie und verlängert die Session (sliding renewal); läuft bei jedem Routenwechsel (ensureAuth()) und ist der Weg, wie share.html prüft, ob eine Anmeldung nötig ist
  • POST /logout.php — löscht das awl_sso-Cookie (Single-Logout über alle AWL-Apps)

Aufträge

  • GET /orders.php — Aufträge des Users (Filter: ?open=1, ?q=suche)
  • GET /orders.php?id=X — Auftrag-Detail inkl. verknüpfte Berichte
  • GET /orders.php?id=X&action=photos — Anhang-Liste (je Datei taken_at = EXIF-Aufnahmezeit, danach sortiert)
  • POST /orders.php?id=X&action=upload_photo — Foto-Upload (multipart)
  • POST /orders.php?action=create — Neuen Auftrag anlegen (Kunde, Ihr Zeichen, Beschreibung, Liefertermin, optional direkt freigeben)

Kunden

  • GET /customers.php — Kundenliste (Filter: ?q=suche)
  • GET /customers.php?id=X — Kundendetail

Berichte

  • GET /reports.php — Alle Berichte des Users
  • GET /reports.php?id=X — Bericht-Detail mit Seiten (je Seite composite_path, title, layoutcomposite_path anzeigen, wenn gesetzt)
  • POST /reports.php?action=create — Neuen Bericht anlegen
  • POST /reports.php?id=X&action=finalize — Bericht finalisieren
  • DELETE /reports.php?id=X — Bericht löschen

Seiten

  • DELETE /pages.php?id=X — Seite löschen
  • POST /pages.php?id=X — Seite aktualisieren (note, rotation)
  • POST /pages.php?action=signature&bericht_id=X — Unterschrift hinzufügen
  • POST /pages.php?action=reorder — Seiten umordnen

Material

  • GET /materials.php?element_type=X&element_id=Y — Materialliste
  • POST /materials.php?element_type=X&element_id=Y — Material hinzufügen
  • POST /materials.php?id=X&delete=1 — Material löschen

Lieferungen

  • GET /shipments.php?order_id=X — Lieferungen zu einem Auftrag
  • GET /shipments.php?id=X — Lieferung-Detail (inkl. bericht_id falls Unterschrift vorhanden)
  • GET /shipments.php?id=X&action=pdf[&variant=auto|signed|unsigned] — Lieferschein-PDF
  • POST /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 Text
  • POST /note.php?order_id=X — anlegen { subject, text }
  • POST /note.php?order_id=X&file=<name> — ändern
  • POST /note.php?order_id=X&file=<name>&delete=1 — löschen

Medien

  • GET /photo.php?relpath=X — Datei abrufen; Auth über awl_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=small ist 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ück
  • POST /delete_photo.php — Foto löschen
  • POST /voice.php?order_id=X — Sprachnotiz hochladen
  • POST /transcribe.php — Whisper-Transkription anfordern
  • GET /pdf.php?id=X — PDF abrufen (Final oder Preview); Auth über awl_sso-Cookie (same-origin)

Templates

  • GET /templates.php — Bericht-Vorlagen
  • GET /odt_templates.php — ODT-Deckblatt-Vorlagen

Konfiguration

  • GET /config.php — Admin-Vorgaben zur Fotoqualität (photo_maxside, photo_quality); wird beim Start und nach jedem Login neu geladen, zwischengespeichert in localStorage für den Offline-Fall

Lizenz

GPL v3+