# 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`.
