# Split/Full Pricing Toggle ## Where It Lives Three separate components — each has its own copy: | Location | Component | File | |----------|-----------|------| | Quote Generator | `ServiceRow` (memo) | `src/components/quoteGenerator/ServiceRow.tsx` | | RO Create/Edit | `ServiceRow` (inline) | `src/components/ROForm.tsx` | | RO Detail Modal | `EditableServiceRow` (inline) | `src/pages/RODetailModal.tsx` | All three share the same toggle pattern. When adding the toggle to a new service row, copy the pattern from any existing one. ## Type Fields Added to `Service` / `QuoteService` / `ROService` in `src/types.ts`: ```ts pricingMode?: 'full' | 'split'; laborHours?: number; laborRate?: number; partsCost?: number; ``` `ROService` already had `pricingMode` and split fields. `QuoteService` extended `Service` to add them. ## Toggle Pattern A pill-button group rendered in the row header: ```tsx
``` ### State Derivation ```ts const isSplit = service.pricingMode === 'split'; // default Full ``` When `pricingMode` is undefined (existing data without the field), it defaults to **Full**. ### Mode Switch Behavior | Switch | What Happens | |--------|-------------| | Full → Split | Derives `laborHours = flatten Total / laborRate` from the flat price. Sets `laborRate = defaultLaborRate`, `partsCost = 0` | | Split → Full | Just sets `pricingMode = 'full'`. Split field values persist in state but are hidden | ### Full Mode Display - Grid collapses to 2 columns: **Name** + **Total Price** (editable input for `service.total`) ### Split Mode Display - Grid shows 5 columns: **Name**, **Labor Hrs**, **Rate/hr**, **Parts $**, **Total** (read-only, calculated) ## Total Calculation `roServiceTotal()` in `src/lib/totals.ts`: ```ts export function roServiceTotal(s): number { const split = (s.laborHours || 0) * (s.laborRate || 0) + (s.partsCost || 0); return split || s.total || 0; } ``` For Full mode: hours=0 → split=0 → falls back to `s.total`. **Every call site must pass `total: s.total || 0`** to `roServiceTotal` — otherwise Full-mode services compute to $0. ## Where Total Must Be Passed Search the codebase for `roServiceTotal` call sites and ensure `total` is in the argument: - `ROForm.tsx` ServiceRow render — the `total` const used for display - `ROForm.tsx` `computeROTotals` mapper - `ROForm.tsx` `handleSubmit` cleanedServices mapper - `RODetailModal.tsx` EditableServiceRow render - `RODetailModal.tsx` service cleanup on save ## Initial Defaults Every place that creates a new service must set `pricingMode: 'full'`: | Location | File | Line | |----------|------|------| | ROForm edit path fallback | `ROForm.tsx` | ~385 | | ROForm create path | `ROForm.tsx` | ~413 | | ROForm handleAddService | `ROForm.tsx` | ~553 | | RODetailModal handleAddService | `RODetailModal.tsx` | ~822 | | RODetailModal createEmptyServiceForm | `RODetailModal.tsx` | 57-67 (already 'full') | | QuoteGenerator ServiceSearch handleAdd | `ServiceSearch.tsx` | 56 (addService passes defaults) | ## Persistence When saving services back to PocketBase, `pricingMode` must be included in the cleaned service object: ```ts pricingMode: (s.pricingMode || 'full') as 'full' | 'split', ``` This is in: - `ROForm.tsx` handleSubmit cleanedServices - `RODetailModal.tsx` service save handler