# RO Details Tab Architecture ## Overview The Repair Orders page has a "RO Details" tab that provides inline editing of a selected repair order. Clicking any RO row navigates to this tab with a full edit form, info bar, and time management. ## Files Involved - `src/pages/RepairOrders.tsx` — Main page with `RODetailsPanel` component, `handleSelectRO`, `handleInlineUpdate`, early-return rendering - `src/components/ROForm.tsx` — Shared RO create/edit form (extracted from ROModal) - `src/components/ROForm.tsx` — `readOnlyDuration` prop controls whether "Estimated Duration" is editable ## Component Architecture ``` RepairOrders (main component) ├── if activeTab === 'details' → early return with RODetailsPanel │ └── RODetailsPanel │ ├── Back button + RO header │ ├── Info bar (Customer Type, Status, Write-up, Promised, Time Remaining + Add Time) │ ├── Message banner (success/error) │ ├── ROForm (inline, readOnlyDuration=true) │ └── AddTimeModal └── else → mainContent (tab bar + table) ├── TabButton (active/all/completed) ├── FragmentRow → onClick → handleSelectRO → sets selectedROId + activeTab='details' └── ROModal (uses ROForm for create/edit) ``` ## State Flow 1. **Row click**: `handleSelectRO(id)` → `setSelectedROId(id)` + `setActiveTab('details')` 2. **Early return**: When `activeTab === 'details' && selectedRO`, the component returns RODetailsPanel directly 3. **Save**: `RODetailsPanel.onSave` → `handleInlineUpdate(roId, data)` → PB update → `fetchOrders()` 4. **Add Time**: `RODetailsPanel.onAddTime` → `handleAddTime(id, minutes)` → PB update + optimistic UI 5. **Back**: Clears `selectedROId`, sets `activeTab='active'` ## Key Details ### `handleInlineUpdate` vs `handleSave` - `handleSave` uses the module-level `editRO` state (for the modal flow) - `handleInlineUpdate(roId, data)` takes the RO id directly (for the details tab flow) - Both map `estimatedDuration` → `estimatedTime` for PocketBase ### `ROForm` — Shared Form Component - Extracted from the old ROModal (was ~600 lines, now ~50) - Takes `editRO`, `existingRoNumbers`, `settings`, `onSave`, `onCancel`, `submitLabel`, `readOnlyDuration` - Manages ALL form state internally (customer info, services, discount, totals, notes) - Used by both `ROModal` (create/edit popup) and `RODetailsPanel` (inline edit) ### `readOnlyDuration` Prop - When `true`: Shows "Original Estimated Duration of Service" as read-only text ("1h 30m") - When `false` (default): Shows editable Hours/Minutes inputs - The underlying `estimatedDuration` state is still populated and included in saves - Helper text: "Add time via the info bar above to extend." ### Info Bar 5-column responsive grid showing: 1. **Customer Type** — Waiter/Drop-Off with icon 2. **Status** — StatusBadge 3. **Write-up Time** — `formatDateTime(ro.writeupTime || ro.created)` 4. **Promised Time** — `formatDueDateTime(ro)` (writeup + duration) 5. **Time Remaining** — Live countdown + urgency colors + "+ Add Time" button - Urgent (past due): red text + AlertTriangle icon - Warning (≤30min): amber text - Completed/delivered: "—" (no button) ### AddTimeModal - Reused from the existing `AddTimeModal` component in RepairOrders.tsx - Props: `open`, `initialMinutes` (15), `onCancel`, `onConfirm(totalMinutes)` - Calls `onAddTime(ro.id, totalMinutes)` which updates `estimatedTime` in PB ## Pitfalls - **Don't use `handleSave` for details tab** — it depends on `editRO` state which is null during details view - **Don't remove the early return pattern** — the details tab renders BEFORE `mainContent`, so both can't be in the same JSX tree - **ROForm manages its own state** — don't try to lift state up; populate happens via the `editRO` prop and `useEffect` - **The `readOnlyDuration` form still includes `estimatedDuration` in save payload** — it's just not editable by the user ## Dirty Tracking (Unsaved Changes Confirmation) ROForm has an `onDirtyChange?: (dirty: boolean) => void` prop. When provided: 1. After form population (from `editRO`), a JSON snapshot of initial values is captured via `useRef` 2. A `useEffect` watches all form state fields and compares to the snapshot 3. On first divergence, `onDirtyChange(true)` fires once (using `dirtyNotified` ref to prevent redundant calls) 4. The snapshot resets when `editRO` changes (navigating to a different RO) In `RODetailsPanel`: - Tracks `isDirty` state via `onDirtyChange={setIsDirty}` - Back button calls `handleBack()` which checks `isDirty` and shows `window.confirm()` if true - On successful save, `setIsDirty(false)` clears the flag ## RO Sort Order The `sortOrders()` function in `RepairOrders.tsx` uses a 4-tier sort: | Tier | Statuses | Secondary Sort | |---|---|---| | 1 | active, in_progress | Waiters first, then by due time (most urgent on top) | | 2 | waiting_pickup | By due time ascending | | 3 | waiting_parts | By due time ascending (absolute bottom) | `completed` and `delivered` are filtered into the "Completed" tab and excluded from active sorts.