initial commit

This commit is contained in:
ray
2026-07-12 10:17:17 -04:00
commit dab5a4ebc6
1424 changed files with 330463 additions and 0 deletions
@@ -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.