update forms

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-05-07 21:02:15 +03:00
co-authored by Copilot
parent dd32658500
commit 38565298fc
32 changed files with 797 additions and 236 deletions
@@ -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>