Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Mohammad Khyata 2026-05-07 11:30:23 +03:00
parent cdd1cbc31a
commit 1fda8d8d7b
5 changed files with 65 additions and 11 deletions

View File

@ -3,11 +3,13 @@
import { useRouter } from 'next/navigation'
import { ResourcePage } from '@/shared/data-view/resource-page'
import { ColumnHeader } from '@/shared/data-view/table-view'
import type { ColumnDef } from '@tanstack/react-table'
import FormDialog from '@/shared/components/form-dialog'
import { ImportDataButton } from '@/shared/components/import-data-button'
import { ExportDataButton } from '@/shared/components/export-data-button'
import { DownloadSampleButton } from '@/shared/components/download-sample-button'
import { useAuthApi } from '@/shared/useApi'
import type { CrudResourceColumnHelpers, ResourceItem } from '@/shared/data-view/resource-page'
import { VehicleForm } from '@/modules/vehicles/vehicle-form'
import { VEHICLE_ROUTES } from '@garage/api'
import type { VehiclesClient } from '@garage/api'
@ -49,9 +51,11 @@ export default function VehiclesPage() {
</div>
),
})}
columns={({ actionsColumn })=> [
{
accessorKey: "name",
id: "name",
header: ({ column }) => <ColumnHeader column={column} title="Vehicle" />,
cell: ({ row }) => {
const r = row.original as any
@ -117,6 +121,14 @@ export default function VehiclesPage() {
return val ? new Date(val).toLocaleDateString() : "—"
},
},
{
accessorKey: "customer",
header: ({ column }) => <ColumnHeader column={column} title="Customer" />,
cell: ({ row }) => {
const val = (row.original as any).customer
return val ? val.name : "—"
},
},
actionsColumn(),
]}
/>

View File

@ -252,7 +252,7 @@ export function JobCardForm({ resourceId, initialData, onSuccess }: JobCardFormP
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfCustomerSelectField name="customer" />
<RhfVehicleSelectField name="vehicle" customer_id={customer?.value} />
<RhfVehicleSelectField name="vehicle" disabled={!customer?.value} customer_id={customer?.value} />
</div>
<RhfCheckboxField name="has_insurance" label="Has Insurance Work?" />

View File

@ -18,8 +18,14 @@ import type { VehicleFormValues } from "./vehicle.schema"
// ── Helpers ──
function unique(values: (string | undefined)[]): string[] {
return [...new Set(values.filter((v): v is string => !!v))].sort()
function toOptionalString(value: unknown): string | undefined {
if (typeof value === "string") return value || undefined
if (typeof value === "number") return String(value)
return undefined
}
function unique(values: unknown[]): string[] {
return [...new Set(values.map(toOptionalString).filter((v): v is string => !!v))].sort()
}
// ── Internal combobox for a single free-text + suggestion field ──
@ -60,8 +66,9 @@ function VehicleCombobox({
}, [value])
// Client-side filtering of suggestions based on what's been typed
const normalizedInput = inputText.toLowerCase()
const filtered = options.filter((opt) =>
!inputText || opt.toLowerCase().includes(inputText.toLowerCase()),
!normalizedInput || opt.toLowerCase().includes(normalizedInput),
)
return (
@ -130,7 +137,7 @@ export function RhfVehicleIdentityField() {
const { control, setValue } = useFormContext<VehicleFormValues>()
// Read shop_type to pass as shop_type_id filter to the API
const shopType = useWatch({ control, name: "shop_type" })
const shopType = useWatch({ control, name: "shop_type_id" })
const shopTypeId = shopType?.value
const makeCtrl = useController({ name: "make", control })
@ -158,11 +165,26 @@ export function RhfVehicleIdentityField() {
drivetrain?: string
}
function normalizeRecord(record: unknown): MakeAndModelRecord {
const value = record as Record<string, unknown>
return {
id: typeof value.id === "number" ? value.id : undefined,
shop_type_id: typeof value.shop_type_id === "number" ? value.shop_type_id : undefined,
make: toOptionalString(value.make),
model: toOptionalString(value.model),
year: toOptionalString(value.year),
sub_model: toOptionalString(value.sub_model),
engine_size: toOptionalString(value.engine_size),
drivetrain: toOptionalString(value.drivetrain),
}
}
function extractRecords(response: unknown): MakeAndModelRecord[] {
const obj = response as any
if (Array.isArray(obj?.data)) return obj.data
if (Array.isArray(obj?.data?.data)) return obj.data.data
if (Array.isArray(obj)) return obj
if (Array.isArray(obj?.data)) return obj.data.map(normalizeRecord)
if (Array.isArray(obj?.data?.data)) return obj.data.data.map(normalizeRecord)
if (Array.isArray(obj)) return obj.map(normalizeRecord)
return []
}

View File

@ -26,6 +26,8 @@ import { toRelation, toId } from "@/shared/lib/utils"
import { formatUppercase } from "@/shared/utils/formatters"
import { vehicleFormSchema, type VehicleFormValues } from "./vehicle.schema"
import { CustomerForm } from "../customers/customer-form"
import { getFullName } from "@/shared/utils/getFullName"
// ── Props ──
@ -43,6 +45,7 @@ const DEFAULT_VALUES: VehicleFormValues = {
vehicle_fuel_type_id: null,
vehicle_transmission_id: null,
vehicle_color_id: null,
customer_id: null,
make: "",
model: "",
year: "",
@ -74,6 +77,7 @@ function mapToFormValues(data: unknown): VehicleFormValues {
vehicle_fuel_type_id: toRelation(d.vehicle_fuel_type_id, d.vehicle_fuel_type?.title),
vehicle_transmission_id: toRelation(d.vehicle_transmission_id, d.vehicle_transmission?.title),
vehicle_color_id: toRelation(d.vehicle_color_id, d.vehicle_color?.title),
customer_id: toRelation(d.customer_id, d.customer?.name),
make: d.make || "",
model: d.model || "",
year: d.year || "",
@ -95,6 +99,8 @@ function mapToPayload(values: VehicleFormValues) {
vehicle_fuel_type_id: toId(values.vehicle_fuel_type_id),
vehicle_transmission_id: toId(values.vehicle_transmission_id),
vehicle_color_id: toId(values.vehicle_color_id),
customer_id: toId(values.customer_id),
make: values.make,
model: values.model,
year: values.year,
@ -222,6 +228,20 @@ export function VehicleForm({ resourceId, initialData, onSuccess }: VehicleFormP
/>
<RhfTextField name="vin_number" label="VIN Number" placeholder="e.g. 1HGBH41JXMN109186" />
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfAsyncSelectField
name="customer_id"
label="Customer"
placeholder="Select customer"
queryKey={["customers"]}
listFn={() => api.customers.list()}
mapOption={(op:any)=> ({ value: String(op.id), label: getFullName(op) })}
createForm={(props) => <CustomerForm {...props} />}
createLabel="Customer"
{...STORE_OBJECT}
/>
</div>
{/* License & identifiers */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">

View File

@ -11,7 +11,7 @@ export const vehicleFormSchema = z.object({
vehicle_fuel_type_id: relationFieldSchema,
vehicle_transmission_id: relationFieldSchema,
vehicle_color_id: relationFieldSchema,
customer_id: relationFieldSchema,
// ── Vehicle identity ──
make: z.string().optional(),
model: z.string().optional(),