feat: unified station mode with POS + repairs desk view

Phase 1: Station shell
- /station route replaces /pos (with redirect)
- Shared lock screen, activity tracking, auto-lock timer
- Permission-gated tab bar (POS | Repairs | Lessons)
- POSRegister embedded mode (skip lock/timer/topbar)
- Sidebar navigates to /station

Phase 2: Repairs station — front desk
- Status bar with count badges per status group, clickable filters
- Ticket queue panel with search and status filtering
- Ticket detail panel with status progress, notes, photos, line items
- Full stepped intake form: customer → item → problem/estimate → review
- Service template quick-add for line items

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ryan
2026-04-06 01:33:06 +00:00
parent abe75fb1fd
commit 082e388799
15 changed files with 1291 additions and 24 deletions

View File

@@ -0,0 +1,66 @@
import { useState } from 'react'
import { RepairStatusBar } from './repair-status-bar'
import { RepairQueuePanel } from './repair-queue-panel'
import { RepairDetailPanel } from './repair-detail-panel'
import { RepairIntakeForm } from './repair-intake-form'
interface RepairDeskViewProps {
canEdit: boolean
}
export function RepairDeskView({ canEdit }: RepairDeskViewProps) {
const [selectedTicketId, setSelectedTicketId] = useState<string | null>(null)
const [intakeMode, setIntakeMode] = useState(false)
const [statusFilter, setStatusFilter] = useState<string[] | null>(null)
const [activeFilterLabel, setActiveFilterLabel] = useState<string | null>(null)
function handleFilterChange(statuses: string[] | null) {
setStatusFilter(statuses)
// Track which group label is active for the status bar highlight
if (!statuses) {
setActiveFilterLabel(null)
} else {
// Map statuses back to group label
const groups: Record<string, string> = {
new: 'New', in_transit: 'New', intake: 'New',
diagnosing: 'Diagnosing', pending_approval: 'Diagnosing',
approved: 'In Progress', in_progress: 'In Progress', pending_parts: 'In Progress',
ready: 'Ready',
}
setActiveFilterLabel(groups[statuses[0]] ?? null)
}
}
if (intakeMode) {
return (
<div className="flex flex-col h-full">
<RepairIntakeForm
onComplete={(ticketId) => {
setIntakeMode(false)
setSelectedTicketId(ticketId)
}}
onCancel={() => setIntakeMode(false)}
/>
</div>
)
}
return (
<div className="flex flex-col h-full">
<RepairStatusBar activeFilter={activeFilterLabel} onFilterChange={handleFilterChange} />
<div className="flex flex-1 min-h-0">
<div className="w-[35%] border-r border-border overflow-hidden">
<RepairQueuePanel
selectedTicketId={selectedTicketId}
onSelectTicket={setSelectedTicketId}
onNewIntake={() => setIntakeMode(true)}
statusFilter={statusFilter}
/>
</div>
<div className="w-[65%] overflow-hidden">
<RepairDetailPanel ticketId={selectedTicketId} canEdit={canEdit} />
</div>
</div>
</div>
)
}