Mobile PWA für Baustellen-Doku — Foto-Upload, Sprachnotizen, Skizzen, offline-fähig. SvelteKit + Workbox.
Find a file
2026-08-31 19:13:38 +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 App ohne Netz lesbar statt "Failed to fetch" 2026-08-31 19:07:09 +02:00
.gitignore feat: Initiales Release Baustelle PWA v1.0.0 [deploy] 2026-04-08 22:50:01 +02:00
app.css App ohne Netz lesbar statt "Failed to fetch" 2026-08-31 19:07:09 +02:00
app.js App ohne Netz lesbar statt "Failed to fetch" 2026-08-31 19:07:09 +02:00
index.php App ohne Netz lesbar statt "Failed to fetch" 2026-08-31 19:07:09 +02:00
manifest.webmanifest Textnotizen, echte Thumbnails, eigene Dialoge, Bild-Cache 2026-08-22 18:12:09 +02:00
README.md App ohne Netz lesbar statt "Failed to fetch" 2026-08-31 19:07:09 +02:00
ROADMAP.md Doku: ROADMAP-Stand auf 31.08.2026 nachgezogen 2026-08-31 19:13:38 +02:00
share.html Teilen-Seite merkt sich die letzten Aufträge — Serie kostet nur noch einen Tipp [deploy] 2026-08-30 20:19:37 +02:00
sw.js App ohne Netz lesbar statt "Failed to fetch" 2026-08-31 19:07:09 +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
  • Serie ohne Sucherei: die Teilen-Seite merkt sich die letzten drei benutzten Aufträge und zeigt sie unter „Zuletzt benutzt" ganz oben. Wurde einer davon in den letzten 30 Minuten benutzt, ist er vorausgewählt — die zweite und dritte Nachricht der Kundin kosten dann nur noch einen Tipp. Danach steht er nur noch als Schnellzugriff da, damit nichts versehentlich im Auftrag von vorgestern landet
  • 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

Ohne Netz (Keller, Neubau, Funkloch — auf der Baustelle der Normalfall)

  • Datenspiegel: Auftragsliste, Kundenliste, Berichte sowie geöffnete Auftrags- und Kundendetails werden nach jedem erfolgreichen Abruf in IndexedDB gespiegelt und ohne Netz von dort angezeigt (Details: die letzten 30). Zusammen mit dem Bild-Cache steht ein einmal geöffneter Auftrag samt Fotos auch im Funkloch
  • Sichtbares Band📴 Kein Netz — angezeigt wird der Stand von HH:MM Uhr" — ohne den Hinweis hält man alte Zahlen für aktuell. Kommt das Netz zurück, lädt die Ansicht neu und das Band verschwindet
  • Suche greift auf den gespiegelten Stand zu, statt ins Leere zu laufen (mit Hinweis)
  • Verständliche Meldungen: ein fetch(), das nicht bis zum Server kommt, wird zu „Keine Verbindung zum Server" — nicht mehr die rohe Browsermeldung „Failed to fetch". Ein echter Serverfehler (401/403/500) wird weiterhin als solcher gemeldet
  • Start ohne Netz: der Service Worker liefert die App-Hülle aus dem Cache; die Seite meldet ihm nach dem Laden ihre tatsächlichen Asset-URLs zum Nachcachen, sonst wäre der Cache direkt nach einem Deploy leer (?v=<mtime> verhindert eine feste Dateiliste)
  • Beim Abmelden wird der Spiegel gelöscht — am geteilten Gerät sieht sonst der nächste Benutzer offline die Kunden und Preise des vorherigen
  • Ändern braucht Netz — bewusst keine Schreib-Warteschlange für Auftragsdaten (bräuchte Idempotenz + Konfliktbehandlung). Fotos sind die Ausnahme: die gehen über die Persist-First-Queue und werden nachgeliefert

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+