finish bills , po , jobcards relational fields creation

This commit is contained in:
2026-04-13 18:48:15 +03:00
parent 3eb471a604
commit 020ffccfd6
41 changed files with 11973 additions and 467 deletions
@@ -4,14 +4,13 @@ import { AlertTriangle, Plus, Save } from "lucide-react"
import { Button } from "@/shared/components/ui/button"
import { Alert, AlertTitle } from "@/shared/components/ui/alert"
import { FieldGroup } from "@/shared/components/ui/field"
import {
Rhform,
RhfTextField,
RhfTextareaField,
RhfCheckboxField,
RhfAsyncSelectField,
RhfAsyncMultiSelectField,
RhfDateField,
RhfAutoGenerateField,
} from "@/shared/components/form"
import { toast } from "sonner"
import { useAuthApi } from "@/shared/useApi"
@@ -23,9 +22,11 @@ import {
estimateFormSchema,
type EstimateFormValues,
} from "./estimate.schema"
import { ESTIMATE_ROUTES, DEPARTMENT_ROUTES, LABEL_ROUTES } from "@garage/api"
import { ESTIMATE_ROUTES, DEPARTMENT_ROUTES } from "@garage/api"
import { RhfCustomerSelectField } from "@/modules/customers/rhf-customer-select-field"
import { RhfVehicleSelectField } from "@/modules/vehicles/rhf-vehicle-select-field"
import { RhfLabelPickerField, type LabelItem } from "@/modules/labels/rhf-label-picker-field"
import { RhfCustomerRemarksField } from "./rhf-customer-remarks-field"
// ── Props ──
@@ -46,7 +47,7 @@ const DEFAULT_VALUES: EstimateFormValues = {
estimate_number: "",
date: "",
has_insurance: false,
remarks: "",
remarks: [],
labels: [],
}
@@ -61,12 +62,16 @@ function mapToFormValues(data: unknown): EstimateFormValues {
vehicle: toRelation(d.vehicle_id, d.vehicle_name),
department: toRelation(d.department_id, d.department_name),
estimate_number: d.estimate_number || "",
date: d.date || "",
date: d.date ? d.date.split("T")[0] : "",
has_insurance: d.has_insurance ?? false,
remarks: Array.isArray(d.remarks) ? d.remarks.join("\n") : d.remarks || "",
labels: Array.isArray(d.labels)
? d.labels.map((l: any) => ({ value: String(l.id), label: l.name }))
remarks: Array.isArray(d.remarks)
? d.remarks.map((r: any) => (typeof r === "string" ? r : (r?.remark ?? ""))).filter(Boolean)
: [],
labels: (d.labels ?? []).map((l: any): LabelItem => ({
id: l.id,
title: l.title ?? l.name ?? "",
color_code: l.color_code ?? "",
})),
}
}
@@ -79,10 +84,8 @@ function mapFormToPayload(values: EstimateFormValues) {
estimate_number: values.estimate_number || undefined,
date: values.date || undefined,
has_insurance: values.has_insurance,
remarks: values.remarks
? values.remarks.split("\n").filter(Boolean)
: [],
label_ids: values.labels.map((l) => Number(l.value)),
remarks: values.remarks?.filter(Boolean) ?? [],
label_ids: values.labels?.map((l) => l.id) ?? [],
}
}
@@ -140,20 +143,17 @@ export function EstimateForm({ resourceId, initialData, onSuccess }: EstimateFor
</Alert>
)}
<FieldGroup>
<RhfTextField name="title" label="Title" placeholder="Estimate title" required />
<div className="space-y-4">
<RhfLabelPickerField name="labels" label="Labels" />
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfTextField name="estimate_number" label="Estimate Number" placeholder="EST-001" />
<RhfTextField name="date" label="Date" type="date" />
</div>
<RhfTextField name="title" label="Title" placeholder="Estimate title" required />
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfCustomerSelectField name="customer" />
<RhfVehicleSelectField name="vehicle" />
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
<RhfAsyncSelectField
name="department"
label="Department"
@@ -163,21 +163,13 @@ export function EstimateForm({ resourceId, initialData, onSuccess }: EstimateFor
mapOption={mapLookupOption}
{...STORE_OBJECT}
/>
<RhfAsyncMultiSelectField
name="labels"
label="Labels"
placeholder="Select labels"
multiple
queryKey={[LABEL_ROUTES.INDEX]}
listFn={() => api.labels.list()}
mapOption={mapLookupOption}
{...STORE_OBJECT}
/>
<RhfDateField name="date" label="Date" />
<RhfAutoGenerateField autoFetch name="estimate_number" label="Estimate#" placeholder="EST-001" table="estimates" />
</div>
<RhfCheckboxField name="has_insurance" label="Has Insurance" />
<RhfTextareaField name="remarks" label="Remarks" placeholder="Enter remarks (one per line)" rows={3} />
<RhfCustomerRemarksField name="remarks" />
<Button type="submit" variant="default" disabled={isPending}>
{isEditing ? <Save /> : <Plus />}
@@ -185,7 +177,7 @@ export function EstimateForm({ resourceId, initialData, onSuccess }: EstimateFor
? (isEditing ? "Updating..." : "Creating...")
: (isEditing ? "Update Estimate" : "Create Estimate")}
</Button>
</FieldGroup>
</div>
</Rhform>
)
}
@@ -17,12 +17,20 @@ const estimateFormSchema = z.object({
estimate_number: z.string().optional(),
date: z.string().optional(),
has_insurance: z.boolean().default(false),
remarks: z.string().optional(),
// ── Multi-select relations ──
// ── Remarks (array of strings) ──
remarks: z.array(z.string()).optional(),
// ── Labels ──
labels: z
.array(z.object({ value: z.string(), label: z.string() }))
.default([]),
.array(
z.object({
id: z.number(),
title: z.string(),
color_code: z.string(),
}),
)
.optional(),
})
type EstimateFormValues = z.infer<typeof estimateFormSchema>