refactor: enhance employee and service forms with new fields and validation
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -26,7 +26,8 @@ import {
|
||||
type PaymentMadeFormValues,
|
||||
} from "./payment-made.schema"
|
||||
import {
|
||||
PAYMENT_MADE_ROUTES,
|
||||
ApiError,
|
||||
BILL_ROUTES,
|
||||
PAYMENT_MODE_ROUTES,
|
||||
EMPLOYEE_ROUTES,
|
||||
PaymentFor,
|
||||
@@ -53,6 +54,7 @@ export type PaymentMadeFormProps = {
|
||||
// ── Default values ──
|
||||
|
||||
const DEFAULT_VALUES: PaymentMadeFormValues & { details: Array<{ bill_id?: string | number | null; amount_paid?: number, expense_id?: string | number | null }> } = {
|
||||
bill: null,
|
||||
vendor: null,
|
||||
employee: null,
|
||||
payment_mode: null,
|
||||
@@ -76,6 +78,7 @@ function mapToFormValues(data: unknown): typeof DEFAULT_VALUES {
|
||||
const paymentModeLabel = d.payment_mode?.title ?? d.payment_mode?.name ?? d.payment_mode_name
|
||||
|
||||
return {
|
||||
bill: toRelation(d.bill_id, d.bill?.bill_number ?? d.bill_number),
|
||||
vendor: toRelation(d.vendor_id, d.vendor?.company_name ?? d.vendor?.name ?? d.vendor_name),
|
||||
employee: toRelation(d.employee_id, d.employee?.first_name ? `${d.employee.first_name} ${d.employee.last_name ?? ""}`.trim() : d.employee_name),
|
||||
payment_mode: toRelation(paymentModeId, paymentModeLabel),
|
||||
@@ -92,10 +95,13 @@ function mapToFormValues(data: unknown): typeof DEFAULT_VALUES {
|
||||
|
||||
|
||||
function mapFormToPayload(values: PaymentMadeFormValues, billId?: string | number | null, expenseId?: string | number | null) {
|
||||
const resolvedBillId = billId ?? toId(values.bill)
|
||||
const paymentDetails =
|
||||
expenseId ? [{ expense_id: expenseId, amount_paid: values.amount || 0 }]
|
||||
: [{ bill_id: billId, amount_paid: values.amount || 0 }]
|
||||
values.payment_for === "expense"
|
||||
? [{ expense_id: expenseId, amount_paid: values.amount || 0 }]
|
||||
: [{ bill_id: resolvedBillId, amount_paid: values.amount || 0 }]
|
||||
return {
|
||||
bill_id: values.payment_for === "bill" ? Number(resolvedBillId) : undefined,
|
||||
vendor_id: toId(values.vendor),
|
||||
employee_id: toId(values.employee) || undefined,
|
||||
payment_mode_id: toId(values.payment_mode),
|
||||
@@ -108,7 +114,6 @@ function mapFormToPayload(values: PaymentMadeFormValues, billId?: string | numbe
|
||||
notes: values.notes || undefined,
|
||||
payment_made: values.amount || 0,
|
||||
details: paymentDetails,
|
||||
...(billId ? { bill_id: Number(billId) } : {}),
|
||||
...(expenseId ? { expense_id: Number(expenseId) } : {}),
|
||||
}
|
||||
}
|
||||
@@ -120,6 +125,11 @@ const mapLookupOption = (item: any) => ({
|
||||
label: item.name ?? item.title ?? `#${item.id}`,
|
||||
})
|
||||
|
||||
const mapBillOption = (item: any) => ({
|
||||
value: String(item.id),
|
||||
label: item.bill_number ?? item.title ?? `#${item.id}`,
|
||||
})
|
||||
|
||||
const mapVendorOption = (item: any) => ({
|
||||
value: String(item.id),
|
||||
label: item.name ?? item.company_name ?? `#${item.id}`,
|
||||
@@ -139,6 +149,13 @@ const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) =
|
||||
export function PaymentMadeForm({ resourceId, initialData, onSuccess, billId, expenseId }: PaymentMadeFormProps) {
|
||||
const api = useAuthApi()
|
||||
|
||||
const paymentForOptions = useMemo(() => {
|
||||
if (expenseId) {
|
||||
return PAYMENT_FOR_OPTIONS.filter((option) => option.value === "expense")
|
||||
}
|
||||
return PAYMENT_FOR_OPTIONS.filter((option) => option.value === "bill")
|
||||
}, [expenseId])
|
||||
|
||||
const resolvedInitialData = useMemo(() => {
|
||||
const base: any = { ...(initialData as any) }
|
||||
if (!resourceId) {
|
||||
@@ -146,6 +163,8 @@ export function PaymentMadeForm({ resourceId, initialData, onSuccess, billId, ex
|
||||
base.payment_for = "bill"
|
||||
} else if (expenseId) {
|
||||
base.payment_for = "expense"
|
||||
} else {
|
||||
base.payment_for = "bill"
|
||||
}
|
||||
}
|
||||
return Object.keys(base).length ? base : initialData
|
||||
@@ -159,6 +178,13 @@ export function PaymentMadeForm({ resourceId, initialData, onSuccess, billId, ex
|
||||
mapToFormValues,
|
||||
})
|
||||
|
||||
const vendor = form.watch("vendor")
|
||||
const employee = form.watch("employee")
|
||||
const paymentFor = form.watch("payment_for")
|
||||
const vendorRequired = !employee?.value
|
||||
const employeeRequired = !vendor?.value
|
||||
const showBillField = !billId && !expenseId
|
||||
|
||||
const { mutate, error, isPending } = useFormMutation(form, {
|
||||
mutationFn: (values: PaymentMadeFormValues) => {
|
||||
const payload = mapFormToPayload(values, billId, expenseId)
|
||||
@@ -172,6 +198,27 @@ export function PaymentMadeForm({ resourceId, initialData, onSuccess, billId, ex
|
||||
})
|
||||
return promise
|
||||
},
|
||||
onError: (error) => {
|
||||
if (!(error instanceof ApiError) || !error.validationErrors) {
|
||||
return
|
||||
}
|
||||
|
||||
const vendorError = error.validationErrors.vendor_id?.[0]
|
||||
const employeeError = error.validationErrors.employee_id?.[0]
|
||||
const billError = error.validationErrors.bill_id?.[0]
|
||||
|
||||
if (billError) {
|
||||
form.setError("bill", { message: billError })
|
||||
}
|
||||
|
||||
if (!vendorError && !employeeError) {
|
||||
return
|
||||
}
|
||||
|
||||
const message = "Select either a vendor or an employee, not both"
|
||||
form.setError("vendor", { message })
|
||||
form.setError("employee", { message })
|
||||
},
|
||||
onSuccess: () => {
|
||||
form.reset()
|
||||
onSuccess?.()
|
||||
@@ -192,14 +239,30 @@ export function PaymentMadeForm({ resourceId, initialData, onSuccess, billId, ex
|
||||
|
||||
<FieldGroup>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
{showBillField && (
|
||||
<RhfAsyncSelectField
|
||||
name="bill"
|
||||
label="Bill"
|
||||
placeholder="Select bill"
|
||||
required={paymentFor === "bill"}
|
||||
queryKey={[BILL_ROUTES.INDEX]}
|
||||
listFn={() => api.bills.list()}
|
||||
mapOption={mapBillOption}
|
||||
{...STORE_OBJECT}
|
||||
/>
|
||||
)}
|
||||
<RhfVendorSelectField
|
||||
name="vendor"
|
||||
label="Vendor"
|
||||
required={vendorRequired}
|
||||
description="Select either a vendor or an employee."
|
||||
/>
|
||||
<RhfAsyncSelectField
|
||||
name="employee"
|
||||
label="Employee"
|
||||
placeholder="Select employee"
|
||||
required={employeeRequired}
|
||||
description="Select either a vendor or an employee."
|
||||
queryKey={[EMPLOYEE_ROUTES.INDEX]}
|
||||
listFn={() => api.employees.list()}
|
||||
mapOption={mapEmployeeOption}
|
||||
@@ -212,7 +275,7 @@ export function PaymentMadeForm({ resourceId, initialData, onSuccess, billId, ex
|
||||
name="payment_for"
|
||||
label="Payment For"
|
||||
placeholder="Select type"
|
||||
options={PAYMENT_FOR_OPTIONS}
|
||||
options={paymentForOptions}
|
||||
required
|
||||
/>
|
||||
<RhfTextField
|
||||
|
||||
@@ -27,6 +27,7 @@ const detailsItemSchema = z.object({
|
||||
|
||||
const paymentMadeFormSchema = z.object({
|
||||
// ── Relations ──
|
||||
bill: relationFieldSchema,
|
||||
vendor: relationFieldSchema,
|
||||
employee: relationFieldSchema,
|
||||
payment_mode: requiredRelationFieldSchema,
|
||||
@@ -41,9 +42,18 @@ const paymentMadeFormSchema = z.object({
|
||||
notes: z.string().optional(),
|
||||
details: z.array(detailsItemSchema).min(1, "At least one payment detail is required"),
|
||||
}).superRefine((values, ctx) => {
|
||||
const hasBill = Boolean(values.bill?.value)
|
||||
const hasVendor = Boolean(values.vendor?.value)
|
||||
const hasEmployee = Boolean(values.employee?.value)
|
||||
|
||||
if (values.payment_for === "bill" && !hasBill) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["bill"],
|
||||
message: "Bill is required",
|
||||
})
|
||||
}
|
||||
|
||||
if (!hasVendor && !hasEmployee) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
@@ -56,6 +66,19 @@ const paymentMadeFormSchema = z.object({
|
||||
message: "Vendor or employee is required",
|
||||
})
|
||||
}
|
||||
|
||||
if (hasVendor && hasEmployee) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["vendor"],
|
||||
message: "Select either a vendor or an employee, not both",
|
||||
})
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["employee"],
|
||||
message: "Select either a vendor or an employee, not both",
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
type PaymentMadeFormValues = z.infer<typeof paymentMadeFormSchema>
|
||||
|
||||
Reference in New Issue
Block a user