humam kerdiah 4bfd8c84a9 feat: add template checkpoint edit dialog and vendor management components
- 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.
2026-05-18 12:08:42 +04:00

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))}
/>
),
},
]}
/>
)
}