87 lines
4.0 KiB
Markdown
87 lines
4.0 KiB
Markdown
# SPQ-v2 Phase 4 Implementation Reference (2026-07-07)
|
|
|
|
## Completed Features
|
|
|
|
Six features implemented in a single session using batched parallel delegation.
|
|
|
|
### Batch A — Pure frontend (no PB migrations)
|
|
|
|
**4.5 Technician time-card CSV export**
|
|
- `src/pages/advisor/TimeCards.tsx` — date-range picker, queries `technicianAssignments` by shop, aggregates clock entries per-tech per-day, CSV download via Blob + URL.createObjectURL
|
|
- Route `/time-cards` in App.tsx, nav in both Layout.tsx and MobileLayout.tsx (Clock icon)
|
|
- Preload helper in `src/lib/routePreload.ts`
|
|
|
|
**4.9 Data export / GDPR**
|
|
- `src/pages/Privacy.tsx` — export all data as JSON download (repairOrders, customers, appointments, invoices, settings), delete account with two-step confirmation
|
|
- Route `/privacy` in App.tsx under `<AdvisorOnly>`, link in Settings Account tab
|
|
|
|
### Batch B — PB hooks (no new collections)
|
|
|
|
**4.1 Quote approval notifications**
|
|
- PB M23: `app.onRecordUpdateRequest` on `quotes` — when status changes `sent → approved/declined`, emails shop owner via PB mailer
|
|
|
|
**4.2 Quote expiry enforcement**
|
|
- PB M24: `app.onRecordUpdateRequest` on `quotes` — rejects share-token updates past `created + quoteExpiryDays` with 410
|
|
- `src/pages/QuoteApprove.tsx` — distinct "Quote Has Expired" UI (Clock icon, red styling), catches 410 on load and submit
|
|
|
|
### Batch C — New PB collections + frontend
|
|
|
|
**4.3 Financial audit log**
|
|
- PB M25: `roFinancialAudit` collection (roId, field, from, to, by, at, prevHash, hash) with hash-chained hook on `repairOrders` financial blob diffs
|
|
- Viewer in RODetailModal Timeline tab — queries by roId, shows field labels, from→to JSON, hash chain
|
|
|
|
**4.6 Recurring maintenance reminders**
|
|
- PB M26: `reminders` collection (customerId, customerName, vehicleInfo, shopUserId, mileageAtService, intervalMileage, dueMileage, notes, active)
|
|
- "Set Reminder" button in RODetailModal header, inline form with computed due mileage
|
|
- "Maintenance Reminders" card in Dashboard with count badge
|
|
|
|
## Skipped / Deferred
|
|
|
|
- 4.4 Multi-vehicle history — skipped per user
|
|
- 4.7 Offline-first for technicians — sub-project scope, skipped
|
|
- 4.8 Two-factor auth — skipped per user
|
|
- 4.10 Multi-shop readiness — decision deferred (no code change needed)
|
|
|
|
## PB Migrations Applied
|
|
|
|
- M23 `1740000000023_quote_approval_notify.js`
|
|
- M24 `1740000000024_quote_expiry_enforce.js`
|
|
- M25 `1740000000025_ro_financial_audit.js`
|
|
- M26 `1740000000026_reminders.js`
|
|
- M27 `1740000000027_set_audit_and_reminder_rules.js` (no-op placeholder due to PB 0.39 rule validation quirk)
|
|
|
|
## Verification
|
|
|
|
`npm run build` ✓, `npm run lint` ✓ (0 errors, 24 pre-existing warnings), `npm run test` ✓ (86/86 tests)
|
|
|
|
## PB 0.39 Quirks Discovered
|
|
|
|
See `pb-0.39-migration-quirks.md` for full details. Key ones:
|
|
- Field-based API rules can't be set via JS migrations (even in separate files) — use empty rules + frontend filtering
|
|
- `DateTimeField` doesn't exist — use `TextField` with ISO strings
|
|
- `onRecordBeforeUpdateRequest` and `onRecordAfterUpdateRequest` don't exist — use `onRecordUpdateRequest`
|
|
- Collection constructor and `new Record()` both work in JS migrations
|
|
|
|
## GlitchTip DSN Format (FIXED 2026-07-07)
|
|
|
|
The Sentry client-side SDK v10 rejects DSNs with multi-segment paths like `/glitchtip/1`. Fix applied:
|
|
|
|
1. **`.env.production`**: DSN path changed from `/glitchtip/1` to `/1`
|
|
2. **nginx**: Added regex location `^/([0-9]+)/(api|store)/(.*)$` that proxies bare project-ID paths to GlitchTip at `127.0.0.1:8001`
|
|
3. After hard refresh, Sentry initializes with zero console errors
|
|
|
|
The nginx snippet:
|
|
```nginx
|
|
location ~ ^/([0-9]+)/(api|store)/(.*)$ {
|
|
rewrite ^/([0-9]+)/api/(.*)$ /api/$1/$2 break;
|
|
rewrite ^/([0-9]+)/store/(.*)$ /api/$1/$2 break;
|
|
proxy_pass http://127.0.0.1:8001;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_buffering off;
|
|
}
|
|
```
|