- Implemented TemplateCheckpointEditDialog for creating and editing inspection checkpoints. - Added VendorActions component for managing vendor actions including edit, activate/deactivate, and delete. - Created VendorContext for managing vendor state across components. - Developed VendorGeneralInfo component to display detailed vendor information. - Introduced AedSymbol and Money components for consistent currency representation. - Added PromptDialog for user input prompts throughout the application. - Implemented RelationLink component for unified related-data display in CRUD tables. - Created InspectionTemplatesClient for API interactions related to inspection templates.
179 lines
8.1 KiB
TypeScript
179 lines
8.1 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { ResourcePage } from "@/shared/data-view/resource-page"
|
|
import { ColumnHeader } from "@/shared/data-view/table-view"
|
|
import FormDialog from "@/shared/components/form-dialog"
|
|
import { InspectionForm } from "@/modules/inspections/inspection-form"
|
|
import { InspectionFromTemplateForm } from "@/modules/inspections/inspection-from-template-form"
|
|
import { InspectionRowActions } from "@/modules/inspections/inspection-row-actions"
|
|
import { Button } from "@/shared/components/ui/button"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/shared/components/ui/dialog"
|
|
import { ScrollArea } from "@/shared/components/ui/scroll-area"
|
|
import { INSPECTION_ROUTES, InspectionStatus } from "@garage/api"
|
|
import type { InspectionsClient } from "@garage/api"
|
|
import Link from "next/link"
|
|
import { Car, ClipboardList, ListChecks, UserIcon } from "lucide-react"
|
|
import { useRouter } from "next/navigation"
|
|
import { RelationLink } from "@/shared/components/relation-link"
|
|
import { getFullName } from "@/shared/utils/getFullName"
|
|
import { getVehicleLabel } from "@/modules/vehicles/utils/getVehicleLabel"
|
|
|
|
const STATUS_BADGE_CLASS: Record<string, string> = {
|
|
in_progress: "bg-amber-100 text-amber-800",
|
|
completed: "bg-emerald-100 text-emerald-800",
|
|
cancelled: "bg-rose-100 text-rose-800",
|
|
}
|
|
|
|
const STATUS_LABEL: Record<string, string> = {
|
|
in_progress: "In Progress",
|
|
completed: "Completed",
|
|
cancelled: "Cancelled",
|
|
}
|
|
|
|
export default function InspectionsPage() {
|
|
const router = useRouter()
|
|
const [fromTemplateOpen, setFromTemplateOpen] = useState(false)
|
|
|
|
return (
|
|
<ResourcePage<InspectionsClient>
|
|
pageTitle="Inspections"
|
|
routeKey={INSPECTION_ROUTES.INDEX}
|
|
searchable
|
|
searchPlaceholder="Search inspections..."
|
|
statusFilter={{ statuses: InspectionStatus }}
|
|
getClient={(api) => api.inspections}
|
|
onRowClick={(row) => router.push(`/sales/inspections/${(row as any).id}`)}
|
|
headerProps={({ selectedItem, invalidateQuery }) => ({
|
|
actions: (
|
|
<div className="flex items-center gap-2">
|
|
<Button variant="outline" size="sm" asChild>
|
|
<Link href="/settings/inspection-templates">
|
|
<ListChecks className="size-4" />
|
|
Manage templates
|
|
</Link>
|
|
</Button>
|
|
<Button size="sm" variant="default" onClick={() => setFromTemplateOpen(true)}>
|
|
<ClipboardList className="size-4" />
|
|
New from template
|
|
</Button>
|
|
<FormDialog title="Inspection" classNames={{ trigger: "" }}>
|
|
{(resourceId) => (
|
|
<InspectionForm
|
|
resourceId={resourceId}
|
|
initialData={selectedItem}
|
|
onSuccess={invalidateQuery}
|
|
/>
|
|
)}
|
|
</FormDialog>
|
|
<Dialog open={fromTemplateOpen} onOpenChange={setFromTemplateOpen}>
|
|
<DialogContent className="min-w-2xl">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-2xl font-bold">
|
|
New inspection from template
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
<ScrollArea className="max-h-[80vh] px-4">
|
|
<InspectionFromTemplateForm
|
|
onSuccess={(id) => {
|
|
setFromTemplateOpen(false)
|
|
invalidateQuery()
|
|
if (id) router.push(`/sales/inspections/${id}/checkpoints`)
|
|
}}
|
|
/>
|
|
</ScrollArea>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
),
|
|
})}
|
|
columns={({ openEdit, deleteItem }) => [
|
|
{
|
|
accessorKey: "title",
|
|
header: ({ column }) => <ColumnHeader column={column} title="Title" />,
|
|
cell: ({ row }) => {
|
|
const r = row.original as any
|
|
return (
|
|
<div className="flex flex-col">
|
|
<span className="font-medium">{r.title ?? "—"}</span>
|
|
{r.order_number && (
|
|
<span className="text-xs text-muted-foreground">{r.order_number}</span>
|
|
)}
|
|
</div>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "customer",
|
|
header: ({ column }) => <ColumnHeader column={column} title="Customer" />,
|
|
cell: ({ row }) => {
|
|
const c = (row.original as any).customer
|
|
return (
|
|
<RelationLink
|
|
href={c?.id ? `/sales/customers/${c.id}` : null}
|
|
icon={UserIcon}
|
|
label={getFullName(c)}
|
|
/>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "vehicle",
|
|
header: ({ column }) => <ColumnHeader column={column} title="Vehicle" />,
|
|
cell: ({ row }) => {
|
|
const v = (row.original as any).vehicle
|
|
return (
|
|
<RelationLink
|
|
href={v?.id ? `/sales/vehicles/${v.id}` : null}
|
|
icon={Car}
|
|
label={getVehicleLabel(v as any)}
|
|
meta={v?.license_plate}
|
|
/>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "inspection_category",
|
|
header: ({ column }) => <ColumnHeader column={column} title="Category" />,
|
|
cell: ({ row }) => {
|
|
const ic = (row.original as any).inspection_category
|
|
return ic?.inspection_name ?? ic?.name ?? "—"
|
|
},
|
|
},
|
|
{
|
|
accessorKey: "status",
|
|
header: ({ column }) => <ColumnHeader column={column} title="Status" />,
|
|
cell: ({ row }) => {
|
|
const status = (row.original as any).status as string | undefined
|
|
if (!status) return "—"
|
|
const cls = STATUS_BADGE_CLASS[status] ?? "bg-gray-100 text-gray-700"
|
|
return (
|
|
<span className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${cls}`}>
|
|
{STATUS_LABEL[status] ?? status}
|
|
</span>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
id: "actions",
|
|
header: () => <span className="sr-only">Actions</span>,
|
|
enableSorting: false,
|
|
enableHiding: false,
|
|
cell: ({ row }) => (
|
|
<InspectionRowActions
|
|
inspection={row.original as any}
|
|
onEdit={() => openEdit(row.original as any)}
|
|
onDelete={() => deleteItem(String((row.original as any).id))}
|
|
/>
|
|
),
|
|
},
|
|
]}
|
|
/>
|
|
)
|
|
}
|