Add dev seed data, batch status progress bar, pre-fill account from batch
Dev seed script creates 8 accounts, 8 members, 16 repair templates, 6 repair tickets in various statuses, and a school batch with 5 instruments. Run with bun run db:seed-dev. Batch detail page now has a status progress bar matching the ticket detail pattern. Add Repair from batch pre-fills the account and contact name. New repair form reads accountId and contactName from search params when linked from a batch.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { Check, ClipboardList, Wrench, Package, Truck, Ban } from 'lucide-react'
|
||||
|
||||
const STEPS = [
|
||||
{ key: 'intake', label: 'Intake', icon: ClipboardList },
|
||||
{ key: 'in_progress', label: 'In Progress', icon: Wrench },
|
||||
{ key: 'completed', label: 'Completed', icon: Package },
|
||||
{ key: 'delivered', label: 'Delivered', icon: Truck },
|
||||
] as const
|
||||
|
||||
interface BatchStatusProgressProps {
|
||||
currentStatus: string
|
||||
onStatusClick?: (status: string) => void
|
||||
}
|
||||
|
||||
export function BatchStatusProgress({ currentStatus, onStatusClick }: BatchStatusProgressProps) {
|
||||
const isCancelled = currentStatus === 'cancelled'
|
||||
const currentIdx = STEPS.findIndex((s) => s.key === currentStatus)
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center w-full">
|
||||
{STEPS.map((step, idx) => {
|
||||
const isCompleted = !isCancelled && currentIdx > idx
|
||||
const isCurrent = !isCancelled && currentIdx === idx
|
||||
const isFuture = isCancelled || currentIdx < idx
|
||||
|
||||
return (
|
||||
<div key={step.key} className="flex items-center flex-1 last:flex-none">
|
||||
<button
|
||||
type="button"
|
||||
disabled={!onStatusClick || isCancelled}
|
||||
onClick={() => onStatusClick?.(step.key)}
|
||||
className={`relative flex flex-col items-center gap-1 group ${onStatusClick && !isCancelled ? 'cursor-pointer' : 'cursor-default'}`}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-center h-9 w-9 rounded-full border-2 transition-colors
|
||||
${isCompleted ? 'bg-primary border-primary text-primary-foreground' : ''}
|
||||
${isCurrent ? 'border-primary bg-primary/10 text-primary ring-2 ring-primary/30' : ''}
|
||||
${isFuture ? 'border-muted-foreground/30 text-muted-foreground/40' : ''}
|
||||
${isCancelled ? 'border-destructive/30 text-destructive/40' : ''}
|
||||
${onStatusClick && !isCancelled ? 'group-hover:border-primary group-hover:text-primary' : ''}
|
||||
`}
|
||||
>
|
||||
{isCompleted ? <Check className="h-4 w-4" /> : <step.icon className="h-4 w-4" />}
|
||||
</div>
|
||||
<span
|
||||
className={`text-[10px] font-medium text-center leading-tight max-w-[70px]
|
||||
${isCompleted ? 'text-primary' : ''}
|
||||
${isCurrent ? 'text-primary font-semibold' : ''}
|
||||
${isFuture ? 'text-muted-foreground/50' : ''}
|
||||
${isCancelled ? 'text-destructive/50' : ''}
|
||||
`}
|
||||
>
|
||||
{step.label}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{idx < STEPS.length - 1 && (
|
||||
<div
|
||||
className={`flex-1 h-0.5 mx-1 mt-[-18px]
|
||||
${!isCancelled && currentIdx > idx ? 'bg-primary' : 'bg-muted-foreground/20'}
|
||||
${isCancelled ? 'bg-destructive/20' : ''}
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{isCancelled && (
|
||||
<div className="flex items-center gap-2 pl-4">
|
||||
<Ban className="h-4 w-4 text-destructive" />
|
||||
<span className="text-sm font-medium text-destructive">Cancelled</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { Badge } from '@/components/ui/badge'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { ArrowLeft, Check, X, Plus, FileText, Download } from 'lucide-react'
|
||||
import { BatchStatusProgress } from '@/components/repairs/batch-status-progress'
|
||||
import { toast } from 'sonner'
|
||||
import { useAuthStore } from '@/stores/auth.store'
|
||||
import jsPDF from 'jspdf'
|
||||
@@ -99,8 +100,8 @@ function RepairBatchDetailPage() {
|
||||
}
|
||||
|
||||
function handleAddRepair() {
|
||||
// Navigate to new repair with batch pre-linked
|
||||
navigate({ to: '/repairs/new', search: { batchId, batchName: batch!.batchNumber ?? '' } as any })
|
||||
// Navigate to new repair with batch and account pre-linked
|
||||
navigate({ to: '/repairs/new', search: { batchId, batchName: batch!.batchNumber ?? '', accountId: batch!.accountId, contactName: batch!.contactName ?? '' } as any })
|
||||
}
|
||||
|
||||
async function generateBatchPdf() {
|
||||
@@ -248,6 +249,16 @@ function RepairBatchDetailPage() {
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Status Progress Bar */}
|
||||
<Card>
|
||||
<CardContent className="pt-6 pb-4">
|
||||
<BatchStatusProgress
|
||||
currentStatus={batch.status}
|
||||
onStatusClick={hasPermission('repairs.edit') ? (status) => statusMutation.mutate(status) : undefined}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{hasPermission('repairs.admin') && batch.approvalStatus === 'pending' && (
|
||||
|
||||
@@ -32,6 +32,8 @@ export const Route = createFileRoute('/_authenticated/repairs/new')({
|
||||
validateSearch: (search: Record<string, unknown>) => ({
|
||||
batchId: (search.batchId as string) || undefined,
|
||||
batchName: (search.batchName as string) || undefined,
|
||||
accountId: (search.accountId as string) || undefined,
|
||||
contactName: (search.contactName as string) || undefined,
|
||||
}),
|
||||
component: NewRepairPage,
|
||||
})
|
||||
@@ -42,6 +44,8 @@ function NewRepairPage() {
|
||||
const search = Route.useSearch()
|
||||
const linkedBatchId = search.batchId
|
||||
const linkedBatchName = search.batchName
|
||||
const linkedAccountId = search.accountId
|
||||
const linkedContactName = search.contactName
|
||||
|
||||
// Account search
|
||||
const [accountSearch, setAccountSearch] = useState('')
|
||||
@@ -82,14 +86,14 @@ function NewRepairPage() {
|
||||
} = useForm({
|
||||
resolver: zodResolver(RepairTicketCreateSchema),
|
||||
defaultValues: {
|
||||
customerName: '',
|
||||
customerName: linkedContactName ?? '',
|
||||
customerPhone: '',
|
||||
instrumentDescription: '',
|
||||
serialNumber: '',
|
||||
problemDescription: '',
|
||||
conditionIn: undefined,
|
||||
estimatedCost: undefined,
|
||||
accountId: undefined,
|
||||
accountId: linkedAccountId ?? undefined,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user