216 lines
7.9 KiB
TypeScript
216 lines
7.9 KiB
TypeScript
import {
|
|
ClipboardCheck,
|
|
Calendar,
|
|
Clock,
|
|
Hash,
|
|
FileText,
|
|
User,
|
|
Car,
|
|
Building2,
|
|
FolderOpen,
|
|
} from "lucide-react"
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/shared/components/ui/card"
|
|
import { Badge } from "@/shared/components/ui/badge"
|
|
import { Separator } from "@/shared/components/ui/separator"
|
|
|
|
type InspectionData = {
|
|
id?: number
|
|
title?: string
|
|
customer_id?: number
|
|
vehicle_id?: number
|
|
department_id?: number
|
|
inspection_category_id?: number
|
|
employee_id?: number
|
|
order_number?: string
|
|
date?: string
|
|
time?: string
|
|
note?: string
|
|
status?: string
|
|
created_at?: string
|
|
updated_at?: string
|
|
customer?: { id?: number; first_name?: string; last_name?: string }
|
|
vehicle?: { id?: number; make?: string; model?: string; license_plate?: string }
|
|
department?: { id?: number; name?: string }
|
|
inspection_category?: { id?: number; name?: string }
|
|
employee?: { id?: number; first_name?: string; last_name?: string }
|
|
}
|
|
|
|
type InspectionGeneralInfoProps = {
|
|
inspection: InspectionData
|
|
}
|
|
|
|
function InfoItem({
|
|
icon: Icon,
|
|
label,
|
|
value,
|
|
}: {
|
|
icon: React.ComponentType<{ className?: string }>
|
|
label: string
|
|
value?: string | null
|
|
}) {
|
|
return (
|
|
<div className="flex items-start gap-3">
|
|
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
|
|
<Icon className="size-4" />
|
|
</div>
|
|
<div className="flex flex-col gap-0.5">
|
|
<span className="text-xs text-muted-foreground">{label}</span>
|
|
<span className="text-sm font-medium">
|
|
{value || <span className="text-muted-foreground">—</span>}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const STATUS_COLORS: Record<string, string> = {
|
|
open: "bg-blue-100 text-blue-800",
|
|
in_progress: "bg-yellow-100 text-yellow-800",
|
|
completed: "bg-green-100 text-green-800",
|
|
}
|
|
|
|
function formatStatus(status?: string) {
|
|
if (!status) return "—"
|
|
return status.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
|
|
}
|
|
|
|
export function InspectionGeneralInfo({ inspection }: InspectionGeneralInfoProps) {
|
|
const customerName = inspection.customer
|
|
? `${inspection.customer.first_name ?? ""} ${inspection.customer.last_name ?? ""}`.trim()
|
|
: undefined
|
|
|
|
const vehicleName = inspection.vehicle
|
|
? `${inspection.vehicle.make ?? ""} ${inspection.vehicle.model ?? ""}`.trim()
|
|
: undefined
|
|
|
|
const employeeName = inspection.employee
|
|
? `${inspection.employee.first_name ?? ""} ${inspection.employee.last_name ?? ""}`.trim()
|
|
: undefined
|
|
|
|
return (
|
|
<div className="grid gap-6 md:grid-cols-2">
|
|
{/* Inspection Details */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<ClipboardCheck className="size-4" />
|
|
Inspection Details
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="grid gap-4">
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
{inspection.title && (
|
|
<Badge variant="secondary">{inspection.title}</Badge>
|
|
)}
|
|
{inspection.status && (
|
|
<Badge className={STATUS_COLORS[inspection.status] || ""}>
|
|
{formatStatus(inspection.status)}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
<Separator />
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<InfoItem
|
|
icon={Hash}
|
|
label="Order Number"
|
|
value={inspection.order_number}
|
|
/>
|
|
<InfoItem
|
|
icon={FolderOpen}
|
|
label="Category"
|
|
value={inspection.inspection_category?.name}
|
|
/>
|
|
<InfoItem
|
|
icon={Calendar}
|
|
label="Date"
|
|
value={inspection.date ? new Date(inspection.date).toLocaleDateString() : null}
|
|
/>
|
|
<InfoItem
|
|
icon={Clock}
|
|
label="Time"
|
|
value={inspection.time}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Assignments */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<User className="size-4" />
|
|
Assignments
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="grid gap-4 sm:grid-cols-2">
|
|
<InfoItem
|
|
icon={User}
|
|
label="Customer"
|
|
value={customerName}
|
|
/>
|
|
<InfoItem
|
|
icon={Car}
|
|
label="Vehicle"
|
|
value={vehicleName}
|
|
/>
|
|
<InfoItem
|
|
icon={User}
|
|
label="Employee"
|
|
value={employeeName}
|
|
/>
|
|
<InfoItem
|
|
icon={Building2}
|
|
label="Department"
|
|
value={inspection.department?.name}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Notes & Timestamps */}
|
|
{(inspection.note || inspection.created_at) && (
|
|
<Card className="md:col-span-2">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<FileText className="size-4" />
|
|
Notes & Timestamps
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="grid gap-4">
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<InfoItem
|
|
icon={Calendar}
|
|
label="Created At"
|
|
value={inspection.created_at ? new Date(inspection.created_at).toLocaleString() : null}
|
|
/>
|
|
<InfoItem
|
|
icon={Calendar}
|
|
label="Updated At"
|
|
value={inspection.updated_at ? new Date(inspection.updated_at).toLocaleString() : null}
|
|
/>
|
|
</div>
|
|
{inspection.note && (
|
|
<>
|
|
<Separator />
|
|
<div className="flex items-start gap-3">
|
|
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
|
|
<FileText className="size-4" />
|
|
</div>
|
|
<div className="flex flex-col gap-0.5">
|
|
<span className="text-xs text-muted-foreground">Note</span>
|
|
<p className="text-sm whitespace-pre-wrap">{inspection.note}</p>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|