Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-05-07 13:32:35 +03:00
co-authored by Copilot
parent 4faafe6667
commit c7eb23dd3f
17 changed files with 668 additions and 120 deletions
@@ -11,7 +11,7 @@ import { toast } from "sonner"
import { useAuthApi } from "@/shared/useApi"
const schema = z.object({
title: z.string().min(1, "Title is required"),
name: z.string().min(1, "Name is required"),
})
type FormValues = z.infer<typeof schema>
@@ -21,16 +21,16 @@ export function DocumentTypeInlineForm({ onSuccess }: InlineCreateFormProps) {
const form = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: { title: "" },
defaultValues: { name: "" },
})
const handleSubmit = async (values: FormValues) => {
try {
const result = await api.vehicleDocuments.createDocumentType({ title: values.title } as any)
const result = await api.vehicleDocuments.createDocumentType({ name: values.name } as any)
toast.success("Document type created")
form.reset()
const item = (result as any)?.data ?? result
onSuccess({ value: String(item.id), label: item.title ?? item.name ?? String(item.id) })
onSuccess({ value: String(item.id), label: item.name ?? String(item.id) })
} catch {
toast.error("Failed to create document type")
}
@@ -40,8 +40,8 @@ export function DocumentTypeInlineForm({ onSuccess }: InlineCreateFormProps) {
<Rhform form={form} onSubmit={handleSubmit}>
<FieldGroup>
<RhfTextField
name="title"
label="Title"
name="name"
label="Name"
placeholder="e.g. Registration Certificate"
required
/>
@@ -3,12 +3,16 @@
import { useAuthApi } from "@/shared/useApi"
import { useRouter } from "next/navigation"
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 {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/shared/components/ui/dropdown-menu"
import { useFormDialog } from "@/shared/components/form-dialog"
import { VehicleForm } from "./vehicle-form"
import { Ellipsis, Pencil, Trash2 } from "lucide-react"
type VehicleActionsProps = {
@@ -18,10 +22,7 @@ type VehicleActionsProps = {
export function VehicleActions({ vehicleId }: VehicleActionsProps) {
const api = useAuthApi()
const router = useRouter()
const handleEdit = () => {
router.push(`/sales/vehicles/${vehicleId}/edit`)
}
const editDialog = useFormDialog("vehicle-details-edit")
const handleDelete = async () => {
await api.vehicles.destroy(vehicleId)
@@ -36,7 +37,7 @@ export function VehicleActions({ vehicleId }: VehicleActionsProps) {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={handleEdit}>
<DropdownMenuItem onSelect={() => editDialog.open(vehicleId)}>
<Pencil className="size-4" />
Edit
</DropdownMenuItem>
@@ -45,6 +46,23 @@ export function VehicleActions({ vehicleId }: VehicleActionsProps) {
Delete
</DropdownMenuItem>
</DropdownMenuContent>
<Dialog open={editDialog.isOpen} onOpenChange={(isOpen) => { if (!isOpen) editDialog.close() }}>
<DialogContent className="min-w-xl lg:min-w-4xl">
<DialogHeader>
<DialogTitle className="text-2xl font-bold">Vehicle</DialogTitle>
</DialogHeader>
<ScrollArea className="max-h-[80vh] px-4">
<VehicleForm
resourceId={editDialog.resourceId}
onSuccess={() => {
editDialog.close()
router.refresh()
}}
/>
</ScrollArea>
</DialogContent>
</Dialog>
</DropdownMenu>
)
}
@@ -7,7 +7,8 @@ import { Alert, AlertTitle } from "@/shared/components/ui/alert"
import { FieldGroup } from "@/shared/components/ui/field"
import {
Rhform,
RhfTextareaField,
RhfTextField,
RhfDateField,
RhfAsyncSelectField,
RhfDocumentField,
} from "@/shared/components/form"
@@ -17,6 +18,7 @@ import { useAuthApi } from "@/shared/useApi"
import { useResourceForm } from "@/shared/hooks/use-resource-form"
import { useFormMutation } from "@/shared/hooks/use-form-mutation"
import { toRelation, toId } from "@/shared/lib/utils"
import { getFullName } from "@/shared/utils/getFullName"
import { vehicleDocumentFormSchema, type VehicleDocumentFormValues } from "./vehicle-document.schema"
@@ -33,8 +35,10 @@ export type VehicleDocumentFormProps = {
const DEFAULT_VALUES: VehicleDocumentFormValues = {
document_type: null,
customer: null,
document_number: "",
document_expire: "",
file: null,
note: "",
}
// ── Mapping helpers ──
@@ -51,17 +55,21 @@ function mapToFormValues(data: unknown): VehicleDocumentFormValues {
return {
document_type: toRelation(d.document_type_id, d.document_type?.name ?? d.document_type?.title),
customer: toRelation(d.customer_id, getFullName(d.customer) || d.customer?.name),
document_number: d.document_number || "",
document_expire: d.document_expire || "",
file: null,
note: d.note || "",
}
}
function mapToPayload(values: VehicleDocumentFormValues, vehicleId: string) {
return {
vehicle_id: Number(vehicleId),
vehicle_id: vehicleId,
document_type_id: toId(values.document_type),
file: values.file instanceof File ? values.file : undefined,
note: values.note || undefined,
customer_id: toId(values.customer),
document_number: values.document_number || undefined,
document_expire: values.document_expire || undefined,
document_file: values.file instanceof File ? values.file : undefined,
}
}
@@ -122,18 +130,36 @@ export function VehicleDocumentForm({ vehicleId, resourceId, initialData, onSucc
{...STORE_OBJECT}
/>
{/* <RhfAsyncSelectField
name="customer"
label="Customer"
placeholder="Select customer"
queryKey={["customers"]}
listFn={() => api.customers.list()}
mapOption={(item: any) => ({
value: String(item.id),
label: getFullName(item) || item.name || `#${item.id}`,
})}
{...STORE_OBJECT}
/> */}
<RhfTextField
name="document_number"
label="Document Number"
placeholder="e.g. DOC-001"
/>
<RhfDateField
name="document_expire"
label="Expiry Date"
/>
<RhfDocumentField
name="file"
label="Document File"
required
/>
<RhfTextareaField
name="note"
label="Note"
placeholder="Optional notes about this document"
/>
<Button type="submit" disabled={isPending} className="w-full">
{isPending ? null : isEditing ? <Save /> : <Plus />}
{isPending ? "Saving..." : isEditing ? "Update Document" : "Upload Document"}
@@ -4,8 +4,10 @@ const relationFieldSchema = z.object({ value: z.string(), label: z.string() }).n
export const vehicleDocumentFormSchema = z.object({
document_type: relationFieldSchema,
customer: relationFieldSchema,
document_number: z.string().optional(),
document_expire: z.string().optional(),
file: z.instanceof(File, { message: "File is required" }).nullable(),
note: z.string().optional(),
})
export type VehicleDocumentFormValues = z.infer<typeof vehicleDocumentFormSchema>
@@ -24,6 +24,7 @@ import { useResourceForm } from "@/shared/hooks/use-resource-form"
import { useFormMutation } from "@/shared/hooks/use-form-mutation"
import { toRelation, toId } from "@/shared/lib/utils"
import { formatUppercase } from "@/shared/utils/formatters"
import { VEHICLE_ROUTES } from "@garage/api"
import { vehicleFormSchema, type VehicleFormValues } from "./vehicle.schema"
import { CustomerForm } from "../customers/customer-form"
@@ -125,6 +126,8 @@ export function VehicleForm({ resourceId, initialData, onSuccess }: VehicleFormP
defaultValues: DEFAULT_VALUES,
resourceId,
initialData,
initialize: (id) => api.vehicles.getById(id),
queryKey: [VEHICLE_ROUTES.BY_ID, resourceId],
mapToFormValues,
})