All checks were successful
Deploy baustelle-pwa / deploy (push) Successful in 13s
Wenn die Kundin drei Nachrichten hintereinander schickt, musste man den Auftrag
bisher jedes Mal neu heraussuchen. Jetzt stehen die letzten drei benutzten
Aufträge unter "Zuletzt benutzt" ganz oben.
Wurde einer davon in den letzten 30 Minuten benutzt, ist er vorausgewählt und
der Knopf ist sofort scharf ("In SO2608-0044 ablegen") — die zweite und dritte
Ladung kosten damit einen einzigen Tipp. Nach Ablauf der 30 Minuten steht der
Auftrag nur noch als Schnellzugriff da, ohne Vorauswahl: eine dauerhafte
Vorauswahl würde irgendwann still im Auftrag von vorgestern landen, und die
Auftragsnummer steht deshalb auch immer im Knopftext.
Die Merkliste (IndexedDB 'share_recent_orders') wird beim erfolgreichen Ablegen
fortgeschrieben, neuester zuerst, ohne Duplikate, maximal drei Einträge.
Kartenaufbau und Auswahl-Logik liegen jetzt in gemeinsamen Helfern
(karteHtml/bindeKarten/waehleAuftrag), damit Schnellzugriff und Suchtreffer
denselben Auftrag synchron markieren.
Lokal durchgespielt: erste Ladung ohne Merkliste (keine Vorauswahl), zweite
direkt danach (vorausgewählt, ein Tipp, landet im selben Auftrag), und ein
drei Stunden alter Eintrag (Schnellzugriff sichtbar, aber Knopf grau).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
183 lines
12 KiB
Markdown
183 lines
12 KiB
Markdown
# 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-Dialoge** — `confirm`/`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`, `layout` — `composite_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+
|