baustelle-pwa/ROADMAP.md
Eddy 707638808c
All checks were successful
Deploy baustelle-pwa / deploy (push) Successful in 13s
Teilen aus WhatsApp: Bilder UND Text landen jetzt wirklich im Auftrag [deploy]
Jeder Teilen-Vorgang endete bisher in einer Sackgasse — Login-Hinweis oder
"Keine geteilten Fotos gefunden", und danach war das Geteilte weg. Zwei Ursachen:

1. share.html prüfte api.getToken() — ein JWT in IndexedDB, das es seit der
   SSO-Umstellung nicht mehr gibt (api.login() legt nur noch 'user' ab, die
   Sitzung steckt im HttpOnly-Cookie awl_sso). Der Wert war immer leer, also kam
   IMMER "Bitte zuerst anmelden"; der Weg "Zur App" verwarf das Geteilte.
   Jetzt: Cookie-Prüfung über api/verify.php wie in ensureAuth(), und die
   Anmeldung (Passwort + Passkey) läuft IN der Teilen-Seite — das Geteilte bleibt
   dabei gesichert und der Ablauf geht danach direkt weiter.

2. Der Service Worker sicherte nur Dateien. Titel, Text und Link aus dem
   share_target wurden weggeworfen, ein reiner Text-Share schrieb gar nichts.
   Jetzt wird das ganze Paket gesichert und immer weitergeleitet (absolute
   Redirect-URL, leere File-Platzhalter fliegen raus).

Die Teilen-Seite zeigt Bildvorschau, Betreff und Text zum Nachbessern und eine
Auftragssuche (ohne Suchbegriff die offenen Aufträge, mit Suchbegriff alle).
Beim Ablegen gehen die Bilder über die normale Upload-Queue (Persist-First),
der Text wird als notiz_*.txt über api/note.php gespeichert. Scheitert die
Notiz (kein Netz), bleibt der Text gesichert und der Vorgang ist wiederholbar.

Dazu ein Banner in der App, wenn noch ein Paket unabgelegt bereitliegt —
abgebrochene Vorgänge findet sonst niemand wieder. share.html liest auch den
alten IndexedDB-Schlüssel, damit der erste Share nach dem Deploy (noch alter
Service Worker) nicht verlorengeht.

Lokal durchgespielt: Bild+Text, nur Text, und abgemeldet teilen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 18:03:46 +02:00

171 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ROADMAP — Baustelle PWA
Stand: 2026-08-22 · Gegenstück: `data/bericht` (Modul-Version 1.5.1)
Die PWA hat kein eigenes Backend — alles läuft über `custom/bericht/api/`. Punkte, die eine
API-Änderung brauchen, sind mit **[API]** markiert und stehen zusätzlich in der ROADMAP des
Bericht-Moduls.
---
## 1. Nachgezogen aus Bericht 1.5.0 ✅ (2026-08-22, Modul 1.5.1)
Das Modul hatte mit 1.5.0 Infrastruktur bekommen, von der die PWA nichts hatte.
Die Beschreibungen unten schildern jeweils den Zustand davor.
### 1.1 Echte Thumbnails statt Vollbilder ✅ [API]
Bis dahin lud die PWA für **jede Kachel das komplette Foto**:
- `loadThumbs()` ruft `api.getPhotoBlobUrl(rel, 'small')` auf → `photo.php?size=small`
- `photo.php` liefert nur dann ein Thumbnail, wenn Dolibarr eines unter
`thumbs/<name>_small.<ext>` **vorgefertigt** hat
- Die PWA lädt ihre Fotos aber per `orders.php?action=upload_photo` hoch, und dort wird
**nie `vignette()` aufgerufen** → es existiert nie ein Thumb → es kommt immer das Original
- Bei 30 Baustellenfotos sind das im Mobilfunknetz 30 × 0,51 MB (bei über die
Dolibarr-Oberfläche hochgeladenen Originalen entsprechend mehr)
Das Modul kann das seit 1.5.0 längst: `bericht_attachment_thumb($full, $maxside)` in
`lib/bericht.lib.php` (GD, Cache unter `bericht/thumbs/`, EXIF-Rotation, Speicher-Notbremse).
`api/photo.php` lädt diese lib bereits.
- [x] **[API]** `photo.php` um `size=thumb&w=<px>` erweitern → `bericht_attachment_thumb()`,
mit ETag aus mtime und `Cache-Control: private, max-age=…, immutable`
- [x] `loadThumbs()` umbauen: `<img src="…api/photo.php?…&size=thumb">` **direkt** setzen statt
fetch → Blob → ObjectURL. Damit greifen HTTP-Cache und `loading="lazy"` wirklich; heute
sind beide wirkungslos, weil der Blob schon vorher komplett geladen wird.
- [x] Sequenzielle Schleife auflösen — `for (const t of thumbs) { await … }` lädt Foto für Foto
nacheinander. Beim `<img>`-Weg erledigt der Browser das parallel von selbst.
- [x] Doppel-Ladung entfernen: schlägt `size=small` fehl, wird das Original **noch einmal**
geladen.
### 1.2 Aufnahmezeit statt Dateidatum ✅ [API]
1.5.0 hat einen eigenen EXIF-Parser bekommen (`bericht_jpeg_taken_at()` /
`bericht_file_taken_at()`), weil im Prod-Container das `exif`-Modul fehlt. Der Editor sortiert
Anhänge damit nach Aufnahmezeit. `orders.php?action=photos` lieferte nur `filemtime`.
- [x] **[API]** `taken_at` je Datei ausgeben
- [x] Fotoliste nach Aufnahmezeit sortieren (Auswahl gemerkt, wie im Editor)
### 1.3 Seiten-Titel kommt nie an ✅ [API]
`api/pages.php` **kann** `title` einer Seite setzen, die PWA rendert ihn (`page-title-badge`,
`openPageActionsModal(…, title)`) — aber `api/reports.php` gibt `title` im `pages`-Array
**nicht aus**. Ein im Editor gesetzter Titel ist in der PWA also unsichtbar, und ein in der PWA
gesetzter Titel scheint nach dem Neuladen verschwunden.
- [x] **[API]** `'title' => $p->title` in `reports.php` ergänzen (eine Zeile)
### 1.4 Bearbeitete Seiten und Raster-Layouts ✅ [API]
Seit 1.5.0 hat eine Seite `composite_path` (das im Editor gebaute Seitenbild) und `layout`
(`single`, `grid_2`, `grid_4`, `before_after`, `title_only`, …). Die PWA zeigt stur
`source_path`:
- Nach Bearbeitung im Desktop-Editor zeigt die PWA weiter das **Rohbild** ohne Anmerkungen
- Bei einer Rasterseite ist `source_path` nur **eines von bis zu sechs** Bildern
- Bei `title_only` gibt es gar keine Quelle → ❌-Kachel
- [x] **[API]** `composite_path` in `reports.php` mit ausgeben
- [x] PWA zeigt `composite_path`, wenn vorhanden, sonst `source_path`
- [x] Layout-Kennzeichen an der Kachel (z. B. „⊞ 4er-Raster"), damit klar ist, warum dort
mehr drauf ist als das eine Foto
---
## 2. Verbesserungen unabhängig vom Modul
### 2.1 Browser-Dialoge raus (10 Stück) ✅
`confirm()` / `alert()` / `prompt()` widersprechen der Hausregel und sehen in der
Standalone-PWA auf Android wie ein Systemfehler aus. Ein sauberes Muster liegt schon vor:
das „Bericht löschen"-Modal in `router.on('/reports/:id')`.
| Stelle | Zeile (vor dem Umbau) |
|---|---|
| PIN stimmt nicht überein | `alert` 295 |
| Bericht finalisieren | `confirm` 1111 |
| PIN-Schutz deaktivieren | `confirm` 1264 |
| Material-Eintrag löschen | `confirm` 1432 |
| Seite aus Bericht entfernen | `confirm` 1809 |
| Foto löschen | `confirm` 2701 |
| Nicht hochgeladenes Foto löschen | `confirm` 3019 |
| Mess-Skala: Länge eingeben | `prompt` 3409 |
| Ungültiges Längenformat | `alert` 3419 |
| „Zuerst kalibrieren" | `alert` 3440 |
- [x] Eine `confirmDialog({title, text, danger})`-Hilfsfunktion (Promise) + `inputDialog()`
für die Längeneingabe, alle zehn Stellen darauf umstellen
### 2.2 Fotos offline sichtbar halten ✅
Der Service Worker reichte alles unter `/custom/bericht/api/` ungecacht durch. Auf der Baustelle
ohne Netz ist damit **kein einziges** schon angesehenes Foto verfügbar.
- [x] Eigener Runtime-Cache `baustelle-media` für `photo.php`-GETs, cache-first mit
Größenbegrenzung (LRU, z. B. 200 Einträge). Die Thumb-URLs aus 1.1 sind dafür der
richtige Kandidat — klein und unveränderlich.
- [x] Vom App-Shell-Cache getrennt halten, damit ein Deploy die Medien nicht wegräumt
---
## 3. Textnotizen ✅ (2026-08-22)
Getippte Notiz zum Auftrag — für alles, was man lieber schreibt als diktiert: Zählernummern,
Absprachen, Maße.
- [x] „📝 Notizen (n)" am Auftrag, direkt unter der Sprachnotiz
- [x] Liste mit Betreff, zweizeiliger Vorschau und Zeitstempel; Antippen öffnet zum Lesen
und Ändern, 🗑 löscht (mit Rückfrage)
- [x] Editor: Betreff-Feld + Textfeld über die volle Höhe, Speichern über ✓ in der Kopfzeile,
Rückfrage beim Verlassen mit ungespeicherten Änderungen
- [x] Ablage als `notiz_<betreff>_<datum>.txt` im Auftragsordner (`api/note.php`) — genau der
Weg der Sprachnotiz. Damit auch im Dolibarr-Auftrag und in der Anhänge-Spalte des
Bericht-Editors sichtbar.
- [x] Notizen sind aus „Weitere Dokumente" ausgefiltert — sie haben ihre eigene Sektion und
stünden dort sonst doppelt
**Nicht** umgesetzt (und bewusst verworfen): eine Zeichenfläche für echte Handschrift. Am
Handy ohne Stift ist das unbrauchbar.
## 4. Teilen aus WhatsApp ✅ (2026-08-30)
Bilder und Beschreibungstexte, die die Kundin per WhatsApp schickt, landen direkt im Auftrag.
Vorher endete jeder Teilen-Vorgang in einer Sackgasse:
- `share.html` fragte `api.getToken()` — ein JWT in IndexedDB, das es **seit der SSO-Umstellung
gar nicht mehr gibt** (`api.login()` legt nur noch `user` ab, die Sitzung steckt im
HttpOnly-Cookie `awl_sso`). Ergebnis: immer „Bitte zuerst anmelden", und der Weg „Zur App"
verwarf das Geteilte.
- Der Service Worker sicherte **nur Dateien**. Ein Text-Share (oder Bild + Bildunterschrift)
schrieb nichts in IndexedDB → „Keine geteilten Fotos gefunden".
- [x] Anmeldung über `api/verify.php` (Cookie) statt über das nicht mehr existierende Token
- [x] Anmelden **in** der Teilen-Seite (Passwort + Passkey) — das Geteilte bleibt dabei gesichert
- [x] Service Worker sichert `title`, `text` und `url` mit; Weiterleitung auch ohne Datei
- [x] Auswahl-Seite: Bildvorschau, Betreff + Text zum Nachbessern, Auftragssuche
(ohne Suchbegriff die offenen Aufträge, mit Suchbegriff alle)
- [x] Ablegen: Bilder über die normale Upload-Queue (Persist-First), Text als Notiz über
`api/note.php`. Schlägt die Notiz fehl (kein Netz), bleibt der Text gesichert und der
Vorgang ist wiederholbar
- [x] Banner in der App, wenn noch etwas Geteiltes unabgelegt bereitliegt
## Erledigt
### 2026-08-22
Abschnitte 1, 2 und 3 vollständig. Dazu nebenbei behoben:
- `.modal-header`, `.close-btn` und `.modal-body` hatten **überhaupt kein CSS**, obwohl das
Markup sie seit Längerem nutzt (u. a. beim Löschen eines Berichts) — der Schließen-Knopf
saß dadurch unter der Überschrift statt daneben.
- Das Laden eines Thumbnails überschrieb den kompletten Kachel-Inhalt (`t.innerHTML = …`)
und nahm die Overlays mit: Auswahl-Häkchen in der Mehrfachauswahl und Seitennummer der
Bericht-Seiten waren weg, sobald das Bild da war.
- Beim Abmelden bleiben keine Kundenfotos mehr auf dem Gerät (`CLEAR_MEDIA` an den Service
Worker + `clearPhotoCache()`).
### Davor
Siehe README (Feature-Liste) und die Commit-Historie: SSO-Migration auf awlauth,
WebAuthn-Login, Neuer-Auftrag-Formular, Live-Kamera mit Persist-First-Queue,
Lieferschein-Unterschrift.