garage-erp/apps/dashboard/modules/invoices/invoice-sequence-form.tsx
2026-04-23 15:15:21 +03:00

151 lines
5.4 KiB
TypeScript

"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, RhfAsyncSelectField, RhfCheckboxField } from "@/shared/components/form"
import { toast } from "sonner"
import { useAuthApi } from "@/shared/useApi"
import { useEffect } from "react"
import { DEPARTMENT_ROUTES } from "@garage/api"
const invoiceSequenceSchema = z.object({
title: z.string().min(1, "Title is required"),
sequence_title: z.string().optional(),
prefix: z.string().optional(),
start_number: z.coerce.number().int().min(1).optional(),
auto_generate: z.boolean().optional(),
department: z.object({ value: z.string(), label: z.string() }).nullable().optional(),
})
type InvoiceSequenceFormValues = z.infer<typeof invoiceSequenceSchema>
const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) => o.label }
type InvoiceSequenceFormProps = {
resourceId?: string | null
initialData?: any
onSuccess?: () => void
}
export function InvoiceSequenceForm({ resourceId, initialData, onSuccess }: InvoiceSequenceFormProps) {
const api = useAuthApi()
const isEditing = !!resourceId
const form = useForm<InvoiceSequenceFormValues>({
resolver: zodResolver(invoiceSequenceSchema) as any,
defaultValues: {
title: "",
sequence_title: "",
prefix: "",
start_number: 1,
auto_generate: false,
department: null,
},
})
useEffect(() => {
if (initialData) {
const d = initialData?.data ?? initialData
form.reset({
title: d.title ?? "",
sequence_title: d.sequence_title ?? "",
prefix: d.prefix ?? "",
start_number: d.start_number ?? 1,
auto_generate: d.auto_generate ?? false,
department: d.department_id
? { value: String(d.department_id), label: d.department_name ?? `#${d.department_id}` }
: null,
})
}
}, [initialData, form])
const handleSubmit = async (values: InvoiceSequenceFormValues) => {
try {
const payload = {
title: values.title,
sequence_title: values.sequence_title || undefined,
prefix: values.prefix || undefined,
start_number: values.start_number,
auto_generate: values.auto_generate,
department_id: values.department ? Number(values.department.value) : undefined,
}
const promise = isEditing
? api.invoiceSequences.update(resourceId!, payload)
: api.invoiceSequences.create(payload)
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>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfTextField
name="title"
label="Title"
placeholder="e.g. Default Invoice Sequence"
required
/>
<RhfTextField
name="sequence_title"
label="Sequence Title"
placeholder="e.g. INV"
/>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfTextField
name="prefix"
label="Prefix"
placeholder="e.g. INV-"
/>
<RhfTextField
name="start_number"
label="Start Number"
type="number"
placeholder="e.g. 1"
/>
</div>
<RhfAsyncSelectField
name="department"
label="Department"
placeholder="Select department"
queryKey={[DEPARTMENT_ROUTES.INDEX]}
listFn={() => api.departments.list()}
mapOption={(item: any) => ({
value: String(item.id),
label: item.name ?? item.title ?? `#${item.id}`,
})}
{...STORE_OBJECT}
/>
<RhfCheckboxField name="auto_generate" label="Auto Generate" />
<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>
)
}