feat(i18n): add internationalization support with English and Spanish translations
feat(kiosk): implement kiosk report submission functionality with offline support test(outbox): add tests for outbox functionality including report submission and error handling feat(outbox): create outbox management for queued reports with IndexedDB persistence feat(uuid): add UUID generation utility for client IDs feat(qr): implement QR code generation for reporting links by site and area feat(kiosk): create Kiosk page for reporting incidents without an account
This commit is contained in:
1 parent
3f6dff5f24
commit
3e964d1176
64 files changed
+10056
-267
No files matched your search
+20
-1
@@ -68,6 +68,8 @@ Addressed by number (`/api/incidents/42` is `INC-0042`). Numbers are sequential
|
||||
|
||||
**List and export filters:** `page`, `pageSize` (1–100, default 25), `q` (title, description, location; `INC-0042` or `42` matches the number), `type`, `severity`, `status`, `siteId`.
|
||||
|
||||
Send a random `clientId` (UUID) with each report. Resubmitting the same `clientId` returns the original report (200 instead of 201) instead of creating a duplicate, so devices can safely retry after a dropped connection.
|
||||
|
||||
**Create body:**
|
||||
|
||||
```json
|
||||
@@ -81,7 +83,8 @@ Addressed by number (`/api/incidents/42` is `INC-0042`). Numbers are sequential
|
||||
"locationDetail": "Loading bay C",
|
||||
"description": "…",
|
||||
"immediateAction": "Area cordoned off",
|
||||
"anonymous": false
|
||||
"anonymous": false,
|
||||
"clientId": "6f1c…"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -119,6 +122,22 @@ Addressed by number (`/api/incidents/42` is `INC-0042`). Numbers are sequential
|
||||
|
||||
Each action has `overdue: true` when it's open or in progress and its due date has passed (server time zone, `TZ`). Assignees get one email digest per day listing their actions due tomorrow or overdue.
|
||||
|
||||
## Reporting links (kiosks and QR codes)
|
||||
|
||||
Reporting links let people file reports without an account. Each has a token that can **only** create reports.
|
||||
|
||||
| Method | Path | Who | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| GET | `/api/kiosks` | safety_manager | All links, including their tokens (to reprint QR codes) |
|
||||
| POST | `/api/kiosks` | safety_manager | `{ name, siteId? }`. `siteId` restricts the link to one site |
|
||||
| PATCH | `/api/kiosks/:id` | safety_manager | `{ name?, siteId?, revoked? }` |
|
||||
| GET | `/api/kiosk/config` | link token | Organization name, link name, and the sites/areas it may report on |
|
||||
| POST | `/api/kiosk/reports` | link token | Multipart: `report` (JSON: the create body minus `anonymous`, plus optional `reporterName`) and up to 5 `files`. Returns `{ reference, failedFiles }`. 30 reports per link per 10 minutes |
|
||||
|
||||
The token goes in the `X-Kiosk-Token` header. Browsers can't send custom headers cross-site without a CORS preflight, which the API doesn't allow, so other websites can't submit through a link. Reports without `reporterName` are anonymous. Incidents filed this way have `channel: "kiosk"` and `kiosk: { id, name }`.
|
||||
|
||||
The web app opens links at `/k/<token>`, optionally with `?site=<id>&area=<id>` to pre-fill the location (as QR codes do).
|
||||
|
||||
## Stats
|
||||
|
||||
| Method | Path | Who | Notes |
|
||||
|
||||
+10
-4
@@ -17,6 +17,9 @@ This is a living document. Update it as decisions are made or scope changes.
|
||||
| Compliance scope | US OSHA first; data model kept extensible for other regions (e.g. RIDDOR) | Proposed |
|
||||
| Incident PDFs | Browser print-to-PDF from a print stylesheet; server-side PDF deferred (see TODO) | Decided |
|
||||
| Email | SMTP via `SMTP_URL`; without it, logged in development and dropped in production. Delivery setup deferred (see TODO) | Decided |
|
||||
| Reporting without accounts | "Reporting links": revocable, rate-limited tokens that can only file reports; used for kiosk tablets and QR codes | Decided |
|
||||
| Languages | English + Spanish with typed message files; worker-facing screens first, manager screens later (see TODO) | Decided |
|
||||
| Offline | Installable PWA; reports queued in IndexedDB and retried with a client-generated id so retries never duplicate | Decided |
|
||||
| CI | Gitea Actions (`.gitea/workflows`): lint, typecheck, unit tests, build, e2e | Decided |
|
||||
| Product name | Open SafetyLog EHS (replaced placeholder "IA Tracker" and the prototype's "Safesite" branding) | Decided |
|
||||
|
||||
@@ -61,10 +64,13 @@ This is a living document. Update it as decisions are made or scope changes.
|
||||
|
||||
## Phase 3: Shop-floor reporting
|
||||
|
||||
- Installable PWA with offline queue (sync when connection returns)
|
||||
- Printable QR codes per area that pre-fill the location
|
||||
- Kiosk mode and anonymous reporting
|
||||
- Multi-language support, Spanish first
|
||||
- [x] Installable app (web manifest, icons, service worker caching the app shell plus who-am-I and site lists)
|
||||
- [x] Offline outbox: reports and photos saved on the device, sent automatically on reconnect; duplicate-proof retries via a client-generated id; reports only sent for the user who made them; warning on sign-out with unsent reports
|
||||
- [x] Reporting links (managed in Settings): revocable tokens, optionally limited to one site, 30 reports per 10 minutes, photos included; kiosk page for shared tablets with large touch targets and auto-reset
|
||||
- [x] Printable QR code sheets per site/area, for signed-in users or through a reporting link, with bilingual captions; scanning pre-fills the location
|
||||
- [x] Anonymous reporting from the app, the kiosk and QR codes
|
||||
- [x] English/Spanish: sign-in pages, navigation, report form, kiosk, My actions, offline messages; language toggle remembered per device; follows the browser language by default
|
||||
- [x] Tests: outbox (IndexedDB), translations complete, reporting links and duplicate-proof retries on the server, browser tests for offline sync, Spanish kiosk via QR, QR pre-fill and installability
|
||||
|
||||
## Phase 4: Compliance (US OSHA)
|
||||
|
||||
|
||||
@@ -23,6 +23,14 @@ Open items that aren't tied to a single roadmap phase. Tick them off (or delete
|
||||
- [ ] **HEIC thumbnails.** iPhone HEIC photos are stored and downloadable but browsers can't show them inline; convert to JPEG on upload.
|
||||
- [ ] **Session check console noise.** Signed-out visitors see one 401 in the browser console from the session check; harmless, but a `200 { user: null }` endpoint would be quieter.
|
||||
|
||||
## Phase 3 follow-ups
|
||||
|
||||
- [ ] **Translate manager screens.** Spanish currently covers what shop-floor workers use (sign-in, report form, kiosk, My actions, navigation). The incident page, investigation, dashboards, analytics, locations and settings are still English. Add their strings to `src/i18n/en.ts` and `es.ts`.
|
||||
- [ ] **Spanish emails.** Notification and sign-in emails are English only. Store a language preference per user and use it for emails.
|
||||
- [ ] **HTTPS for offline use.** Browsers only allow installing the app and the offline app shell over HTTPS (or localhost). Reports still queue offline over plain HTTP once the page is open, but the page itself can't load offline. Document a simple HTTPS setup (e.g. Caddy in front of the app) in the install guide.
|
||||
- [ ] **Have Spanish reviewed by a native speaker** familiar with plant safety terms (e.g. "casi accidente" for near miss, "planta" for site).
|
||||
- [ ] **Offline incident list.** Only reporting works offline; viewing incidents and actions needs a connection.
|
||||
|
||||
## Done
|
||||
|
||||
- [x] Docker verified 2026-10-06 (Docker 29.8, Compose v5.5): image builds (~315 MB), `docker compose up` with PostgreSQL 18 passes setup → sign-in → report smoke test; data and sessions survive `down`/`up`; clean shutdown; healthcheck healthy; runs as non-root; standalone PGlite container works; `Secure` cookie with an `https://` PUBLIC_URL; web app loads with no errors.
|
||||
|
||||
Reference in new issue
Block a user