Files
hermes-config/skills/software-development/pocketbase-react-apps/references/spq-pb-field-mapping.md
T
2026-07-12 10:17:17 -04:00

125 lines
4.4 KiB
Markdown

# 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<string, any> = {};
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`.