@@ -60,6 +60,7 @@ const DEFAULT_VALUES: JobCardFormValues = {
|
||||
title: "",
|
||||
customer: null,
|
||||
vehicle: null,
|
||||
estimate: null,
|
||||
department: null,
|
||||
service_writer: null,
|
||||
primary_technician: null,
|
||||
@@ -73,6 +74,7 @@ const DEFAULT_VALUES: JobCardFormValues = {
|
||||
estimate_to: "Customer",
|
||||
tax_inclusive: "Tax Inclusive",
|
||||
discount_type: "no",
|
||||
discount_amount: undefined,
|
||||
discount_at: "inclusive_of_tax",
|
||||
order_date: new Date().toISOString().split("T")[0],
|
||||
check_in_date: "",
|
||||
@@ -88,7 +90,11 @@ const DEFAULT_VALUES: JobCardFormValues = {
|
||||
enable_parts_issuing: false,
|
||||
enable_digital_authorisation: false,
|
||||
footer: "",
|
||||
attachments: "",
|
||||
customer_remarks: [],
|
||||
documents: [],
|
||||
attachment_files: [],
|
||||
label_ids: [],
|
||||
labels: [],
|
||||
}
|
||||
|
||||
@@ -111,6 +117,7 @@ function mapToFormValues(data: unknown): JobCardFormValues {
|
||||
title: d.title || "",
|
||||
customer: toRelation(d.customer_id, d.customer ? `${d.customer.first_name} ${d.customer.last_name}` : undefined),
|
||||
vehicle: toRelation(d.vehicle_id, d.vehicle ? (d.vehicle.plate_number ?? `${d.vehicle.make ?? ""} ${d.vehicle.model ?? ""}`.trim()) : undefined),
|
||||
estimate: toRelation(d.estimate_id, d.estimate?.estimate_number),
|
||||
department: toRelation(d.department_id, d.department?.name),
|
||||
service_writer: toRelation(d.service_writer_id, d.service_writer ? `${d.service_writer.first_name} ${d.service_writer.last_name}` : undefined),
|
||||
primary_technician: toRelation(d.primary_technician_id, d.primary_technician ? `${d.primary_technician.first_name} ${d.primary_technician.last_name}` : undefined),
|
||||
@@ -124,6 +131,7 @@ function mapToFormValues(data: unknown): JobCardFormValues {
|
||||
estimate_to: d.estimate_to || "Customer",
|
||||
tax_inclusive: d.tax_inclusive || "Tax Inclusive",
|
||||
discount_type: d.discount_type || "no",
|
||||
discount_amount: d.discount_amount != null ? Number(d.discount_amount) : undefined,
|
||||
discount_at: d.discount_at || "inclusive_of_tax",
|
||||
order_date: mapDate(d.order_date),
|
||||
check_in_date: mapDate(d.check_in_date),
|
||||
@@ -138,11 +146,19 @@ function mapToFormValues(data: unknown): JobCardFormValues {
|
||||
enable_parts_issuing: d.enable_parts_issuing ?? false,
|
||||
enable_digital_authorisation: d.enable_digital_authorisation ?? false,
|
||||
footer: d.footer || "",
|
||||
attachments: d.attachments || "",
|
||||
customer_remarks: Array.isArray(d.customer_remarks)
|
||||
? d.customer_remarks.map((r: any) =>
|
||||
typeof r === "string" ? r : (r?.remark ?? "")
|
||||
).filter(Boolean)
|
||||
: [],
|
||||
documents: Array.isArray(d.documents) ? d.documents : [],
|
||||
attachment_files: Array.isArray(d.attachment_files) ? d.attachment_files : [],
|
||||
label_ids: Array.isArray(d.label_ids)
|
||||
? d.label_ids
|
||||
.map((id: any) => Number(id))
|
||||
.filter((id: number) => Number.isFinite(id))
|
||||
: [],
|
||||
labels: (d.labels ?? []).map((l: any) => ({
|
||||
id: l.id,
|
||||
title: l.title,
|
||||
@@ -156,6 +172,7 @@ function mapFormToPayload(values: JobCardFormValues) {
|
||||
title: values.title,
|
||||
customer_id: toId(values.customer),
|
||||
vehicle_id: toId(values.vehicle),
|
||||
estimate_id: toId(values.estimate) ?? undefined,
|
||||
department_id: toId(values.department),
|
||||
service_writer_id: toId(values.service_writer),
|
||||
primary_technician_id: toId(values.primary_technician),
|
||||
@@ -169,6 +186,7 @@ function mapFormToPayload(values: JobCardFormValues) {
|
||||
estimate_to: values.estimate_to || undefined,
|
||||
tax_inclusive: values.tax_inclusive || undefined,
|
||||
discount_type: values.discount_type || undefined,
|
||||
discount_amount: values.discount_amount,
|
||||
discount_at: values.discount_at || undefined,
|
||||
order_date: values.order_date || undefined,
|
||||
check_in_date: values.check_in_date || undefined,
|
||||
@@ -183,8 +201,11 @@ function mapFormToPayload(values: JobCardFormValues) {
|
||||
enable_parts_issuing: values.enable_parts_issuing,
|
||||
enable_digital_authorisation: values.enable_digital_authorisation,
|
||||
footer: values.footer || undefined,
|
||||
attachments: values.attachments || undefined,
|
||||
customer_remarks: values.customer_remarks?.filter(Boolean) ?? [],
|
||||
label_ids: values.labels?.map((l) => l.id),
|
||||
documents: values.documents,
|
||||
attachment_files: values.attachment_files,
|
||||
label_ids: values.labels?.map((l) => l.id) ?? values.label_ids,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,8 +296,8 @@ export function JobCardForm({ resourceId, initialData, onSuccess }: JobCardFormP
|
||||
<RhfTextField name="title" label="Title" placeholder="Job Card 001" required />
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<RhfCustomerSelectField name="customer" />
|
||||
<RhfVehicleSelectField name="vehicle" disabled={!customer?.value} customer_id={customer?.value} />
|
||||
<RhfCustomerSelectField required name="customer" />
|
||||
<RhfVehicleSelectField required name="vehicle" disabled={!customer?.value} customer_id={customer?.value} />
|
||||
</div>
|
||||
|
||||
<RhfCheckboxField name="has_insurance" label="Has Insurance Work?" />
|
||||
|
||||
@@ -12,6 +12,19 @@ const relationFieldSchema = z
|
||||
.object({ value: z.string(), label: z.string() })
|
||||
.nullable()
|
||||
|
||||
const optionalStringMax255 = z.string().max(255).optional()
|
||||
const optionalTimeString = z.string().max(50).optional()
|
||||
|
||||
const documentSchema = z.object({
|
||||
document_type_id: z.coerce.number().int().optional(),
|
||||
customer_id: z.coerce.number().int().optional(),
|
||||
vehicle_id: z.coerce.number().int().optional(),
|
||||
document_number: z.string().optional(),
|
||||
show_in_invoice: z.boolean().optional(),
|
||||
show_in_estimate: z.boolean().optional(),
|
||||
show_in_statement: z.boolean().optional(),
|
||||
})
|
||||
|
||||
// ── Job Card Statuses ──
|
||||
|
||||
export const JOB_CARD_STATUS_OPTIONS = JobCardStatus.map((v) => ({
|
||||
@@ -48,11 +61,12 @@ const FUEL_LEVEL_OPTIONS = FuelLevel.map((v) => ({
|
||||
|
||||
const jobCardFormSchema = z.object({
|
||||
// ── Required fields ──
|
||||
title: z.string().min(1, "Title is required"),
|
||||
title: z.string().min(1, "Title is required").max(255, "Title must be at most 255 characters"),
|
||||
|
||||
// ── Relations ──
|
||||
customer: relationFieldSchema,
|
||||
vehicle: relationFieldSchema,
|
||||
estimate: relationFieldSchema.optional(),
|
||||
department: relationFieldSchema,
|
||||
service_writer: relationFieldSchema,
|
||||
primary_technician: relationFieldSchema,
|
||||
@@ -62,28 +76,29 @@ const jobCardFormSchema = z.object({
|
||||
tax: relationFieldSchema,
|
||||
|
||||
// ── Numbers & identifiers ──
|
||||
order_number: z.string().optional(),
|
||||
estimate_number: z.string().optional(),
|
||||
order_number: optionalStringMax255,
|
||||
estimate_number: optionalStringMax255,
|
||||
|
||||
// ── Status & settings ──
|
||||
status: z.string().optional(),
|
||||
estimate_to: z.string().optional(),
|
||||
tax_inclusive: z.string().optional(),
|
||||
discount_type: z.string().optional(),
|
||||
discount_at: z.string().optional(),
|
||||
status: z.enum(JobCardStatus).optional(),
|
||||
estimate_to: z.enum(EstimateTo).optional(),
|
||||
tax_inclusive: z.enum(TaxInclusive).optional(),
|
||||
discount_type: z.enum(DiscountType).optional(),
|
||||
discount_amount: z.coerce.number().min(0).optional(),
|
||||
discount_at: z.enum(DiscountAt).optional(),
|
||||
|
||||
// ── Dates & times ──
|
||||
order_date: z.string().optional(),
|
||||
check_in_date: z.string().optional(),
|
||||
check_in_time: z.string().optional(),
|
||||
check_in_time: optionalTimeString,
|
||||
start_date: z.string().optional(),
|
||||
start_time: z.string().optional(),
|
||||
start_time: optionalTimeString,
|
||||
delivery_date: z.string().optional(),
|
||||
delivery_time: z.string().optional(),
|
||||
delivery_time: optionalTimeString,
|
||||
|
||||
// ── Vehicle state ──
|
||||
km_in: z.string().optional(),
|
||||
fuel_level: z.string().optional(),
|
||||
km_in: z.union([z.string(), z.number()]).optional(),
|
||||
fuel_level: optionalTimeString,
|
||||
|
||||
// ── Boolean options ──
|
||||
has_insurance: z.boolean().optional(),
|
||||
@@ -92,9 +107,12 @@ const jobCardFormSchema = z.object({
|
||||
|
||||
// ── Notes ──
|
||||
footer: z.string().optional(),
|
||||
attachments: z.string().optional(),
|
||||
|
||||
// ── Customer Remarks ──
|
||||
customer_remarks: z.array(z.string()).optional(),
|
||||
documents: z.array(documentSchema).optional(),
|
||||
attachment_files: z.array(z.unknown()).max(20, "Attachment files cannot exceed 20 files").optional(),
|
||||
|
||||
// ── Labels ──
|
||||
labels: z
|
||||
@@ -106,6 +124,23 @@ const jobCardFormSchema = z.object({
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
label_ids: z.array(z.coerce.number().int()).optional(),
|
||||
}).superRefine((values, ctx) => {
|
||||
if (!values.customer) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["customer"],
|
||||
message: "Customer is required",
|
||||
})
|
||||
}
|
||||
|
||||
if (!values.vehicle) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["vehicle"],
|
||||
message: "Vehicle is required",
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
type JobCardFormValues = z.infer<typeof jobCardFormSchema>
|
||||
|
||||
Reference in New Issue
Block a user