Files
hermes-config/skills/self-hosting/shop-pro-quote/references/v2-repair-orders-dashboard.md
T
2026-07-12 10:17:17 -04:00

3.9 KiB

v2 RepairOrders Dashboard — Time Management & Interactive Features

File: src/pages/RepairOrders.tsx

Statuses (6 total): active | in_progress | waiting_parts | waiting_pickup | completed | delivered

Time Calculation

calcDueTime(ro)

const baseTime = ro.writeupTime || ro.created;  // fall back to creation timestamp
const duration = ro.estimatedDuration || 60;     // default 1 hour
if (baseTime) return new Date(baseTime).getTime() + duration * 60000;
// fallback: ro.promisedTime (legacy field)

formatTimeRemaining(ro, now)

Returns "2h 15m", "45m", or "-10m" (past due).

  • Completed/delivered → "—"
  • Null due time → "—"

getUrgencyClass(ro, now)

  • Completed/delivered → ''
  • Past due → 'urgent' (red row bg)
  • Within 30 min → 'warning' (amber text)
  • Otherwise → ''

Multi-Tier Sort (sortOrders)

  1. Waiting For Parts → pinned to bottom, sorted by due time ascending within group
  2. Customer type: Waiters above Drop-Offs
  3. Due time: Most urgent (closest to due) first

Adding a New Status

To add a new status (e.g. waiting_pickup), update ALL of these:

  1. types.ts: Add to RepairOrder.status union type
  2. STATUS_CONFIG in RepairOrders.tsx: Add label + Tailwind color classes
  3. ROModal status dropdown: Add <option>
  4. ExpandedDetail action buttons: Add conditional button — guard with ro.status !== 'new_status' && ro.status !== 'completed' && ro.status !== 'delivered'
  5. Existing button guards: Update all other button conditions to exclude the new status where appropriate
  6. Active tab filter (auto): If new_status is not completed or delivered, it's already active
  7. Sort (auto): If not waiting_parts, it sorts normally by customer type → due time

Live Countdown

const [now, setNow] = useState(Date.now());
const tickRef = useRef<ReturnType<typeof setInterval> | null>(null);

useEffect(() => {
  tickRef.current = setInterval(() => setNow(Date.now()), 1000);
  return () => { if (tickRef.current) clearInterval(tickRef.current); };
}, []);

Drives re-render of TimeRemainingCell and urgency styling in the table + expanded detail every second. The sortOrders function is called inside the filtered-orders computation which re-runs on now changes.

Interactive Waiter/Drop-Off Toggle

  • CustomerTypeBadge component: clickable badge that calls onToggleCustomerType
  • Uses window.confirm() for safety ("Are you sure you want to change...")
  • Optimistic update on setOrders, then PB update; rolls back on failure
  • Triggers re-sort automatically (sort re-evaluates customerType)

Add Time Feature

  • "+ Add Time" button appears in ExpandedDetail action bar for non-completed, non-delivered, non-waiting-parts ROs
  • Uses window.prompt('Add extra time (minutes):', '15')
  • Adds to estimatedDuration field in PocketBase
  • Optimistic update via setOrders → updates the countdown, sorting, and urgency instantly

Urgency Styling (table rows)

const rowClass = urgency === 'urgent'
  ? 'bg-red-50 hover:bg-red-100 dark:bg-red-900/15 dark:hover:bg-red-900/25'
  : urgency === 'warning'
    ? 'hover:bg-gray-50 dark:hover:bg-gray-700/50'
    : 'hover:bg-gray-50 dark:hover:bg-gray-700/50';

Past-due expanded details also get bg-red-50 via a className on the <tr>.

Duration Input (Create/Edit Modal)

Two separate inputs — Hours and Minutes:

  • Hours: Math.floor(estimatedDuration / 60)
  • Minutes: estimatedDuration % 60
  • On change: setEstimatedDuration(hours * 60 + Math.min(minutes, 59))

Fields in PocketBase

New fields added to RepairOrders collection (auto-created on first save):

  • customerType: 'waiter' | 'drop-off' (default: 'drop-off')
  • estimatedDuration: number (minutes, default: 60)
  • writeupTime: string (ISO timestamp, set to new Date().toISOString() on create)

Legacy field kept for backward compat:

  • promisedTime?: string — used as fallback in calcDueTime