From ac52b4a20ea559f4e9dfc6c923f09195a1604be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Thu, 12 Mar 2026 22:13:14 +0100 Subject: [PATCH] add document scanning design spec Co-Authored-By: Claude Opus 4.6 --- .../2026-03-12-document-scanning-design.md | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-12-document-scanning-design.md diff --git a/docs/superpowers/specs/2026-03-12-document-scanning-design.md b/docs/superpowers/specs/2026-03-12-document-scanning-design.md new file mode 100644 index 0000000..8d9b671 --- /dev/null +++ b/docs/superpowers/specs/2026-03-12-document-scanning-design.md @@ -0,0 +1,81 @@ +# PTV11 Document Capture & Storage — Design Spec + +**Goal:** Allow users to photograph or upload PTV11 documents and associate them with an Erstgespräch (sprechstunde). Multiple documents per Erstgespräch. Stored locally, viewable in-app. + +**Architecture:** Thin IndexedDB wrapper for blob storage, separate from PGlite. No new SQL migrations. UI integrated into the existing ErstgespraechCard in the process stepper. + +**Tech Stack:** IndexedDB (raw API), `` with native document scanner, `URL.createObjectURL()` for previews. + +--- + +## Storage Layer + +New module: `src/shared/hooks/dokument-store.ts` + +Opens a dedicated IndexedDB database `tpf-dokumente` with a single object store `dokumente` (auto-increment key, indexed on `sprechstundeId`). + +### Record Shape + +```ts +interface Dokument { + id: number; // auto-incremented + sprechstundeId: number; + name: string; // original filename + mimeType: string; // "image/jpeg", "application/pdf", etc. + blob: Blob; + erstelltAm: string; // ISO date +} +``` + +### API + +- `saveDokument(sprechstundeId: number, file: File): Promise` — store file, return id +- `getDokumente(sprechstundeId: number): Promise` — all docs for an Erstgespräch +- `deleteDokument(id: number): Promise` — remove single doc +- `deleteDokumenteForSprechstunde(sprechstundeId: number): Promise` — cascade on Erstgespräch delete +- `deleteAllDokumente(): Promise` — for "delete all data" in settings + +Plain async functions (not React hooks). Components call imperatively and manage their own state. + +## UI Integration + +### Location + +Inside the existing `ErstgespraechCard` in `src/features/prozess/components/process-stepper.tsx`, below diagnosis/Dringlichkeitscode info. + +### New Component + +`src/features/prozess/components/dokument-liste.tsx` + +Props: `{ sprechstundeId: number }` + +Manages its own state: loads documents on mount, refreshes after add/delete. + +### Upload + +`` styled as a button labeled "Dokument hinzufügen". On mobile this triggers the system picker which includes "Scan Documents" on iOS and camera/files on Android. + +### Thumbnails + +Grid of small previews: +- Images: `URL.createObjectURL()` thumbnails +- PDFs: generic PDF icon with filename + +Each thumbnail has a delete button (X) with confirmation. + +### Full-size Viewing + +- Images: modal overlay, image scaled to fit viewport, tap outside or X to close +- PDFs: open in new browser tab via `URL.createObjectURL()` (native PDF viewing) + +## Cleanup Integration + +- `deleteErstgespraech(id)` in `src/features/prozess/hooks.ts` also calls `deleteDokumenteForSprechstunde(id)` +- `deleteAllData()` in `src/features/kontakte/hooks.ts` also calls `deleteAllDokumente()` +- Scenario seeding unchanged — dev scenarios don't need documents + +## Out of Scope + +- PDF export integration (documents into the Absagenliste PDF) — deferred to a future sub-project +- OCR / data extraction from PTV11 +- Cloud sync / backup of documents