# SPQ PocketBase Field Mapping — Frontend ↔ DB ## Critical: Frontend Field Names ≠ PB Column Names The `repairOrders` collection schema has different field names than what the TypeScript types use. Saving to the wrong name causes **silent data loss** — PB accepts unknown fields through the API but does NOT persist them. ### Frontend → PB Translation Table | TypeScript (types.ts) | PB Column | PB Type | Translation | |---|---|---|---| | `estimatedDuration: number` (minutes) | `estimatedTime` | TEXT | `(estimatedDuration / 60).toFixed(1)` → stores hours string like `"1.5"` | | `customerType: 'waiter' \| 'drop-off'` | N/A — no column | — | Store in `financial` JSON blob: `JSON.stringify({ ...existingFinancial, customerType })` | | `writeupTime: string` (ISO) | `writeupTime` | TEXT | Direct match ✓ | | `promisedTime: string` (ISO) | `promisedTime` | TEXT | Direct match ✓ | ### Normalize on Load In `fetchOrders` normalization, convert PB fields back to frontend types: ```typescript // PB returns estimatedTime as hours string → convert to minutes number let estimatedDuration: number | undefined; if (item.estimatedTime && item.estimatedTime !== '') { const hours = parseFloat(item.estimatedTime); if (!isNaN(hours)) estimatedDuration = Math.round(hours * 60); } // PB returns financial JSON blob → extract customerType let customerType: CustomerType | undefined; if (item.financial) { try { const fin = typeof item.financial === 'string' ? JSON.parse(item.financial) : item.financial; if (fin && (fin.customerType === 'waiter' || fin.customerType === 'drop-off')) { customerType = fin.customerType; } } catch {} } return { ...item, services, estimatedDuration, customerType }; ``` ### Translate on Save In `handleSave`, transform before sending to PB: ```typescript const { estimatedDuration, customerType, ...rest } = data; const payload = { ...rest, userId, services: JSON.parse(JSON.stringify(data.services || [])), estimatedTime: estimatedDuration != null ? (estimatedDuration / 60).toFixed(1) : '', }; // Store customerType in financial JSON if (customerType) { try { const existing = typeof rest.financial === 'string' ? JSON.parse(rest.financial) : (rest.financial || {}); payload.financial = JSON.stringify({ ...existing, customerType }); } catch { payload.financial = JSON.stringify({ customerType }); } } ``` ### For Add-Time / Inline Saves When updating just the estimated duration inline (like the "+ Add Time" feature), save directly to `estimatedTime`: ```typescript await pb.collection('repairOrders').update(id, { estimatedTime: (newDuration / 60).toFixed(1), }); ``` ### For Customer-Type Toggle Inline When toggling waiter/drop-off inline, update the `financial` JSON blob: ```typescript const ro = orders.find((o) => o.id === id); let financial: Record = {}; if (ro?.financial) { try { financial = typeof ro.financial === 'string' ? JSON.parse(ro.financial) : ro.financial; } catch {} } financial.customerType = newType; await pb.collection('repairOrders').update(id, { financial: JSON.stringify(financial), }); ``` ## RO Status Values (React App) | TS Value | PB Value | Display | Badge | |---|---|---|---| | `active` | `active` | Active | Blue | | `in_progress` | `in_progress` | In Progress | Yellow | | `waiting_parts` | `waiting_parts` | Waiting Parts | Orange | | `waiting_pickup` | `waiting_pickup` | Waiting Pick-up | Teal | | `completed` | `completed` | Completed | Green | | `delivered` | `delivered` | Delivered | Gray | ## Appointment to Repair Order (Check-In) When converting an appointment into an RO, the `handleCheckInSubmit` maps fields like this: | Appointment field | RO field | |---|---| | `customerName` | `customerName` | | `customerPhone` | `customerPhone` | | `customerEmail` | `customerEmail` | | `vehicleInfo` | `vehicleInfo` | | `advisorName` | `advisorName` | | `notes` | `notes` (mapped from "customer concerns" input) | | — | `vin` (prompted in check-in form) | | — | `mileage` (prompted in check-in form) | | — | `writeupTime` = `new Date().toISOString()` (auto-stamped) | | — | `estimatedTime` = `(estimatedDuration / 60).toFixed(1)` (from hours+minutes input) | | — | `financial` = `{ customerType }` (from radio toggle) | | — | `status` = `'active'` (initial) | | — | `roNumber` = auto-generated (e.g., `RO-20260629-0842`) | After RO creation, appointment status changes to `checked_in`.