5.1 KiB
5.1 KiB
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 withRODetailsPanelcomponent,handleSelectRO,handleInlineUpdate, early-return renderingsrc/components/ROForm.tsx— Shared RO create/edit form (extracted from ROModal)src/components/ROForm.tsx—readOnlyDurationprop 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
- Row click:
handleSelectRO(id)→setSelectedROId(id)+setActiveTab('details') - Early return: When
activeTab === 'details' && selectedRO, the component returns RODetailsPanel directly - Save:
RODetailsPanel.onSave→handleInlineUpdate(roId, data)→ PB update →fetchOrders() - Add Time:
RODetailsPanel.onAddTime→handleAddTime(id, minutes)→ PB update + optimistic UI - Back: Clears
selectedROId, setsactiveTab='active'
Key Details
handleInlineUpdate vs handleSave
handleSaveuses the module-leveleditROstate (for the modal flow)handleInlineUpdate(roId, data)takes the RO id directly (for the details tab flow)- Both map
estimatedDuration→estimatedTimefor 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) andRODetailsPanel(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
estimatedDurationstate 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:
- Customer Type — Waiter/Drop-Off with icon
- Status — StatusBadge
- Write-up Time —
formatDateTime(ro.writeupTime || ro.created) - Promised Time —
formatDueDateTime(ro)(writeup + duration) - 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
AddTimeModalcomponent in RepairOrders.tsx - Props:
open,initialMinutes(15),onCancel,onConfirm(totalMinutes) - Calls
onAddTime(ro.id, totalMinutes)which updatesestimatedTimein PB
Pitfalls
- Don't use
handleSavefor details tab — it depends oneditROstate 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
editROprop anduseEffect - The
readOnlyDurationform still includesestimatedDurationin 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:
- After form population (from
editRO), a JSON snapshot of initial values is captured viauseRef - A
useEffectwatches all form state fields and compares to the snapshot - On first divergence,
onDirtyChange(true)fires once (usingdirtyNotifiedref to prevent redundant calls) - The snapshot resets when
editROchanges (navigating to a different RO)
In RODetailsPanel:
- Tracks
isDirtystate viaonDirtyChange={setIsDirty} - Back button calls
handleBack()which checksisDirtyand showswindow.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.