@@ -21,7 +21,8 @@ import {
|
||||
customerFormSchema,
|
||||
type CustomerFormValues,
|
||||
} from "./customer.schema"
|
||||
import { CUSTOMER_ROUTES } from "@garage/api"
|
||||
import { CUSTOMER_ROUTES, PAYMENT_TERM_ROUTES } from "@garage/api"
|
||||
import { PaymentTermCrudDialog } from "./payment-term-crud-dialog"
|
||||
|
||||
// ── Constants ──
|
||||
|
||||
@@ -69,7 +70,7 @@ function mapCustomerToFormValues(data: unknown): CustomerFormValues {
|
||||
const c = (data as any)?.data ?? data ?? {}
|
||||
|
||||
return {
|
||||
customer_type: toRelation(c.customer_type_id, c.customer_type_name),
|
||||
customer_type: toRelation(c.customer_type_id, c.customer_type?.name ?? c.customer_type_name),
|
||||
referral_source: toRelation(c.referral_source_id, c.referral_source_name),
|
||||
payment_terms: toRelation(c.payment_terms_id, c.payment_terms_name),
|
||||
country: toRelation(c.country_id, c.country_name),
|
||||
@@ -113,10 +114,10 @@ function mapFormToPayload(values: CustomerFormValues) {
|
||||
|
||||
const mapLookupOption = (item: any) => ({
|
||||
value: String(item.id),
|
||||
label: item.name,
|
||||
label: item.title||item.name || `#${item.id}`,
|
||||
})
|
||||
|
||||
const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) => o.label }
|
||||
const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) => o.label || o.name || `#${o.id}` }
|
||||
|
||||
// ── Component ──
|
||||
|
||||
@@ -173,6 +174,7 @@ export function CustomerForm({ resourceId, initialData, onSuccess }: CustomerFor
|
||||
options={SALUTATION_OPTIONS}
|
||||
/>
|
||||
<RhfAsyncSelectField
|
||||
required
|
||||
name="customer_type"
|
||||
label="Customer Type"
|
||||
placeholder="Select customer type"
|
||||
@@ -211,15 +213,21 @@ export function CustomerForm({ resourceId, initialData, onSuccess }: CustomerFor
|
||||
mapOption={mapLookupOption}
|
||||
{...STORE_OBJECT}
|
||||
/>
|
||||
<RhfAsyncSelectField
|
||||
name="payment_terms"
|
||||
label="Payment Terms"
|
||||
placeholder="Select payment terms"
|
||||
queryKey={["payment-terms"]}
|
||||
listFn={() => api.paymentTerms.list()}
|
||||
mapOption={mapLookupOption}
|
||||
{...STORE_OBJECT}
|
||||
/>
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-sm font-medium">Payment Terms</span>
|
||||
<PaymentTermCrudDialog />
|
||||
</div>
|
||||
<RhfAsyncSelectField
|
||||
name="payment_terms"
|
||||
label=""
|
||||
placeholder="Select payment terms"
|
||||
queryKey={[PAYMENT_TERM_ROUTES.INDEX]}
|
||||
listFn={() => api.paymentTerms.list()}
|
||||
mapOption={mapLookupOption}
|
||||
{...STORE_OBJECT}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Address */}
|
||||
|
||||
@@ -12,7 +12,7 @@ type RelationField = z.infer<typeof relationFieldSchema>
|
||||
|
||||
const customerFormSchema = z.object({
|
||||
// ── Relations (stored as objects, mapped to IDs on submit) ──
|
||||
customer_type: relationFieldSchema,
|
||||
customer_type: relationFieldSchema.refine((val) => !!val?.value, "Customer type is required"),
|
||||
referral_source: relationFieldSchema,
|
||||
payment_terms: relationFieldSchema,
|
||||
country: relationFieldSchema,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
"use client"
|
||||
|
||||
import { CrudDialog } from "@/shared/components/crud-dialog"
|
||||
import { ColumnHeader } from "@/shared/data-view/table-view"
|
||||
import { useAuthApi } from "@/shared/useApi"
|
||||
import { PAYMENT_TERM_ROUTES } from "@garage/api"
|
||||
import { PaymentTermForm } from "./payment-term-form"
|
||||
|
||||
export function PaymentTermCrudDialog() {
|
||||
const api = useAuthApi()
|
||||
|
||||
return (
|
||||
<CrudDialog
|
||||
title="Payment Terms"
|
||||
queryKey={[PAYMENT_TERM_ROUTES.INDEX]}
|
||||
getClient={() => api.paymentTerms}
|
||||
resourceLabel="payment term"
|
||||
columns={() => [
|
||||
{
|
||||
accessorKey: "title",
|
||||
header: ({ column }) => <ColumnHeader column={column} title="Name" />,
|
||||
},
|
||||
]}
|
||||
renderForm={({ resourceId, initialData, onSuccess }) => (
|
||||
<PaymentTermForm
|
||||
resourceId={resourceId}
|
||||
initialData={initialData}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
"use client"
|
||||
|
||||
import { z } from "zod"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Plus, Save } from "lucide-react"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import { FieldGroup } from "@/shared/components/ui/field"
|
||||
import { Rhform, RhfTextField } from "@/shared/components/form"
|
||||
import { toast } from "sonner"
|
||||
import { useAuthApi } from "@/shared/useApi"
|
||||
import { useEffect } from "react"
|
||||
|
||||
// ── Schema ──
|
||||
|
||||
const paymentTermSchema = z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
})
|
||||
|
||||
type PaymentTermFormValues = z.infer<typeof paymentTermSchema>
|
||||
|
||||
// ── Props ──
|
||||
|
||||
type PaymentTermFormProps = {
|
||||
resourceId?: string | null
|
||||
initialData?: any
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
// ── Component ──
|
||||
|
||||
export function PaymentTermForm({ resourceId, initialData, onSuccess }: PaymentTermFormProps) {
|
||||
const api = useAuthApi()
|
||||
const isEditing = !!resourceId
|
||||
|
||||
const form = useForm<PaymentTermFormValues>({
|
||||
resolver: zodResolver(paymentTermSchema),
|
||||
defaultValues: { name: "" },
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
const d = initialData?.data ?? initialData
|
||||
form.reset({ name: d.title ?? "" })
|
||||
}
|
||||
}, [initialData, form])
|
||||
|
||||
const handleSubmit = async (values: PaymentTermFormValues) => {
|
||||
try {
|
||||
const promise = isEditing
|
||||
? api.paymentTerms.update(resourceId!, { title: values.name } as any)
|
||||
: api.paymentTerms.create({ title: values.name } as any)
|
||||
|
||||
toast.promise(promise, {
|
||||
loading: isEditing ? "Updating..." : "Creating...",
|
||||
success: isEditing ? "Updated successfully" : "Created successfully",
|
||||
error: isEditing ? "Failed to update" : "Failed to create",
|
||||
})
|
||||
|
||||
await promise
|
||||
form.reset()
|
||||
onSuccess?.()
|
||||
} catch {
|
||||
// toast already shown
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Rhform form={form} onSubmit={handleSubmit}>
|
||||
<FieldGroup>
|
||||
<RhfTextField
|
||||
name="name"
|
||||
label="Name"
|
||||
placeholder="e.g. Net 30"
|
||||
required
|
||||
/>
|
||||
<Button type="submit" disabled={form.formState.isSubmitting}>
|
||||
{isEditing ? <Save className="h-4 w-4" /> : <Plus className="h-4 w-4" />}
|
||||
{form.formState.isSubmitting
|
||||
? (isEditing ? "Updating..." : "Creating...")
|
||||
: (isEditing ? "Update" : "Create")}
|
||||
</Button>
|
||||
</FieldGroup>
|
||||
</Rhform>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user