initial commit
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user