Files
hermes-config/skills/self-hosting/shop-pro-quote/references/ro-details-tab.md
T
2026-07-12 10:17:17 -04:00

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 with RODetailsPanel component, handleSelectRO, handleInlineUpdate, early-return rendering
  • src/components/ROForm.tsx — Shared RO create/edit form (extracted from ROModal)
  • src/components/ROForm.tsxreadOnlyDuration 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.onSavehandleInlineUpdate(roId, data) → PB update → fetchOrders()
  4. Add Time: RODetailsPanel.onAddTimehandleAddTime(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 estimatedDurationestimatedTime 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 TimeformatDateTime(ro.writeupTime || ro.created)
  4. Promised TimeformatDueDateTime(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.