Files
2026-07-12 10:17:17 -04:00

4.0 KiB

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:

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;
}