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

4.9 KiB

Customer DB Integration in SPQ-v2 (All Forms → Customers Collection)

Overview

When a shop creates a quote, appointment, or repair order, the customer info should be saved to the customers PocketBase collection and linked via customerId across all record types. This reference documents the full pattern as applied to Quotes, Appointments, and Repair Orders.

Architecture

User types first/middle/last name + phone in form
          ↓
  debounced client-side customer matching (400ms)
          ↓
  Match found?  ─yes─→ Show "Use Existing Customer" banner
          |                   ↓
          no            User clicks "Use"
          |                   ↓
          ↓            Form populated, customerId set
  Auto-create on save
  (ensureCustomerRecord)
          ↓
  customers collection (PB)
          ↓
  customerId on primary record (appointment / RO / quote)

Shared Helper Library

src/lib/customerName.ts provides three reusable functions:

  • composeName(first, middle, last) — joins parts into a single name string
  • parseName(fullName) — splits into { firstName, middleName, lastName } (first-word/last-word heuristic)
  • findMatchingCustomer<T>(customers, query) — generic matcher: phone (exact 3+ digits) → exact full name → partial name. Generic so it preserves the input type.

Type Changes

CustomerFormData (src/components/customers/types.ts):

  • Added firstName, middleName, lastName alongside existing name field
  • The name field is auto-composed from the three parts via composeName()

customerWriteSchema (src/schemas/customer.ts):

  • Added optional firstName, middleName, lastName (defaults to '')

Form-Specific Implementations

AppointmentModal (src/components/appointments/AppointmentModal.tsx)

  • Inputs: 3-column grid for First / Middle / Last Name (middle optional)
  • Customer matching: Debounced 400ms, searches loaded customer list client-side
  • Suggestion banner: Shows "Existing customer found — Name [Use] [Continue as New]"
  • Auto-create: ensureCustomerRecord() on submit — creates customer in PB, sets customerId
  • Linked indicator: Green badge "Linked to customer record: Name"
  • Props: customers: CustomerWithVehicles[] (loaded by parent AppointmentsPage)

ROForm (src/components/ROForm.tsx)

  • Same 3-input layout as AppointmentModal
  • Same matching + suggestion banner + use/dismiss controls
  • Same ensureCustomerRecord() on submit
  • Props: customers?: CustomerWithVehicles[] (optional, defaults to [])
  • Threaded through ROModalRepairOrders.tsx (parent fetches customer list)

Customer Form (CustomerFormModal.tsx)

  • Updated to use first/middle/last name inputs instead of single "Name" field
  • Composes name field internally on save

Quote Form (CustomerInfoPanel in QuoteGenerator)

  • Same 3-name-input layout + customer search at top
  • Duplicate detection via debounce + PB filter
  • Auto-create via ensureCustomerRecord() on save/share

Customer Matching Logic

Implemented in findMatchingCustomer() (customerName.ts):

  1. Phone match (exact): If phone has 3+ digits, find customer whose phone (digits-only) equals query
  2. Exact name match: If first + last name entered, find customer whose name equals composed name
  3. Partial name: Try startsWith on customer name, then includes

Important: Matching is client-side on the loaded customers array — it does NOT use PB ~ filter which can 400 on some collections.

Auto-Create on Save

ensureCustomerRecord() is called before saving any record when no customerId exists:

  1. If customerId already set → return it
  2. Get userId from pb.authStore
  3. Compose name from first/middle/last
  4. Double-check for existing customer (in case one was added since page load)
  5. If found → auto-link (set customerId, return id)
  6. If not found → pb.collection('customers').create({ name, phone, email, ... })
  7. Return new id

CustomerId Propagation

  • Appointment → Check-In: The check-in flow reads appointment.customerId and passes to RO create
  • Quote → RO Conversion: customerId is passed through in QuoteGenerator's RO conversion
  • Customers page detail view: Shows linked ROs by querying customerId or (legacy) customerName

Key Files

File Role
src/lib/customerName.ts Shared helpers (composeName, parseName, findMatchingCustomer)
src/components/appointments/AppointmentModal.tsx First/middle/last inputs + matching + auto-save
src/components/ROForm.tsx Same pattern on RO creation/edit
src/components/customers/CustomerFormModal.tsx Customer CRUD modal (also first/middle/last)
src/components/customers/types.ts CustomerRecord, CustomerWithVehicles, CustomerFormData
src/schemas/customer.ts Zod schema (firstName/middleName/lastName fields)