fix many bugs

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-30 19:03:31 +03:00
co-authored by Copilot
parent 0c43c8edd2
commit e1ef6fa2ea
24 changed files with 333 additions and 82 deletions
@@ -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>
)
}