updates
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
564e9e510f
commit
65964605e1
@ -6,6 +6,7 @@ import { CreateInvoiceFromEstimateButton } from '@/modules/estimates/create-invo
|
|||||||
import { CreateJobCardFromEstimateButton } from '@/modules/estimates/create-job-card-from-estimate-button'
|
import { CreateJobCardFromEstimateButton } from '@/modules/estimates/create-job-card-from-estimate-button'
|
||||||
import { FileTextIcon } from 'lucide-react'
|
import { FileTextIcon } from 'lucide-react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { formatDate } from '@/shared/utils/formatters'
|
||||||
|
|
||||||
export default async function layout(props: {
|
export default async function layout(props: {
|
||||||
params: Promise<{ id: string }>
|
params: Promise<{ id: string }>
|
||||||
@ -21,14 +22,18 @@ export default async function layout(props: {
|
|||||||
? `${estimateData.estimate_number}${estimateData.title ? ` — ${estimateData.title}` : ''}`
|
? `${estimateData.estimate_number}${estimateData.title ? ` — ${estimateData.title}` : ''}`
|
||||||
: title
|
: title
|
||||||
|
|
||||||
|
if (!estimateData) {
|
||||||
|
return <div className="text-muted-foreground p-4">Estimate not found.</div>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EstimateProvider estimate={{ id, label: estimateLabel, data: estimateData }}>
|
<EstimateProvider estimate={estimateData}>
|
||||||
<DashboardDetailsPage
|
<DashboardDetailsPage
|
||||||
className="p-0 lg:p-0"
|
className="p-0 lg:p-0"
|
||||||
icon={<FileTextIcon className="size-5" />}
|
icon={<FileTextIcon className="size-5" />}
|
||||||
title={title}
|
title={title}
|
||||||
description={
|
description={
|
||||||
estimateData?.date ? `Date: ${estimateData.date}` : undefined
|
estimateData?.date ? `Date: ${formatDate(estimateData.date)}` : undefined
|
||||||
}
|
}
|
||||||
backHref="/sales/estimates"
|
backHref="/sales/estimates"
|
||||||
actions={
|
actions={
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { EstimateGeneralInfo } from '@/modules/estimates/estimate-general-info'
|
|||||||
import { EstimateServicesSection } from '@/modules/estimates/estimate-services-section'
|
import { EstimateServicesSection } from '@/modules/estimates/estimate-services-section'
|
||||||
import { EstimatePartsSection } from '@/modules/estimates/estimate-parts-section'
|
import { EstimatePartsSection } from '@/modules/estimates/estimate-parts-section'
|
||||||
import { EstimateExpenseItemsSection } from '@/modules/estimates/estimate-expense-items-section'
|
import { EstimateExpenseItemsSection } from '@/modules/estimates/estimate-expense-items-section'
|
||||||
|
import { EstimateTotalsSummary } from '@/modules/estimates/estimate-totals-summary'
|
||||||
import DashboardPage from '@/base/components/layout/dashboard/dashboard-page'
|
import DashboardPage from '@/base/components/layout/dashboard/dashboard-page'
|
||||||
|
|
||||||
export default async function page(props: { params: Promise<{ id: string }> }) {
|
export default async function page(props: { params: Promise<{ id: string }> }) {
|
||||||
@ -23,6 +24,7 @@ export default async function page(props: { params: Promise<{ id: string }> }) {
|
|||||||
<EstimateServicesSection estimateId={id} />
|
<EstimateServicesSection estimateId={id} />
|
||||||
<EstimatePartsSection estimateId={id} />
|
<EstimatePartsSection estimateId={id} />
|
||||||
<EstimateExpenseItemsSection estimateId={id} />
|
<EstimateExpenseItemsSection estimateId={id} />
|
||||||
|
<EstimateTotalsSummary />
|
||||||
</div>
|
</div>
|
||||||
</DashboardPage>
|
</DashboardPage>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import type { EstimatesClient } from '@garage/api'
|
|||||||
import { Car, FileTextIcon } from 'lucide-react'
|
import { Car, FileTextIcon } from 'lucide-react'
|
||||||
import { Button } from '@/shared/components/ui/button'
|
import { Button } from '@/shared/components/ui/button'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
import { formatDate } from '@/shared/utils/formatters'
|
||||||
|
|
||||||
export default function EstimatesPage() {
|
export default function EstimatesPage() {
|
||||||
return (
|
return (
|
||||||
@ -67,6 +68,10 @@ export default function EstimatesPage() {
|
|||||||
{
|
{
|
||||||
accessorKey: "date",
|
accessorKey: "date",
|
||||||
header: ({ column }) => <ColumnHeader column={column} title="Date" />,
|
header: ({ column }) => <ColumnHeader column={column} title="Date" />,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const item = row.original
|
||||||
|
return formatDate(item.date)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "has_insurance",
|
accessorKey: "has_insurance",
|
||||||
|
|||||||
@ -3,9 +3,16 @@
|
|||||||
import { createContext, useContext } from "react"
|
import { createContext, useContext } from "react"
|
||||||
|
|
||||||
export type EstimateContextValue = {
|
export type EstimateContextValue = {
|
||||||
id: string
|
id?: number | string
|
||||||
label: string
|
title?: string | null
|
||||||
data?: Record<string, any>
|
estimate_number?: string | null
|
||||||
|
discount?: string | null
|
||||||
|
sub_total?: number | string | null
|
||||||
|
total?: number | string | null
|
||||||
|
estimate_parts?: { quantity?: string | number; rate?: string | number }[]
|
||||||
|
estimate_services?: { quantity?: string | number; rate?: string | number }[]
|
||||||
|
estimate_expense_items?: { quantity?: string | number; rate?: string | number }[]
|
||||||
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
const EstimateContext = createContext<EstimateContextValue | null>(null)
|
const EstimateContext = createContext<EstimateContextValue | null>(null)
|
||||||
|
|||||||
@ -84,6 +84,18 @@ function InfoItem({
|
|||||||
export function EstimateGeneralInfo({ estimate }: EstimateGeneralInfoProps) {
|
export function EstimateGeneralInfo({ estimate }: EstimateGeneralInfoProps) {
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-6 md:grid-cols-2">
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
|
<div className="col-span-2">
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{estimate?.labels?.map((label) => (
|
||||||
|
<Badge
|
||||||
|
key={label.id}
|
||||||
|
style={label.color_code ? { backgroundColor: label.color_code } : undefined}
|
||||||
|
>
|
||||||
|
{label.title}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
@ -136,7 +148,7 @@ export function EstimateGeneralInfo({ estimate }: EstimateGeneralInfoProps) {
|
|||||||
label="Vehicle"
|
label="Vehicle"
|
||||||
value={
|
value={
|
||||||
estimate.vehicle
|
estimate.vehicle
|
||||||
? `${estimate.vehicle.make ?? ""} ${estimate.vehicle.model ?? ""} (${estimate.vehicle.registration_number ?? ""})`.trim()
|
? `${estimate.vehicle.make ?? ""} ${estimate.vehicle.model ?? ""} ${(estimate as any).vehicle.year ?? ''}`.trim()
|
||||||
: estimate.vehicle_id ? `#${estimate.vehicle_id}` : null
|
: estimate.vehicle_id ? `#${estimate.vehicle_id}` : null
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -162,27 +174,7 @@ export function EstimateGeneralInfo({ estimate }: EstimateGeneralInfoProps) {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{estimate.labels && estimate.labels.length > 0 && (
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Tag className="size-4" /> Labels
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{estimate.labels.map((label) => (
|
|
||||||
<Badge
|
|
||||||
key={label.id}
|
|
||||||
style={label.color_code ? { backgroundColor: label.color_code } : undefined}
|
|
||||||
>
|
|
||||||
{label.title}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{estimate.customer_remarks && estimate.customer_remarks.length > 0 && (
|
{estimate.customer_remarks && estimate.customer_remarks.length > 0 && (
|
||||||
<Card>
|
<Card>
|
||||||
|
|||||||
83
apps/dashboard/modules/estimates/estimate-totals-summary.tsx
Normal file
83
apps/dashboard/modules/estimates/estimate-totals-summary.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Card, CardContent } from "@/shared/components/ui/card"
|
||||||
|
import { Separator } from "@/shared/components/ui/separator"
|
||||||
|
import { formatCurrency, formatEnum } from "@/shared/utils/formatters"
|
||||||
|
import { useEstimate } from "./estimate-context"
|
||||||
|
|
||||||
|
export function EstimateTotalsSummary() {
|
||||||
|
const estimate = useEstimate()
|
||||||
|
|
||||||
|
if (!estimate) return null
|
||||||
|
|
||||||
|
const parts = estimate.estimate_parts ?? []
|
||||||
|
const services = estimate.estimate_services ?? []
|
||||||
|
const expenses = estimate.estimate_expense_items ?? []
|
||||||
|
const discount = estimate.discount
|
||||||
|
const displayTotal = parseFloat(String(estimate.total ?? 0)) || 0
|
||||||
|
|
||||||
|
const hasItems = parts.length > 0 || services.length > 0 || expenses.length > 0
|
||||||
|
|
||||||
|
if (!hasItems && displayTotal === 0) return null
|
||||||
|
|
||||||
|
const subTotal = parseFloat(String(estimate.sub_total ?? 0)) || 0
|
||||||
|
|
||||||
|
function lineTotal(items: { quantity?: string | number; rate?: string | number }[]) {
|
||||||
|
return items.reduce((sum, item) => {
|
||||||
|
const qty = parseFloat(String(item.quantity ?? 0))
|
||||||
|
const rate = parseFloat(String(item.rate ?? 0))
|
||||||
|
return sum + (isNaN(qty) || isNaN(rate) ? 0 : qty * rate)
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardContent className="pt-6">
|
||||||
|
<div className="ml-auto max-w-sm space-y-2">
|
||||||
|
{hasItems && (
|
||||||
|
<>
|
||||||
|
{parts.length > 0 && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Parts ({parts.length})</span>
|
||||||
|
<span>{formatCurrency(lineTotal(parts))}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{services.length > 0 && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Services ({services.length})</span>
|
||||||
|
<span>{formatCurrency(lineTotal(services))}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{expenses.length > 0 && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Expenses ({expenses.length})</span>
|
||||||
|
<span>{formatCurrency(lineTotal(expenses))}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Separator />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex justify-between text-sm font-medium">
|
||||||
|
<span>Subtotal</span>
|
||||||
|
<span>{formatCurrency(subTotal)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{discount && discount !== "no" && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Discount ({formatEnum(discount)})</span>
|
||||||
|
<span className="text-muted-foreground">Applied</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex justify-between text-base font-semibold">
|
||||||
|
<span>Total</span>
|
||||||
|
<span>{formatCurrency(displayTotal)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -72,6 +72,7 @@ function VehicleCombobox({
|
|||||||
</FieldLabel>
|
</FieldLabel>
|
||||||
<div ref={anchorRef}>
|
<div ref={anchorRef}>
|
||||||
<Combobox
|
<Combobox
|
||||||
|
|
||||||
value={inputText || null}
|
value={inputText || null}
|
||||||
onValueChange={(val) => {
|
onValueChange={(val) => {
|
||||||
const str = val !== null ? String(val) : ""
|
const str = val !== null ? String(val) : ""
|
||||||
@ -91,29 +92,20 @@ function VehicleCombobox({
|
|||||||
showClear={!!inputText}
|
showClear={!!inputText}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
aria-invalid={!!error || undefined}
|
aria-invalid={!!error || undefined}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
|
{(loading || filtered.length > 0) && (
|
||||||
<ComboboxContent anchor={anchorRef}>
|
<ComboboxContent anchor={anchorRef}>
|
||||||
<ComboboxList>
|
<ComboboxList>
|
||||||
{loading && (
|
|
||||||
<div className="flex items-center justify-center py-4">
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!loading &&
|
{!loading &&
|
||||||
filtered.map((opt) => (
|
filtered.map((opt) => (
|
||||||
<ComboboxItem key={opt} value={opt}>
|
<ComboboxItem key={opt} value={opt}>
|
||||||
{opt}
|
{opt}
|
||||||
</ComboboxItem>
|
</ComboboxItem>
|
||||||
))}
|
))}
|
||||||
{!loading && filtered.length === 0 && (
|
|
||||||
<ComboboxEmpty>
|
|
||||||
{inputText
|
|
||||||
? `No suggestions for "${inputText}"`
|
|
||||||
: "Start typing to see suggestions"}
|
|
||||||
</ComboboxEmpty>
|
|
||||||
)}
|
|
||||||
</ComboboxList>
|
</ComboboxList>
|
||||||
</ComboboxContent>
|
</ComboboxContent>
|
||||||
|
)}
|
||||||
</Combobox>
|
</Combobox>
|
||||||
</div>
|
</div>
|
||||||
{error && <FieldError>{error}</FieldError>}
|
{error && <FieldError>{error}</FieldError>}
|
||||||
@ -145,6 +137,8 @@ export function RhfVehicleIdentityField() {
|
|||||||
const modelCtrl = useController({ name: "model", control })
|
const modelCtrl = useController({ name: "model", control })
|
||||||
const yearCtrl = useController({ name: "year", control })
|
const yearCtrl = useController({ name: "year", control })
|
||||||
const subModelCtrl = useController({ name: "sub_model", control })
|
const subModelCtrl = useController({ name: "sub_model", control })
|
||||||
|
const engineSizeCtrl = useController({ name: "engine_size", control })
|
||||||
|
const drivetrainCtrl = useController({ name: "drivetrain", control })
|
||||||
|
|
||||||
const makeValue = makeCtrl.field.value ?? ""
|
const makeValue = makeCtrl.field.value ?? ""
|
||||||
const modelValue = modelCtrl.field.value ?? ""
|
const modelValue = modelCtrl.field.value ?? ""
|
||||||
@ -160,6 +154,8 @@ export function RhfVehicleIdentityField() {
|
|||||||
model?: string
|
model?: string
|
||||||
year?: string
|
year?: string
|
||||||
sub_model?: string
|
sub_model?: string
|
||||||
|
engine_size?: string
|
||||||
|
drivetrain?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractRecords(response: unknown): MakeAndModelRecord[] {
|
function extractRecords(response: unknown): MakeAndModelRecord[] {
|
||||||
@ -205,10 +201,34 @@ export function RhfVehicleIdentityField() {
|
|||||||
.map((r) => r.sub_model),
|
.map((r) => r.sub_model),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const engineSizes = unique(
|
||||||
|
allRecords
|
||||||
|
.filter(
|
||||||
|
(r) =>
|
||||||
|
(!makeValue || r.make === makeValue) &&
|
||||||
|
(!modelValue || r.model === modelValue) &&
|
||||||
|
(!yearValue || r.year === yearValue),
|
||||||
|
)
|
||||||
|
.map((r) => r.engine_size),
|
||||||
|
)
|
||||||
|
|
||||||
|
const drivetrains = unique(
|
||||||
|
allRecords
|
||||||
|
.filter(
|
||||||
|
(r) =>
|
||||||
|
(!makeValue || r.make === makeValue) &&
|
||||||
|
(!modelValue || r.model === modelValue) &&
|
||||||
|
(!yearValue || r.year === yearValue),
|
||||||
|
)
|
||||||
|
.map((r) => r.drivetrain),
|
||||||
|
)
|
||||||
|
|
||||||
const makesLoading = recordsLoading
|
const makesLoading = recordsLoading
|
||||||
const modelsLoading = false
|
const modelsLoading = false
|
||||||
const yearsLoading = false
|
const yearsLoading = false
|
||||||
const subModelsLoading = false
|
const subModelsLoading = false
|
||||||
|
const engineSizesLoading = false
|
||||||
|
const drivetrainsLoading = false
|
||||||
|
|
||||||
// ── Cascading onChange handlers ──
|
// ── Cascading onChange handlers ──
|
||||||
|
|
||||||
@ -218,17 +238,23 @@ export function RhfVehicleIdentityField() {
|
|||||||
setValue("model", "")
|
setValue("model", "")
|
||||||
setValue("year", "")
|
setValue("year", "")
|
||||||
setValue("sub_model", "")
|
setValue("sub_model", "")
|
||||||
|
setValue("engine_size", "")
|
||||||
|
setValue("drivetrain", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleModelChange(val: string) {
|
function handleModelChange(val: string) {
|
||||||
modelCtrl.field.onChange(val)
|
modelCtrl.field.onChange(val)
|
||||||
setValue("year", "")
|
setValue("year", "")
|
||||||
setValue("sub_model", "")
|
setValue("sub_model", "")
|
||||||
|
setValue("engine_size", "")
|
||||||
|
setValue("drivetrain", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleYearChange(val: string) {
|
function handleYearChange(val: string) {
|
||||||
yearCtrl.field.onChange(val)
|
yearCtrl.field.onChange(val)
|
||||||
setValue("sub_model", "")
|
setValue("sub_model", "")
|
||||||
|
setValue("engine_size", "")
|
||||||
|
setValue("drivetrain", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -269,6 +295,8 @@ export function RhfVehicleIdentityField() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||||
<VehicleCombobox
|
<VehicleCombobox
|
||||||
label="Sub Model"
|
label="Sub Model"
|
||||||
placeholder="e.g. LE"
|
placeholder="e.g. LE"
|
||||||
@ -279,6 +307,27 @@ export function RhfVehicleIdentityField() {
|
|||||||
loading={subModelsLoading}
|
loading={subModelsLoading}
|
||||||
error={subModelCtrl.fieldState.error?.message}
|
error={subModelCtrl.fieldState.error?.message}
|
||||||
/>
|
/>
|
||||||
|
<VehicleCombobox
|
||||||
|
label="Engine Size"
|
||||||
|
placeholder="e.g. 2.5L"
|
||||||
|
value={engineSizeCtrl.field.value ?? ""}
|
||||||
|
onChange={(val) => engineSizeCtrl.field.onChange(val)}
|
||||||
|
onBlur={engineSizeCtrl.field.onBlur}
|
||||||
|
options={engineSizes}
|
||||||
|
loading={engineSizesLoading}
|
||||||
|
error={engineSizeCtrl.fieldState.error?.message}
|
||||||
|
/>
|
||||||
|
<VehicleCombobox
|
||||||
|
label="Drivetrain"
|
||||||
|
placeholder="e.g. FWD"
|
||||||
|
value={drivetrainCtrl.field.value ?? ""}
|
||||||
|
onChange={(val) => drivetrainCtrl.field.onChange(val)}
|
||||||
|
onBlur={drivetrainCtrl.field.onBlur}
|
||||||
|
options={drivetrains}
|
||||||
|
loading={drivetrainsLoading}
|
||||||
|
error={drivetrainCtrl.fieldState.error?.message}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,6 @@ const DEFAULT_VALUES: VehicleFormValues = {
|
|||||||
engine_size: "",
|
engine_size: "",
|
||||||
drivetrain: "",
|
drivetrain: "",
|
||||||
mileage: "",
|
mileage: "",
|
||||||
owners_number: "",
|
|
||||||
note: "",
|
note: "",
|
||||||
image: null,
|
image: null,
|
||||||
}
|
}
|
||||||
@ -84,7 +83,6 @@ function mapToFormValues(data: unknown): VehicleFormValues {
|
|||||||
engine_size: d.engine_size || "",
|
engine_size: d.engine_size || "",
|
||||||
drivetrain: d.drivetrain || "",
|
drivetrain: d.drivetrain || "",
|
||||||
mileage: d.mileage || "",
|
mileage: d.mileage || "",
|
||||||
owners_number: d.owners_number || "",
|
|
||||||
note: d.note || "",
|
note: d.note || "",
|
||||||
image: null,
|
image: null,
|
||||||
}
|
}
|
||||||
@ -106,7 +104,6 @@ function mapToPayload(values: VehicleFormValues) {
|
|||||||
engine_size: values.engine_size || undefined,
|
engine_size: values.engine_size || undefined,
|
||||||
drivetrain: values.drivetrain || undefined,
|
drivetrain: values.drivetrain || undefined,
|
||||||
mileage: values.mileage || undefined,
|
mileage: values.mileage || undefined,
|
||||||
owners_number: values.owners_number || undefined,
|
|
||||||
note: values.note || undefined,
|
note: values.note || undefined,
|
||||||
image: values.image instanceof File ? values.image : undefined,
|
image: values.image instanceof File ? values.image : undefined,
|
||||||
}
|
}
|
||||||
@ -226,14 +223,7 @@ export function VehicleForm({ resourceId, initialData, onSuccess }: VehicleFormP
|
|||||||
<RhfTextField name="vin_number" label="VIN Number" placeholder="e.g. 1HGBH41JXMN109186" />
|
<RhfTextField name="vin_number" label="VIN Number" placeholder="e.g. 1HGBH41JXMN109186" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Technical specs */}
|
{/* License & identifiers */}
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
||||||
<RhfTextField name="engine_size" label="Engine Size" placeholder="e.g. 2.5L" />
|
|
||||||
<RhfTextField name="drivetrain" label="Drivetrain" placeholder="e.g. FWD" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<RhfTextField name="owners_number" label="Number of Owners" placeholder="e.g. 1" />
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<RhfTextField name="license_plate" label="License Plate" placeholder="e.g. ABC-123" />
|
<RhfTextField name="license_plate" label="License Plate" placeholder="e.g. ABC-123" />
|
||||||
<RhfTextField name="mileage" label="Mileage" placeholder="e.g. 10000" />
|
<RhfTextField name="mileage" label="Mileage" placeholder="e.g. 10000" />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user