"use client" import { useEffect, useRef, useState } from "react" import { zodResolver } from "@hookform/resolvers/zod" import { Controller, useFieldArray, useForm, type Resolver, type UseFormReturn, } from "react-hook-form" import { PlusIcon, Trash2Icon } from "lucide-react" import { z } from "zod/v4" import { CompanyPicker } from "@/components/company-picker" import { Button } from "@/components/ui/button" import { Field, FieldContent, FieldError, FieldLabel, } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Textarea } from "@/components/ui/textarea" import { fetchCustomerContacts, type AdminCustomerContact } from "@/lib/api/customer-contact" import { fetchCustomer, type AdminCustomer, type SaveCustomerProfilePayload, } from "@/lib/api/customer" import { getEnumLabel, getEnumOptions } from "@/lib/enums" import { ContactType, ContactTypeLabels, Gender, GenderLabels } from "@/lib/generated/enums" const genderOptions = [ ...getEnumOptions(GenderLabels).map((item) => ({ value: String(item.value), label: item.label, })), ] as const const genderValueOptions = [ String(Gender.Unknown), String(Gender.Male), String(Gender.Female), ] as const const contactTypeValues = [ ContactType.Mobile, ContactType.Email, ContactType.Other, ] as const const contactRowSchema = z.object({ id: z.number().optional(), contactType: z.enum(contactTypeValues), contactValue: z.string(), remark: z.string(), isPrimary: z.boolean(), }) const customerFormSchema = z.object({ name: z.string().trim().min(1, "客户名称不能为空"), gender: z.enum(genderValueOptions, { message: "请选择性别" }), companyId: z.string().trim().regex(/^\d+$/, "请选择所属公司"), remark: z.string().trim(), contacts: z.array(contactRowSchema), }) export type CustomerFormValues = z.infer export type CustomerContactFormRow = { id?: number contactType: (typeof contactTypeValues)[number] contactValue: string remark: string isPrimary: boolean } const customerFormResolver = zodResolver(customerFormSchema as never) as Resolver< z.input, undefined, z.output > function defaultContactRow(isPrimary: boolean): CustomerContactFormRow { return { contactType: ContactType.Mobile, contactValue: "", remark: "", isPrimary, } } const emptyCustomerForm: CustomerFormValues = { name: "", gender: "0", companyId: "0", remark: "", contacts: [defaultContactRow(true)], } function buildCustomerMainFromAdmin(item: AdminCustomer | null): Omit { if (!item) { return { name: "", gender: "0", companyId: "0", remark: "", } } return { name: item.name, gender: String(item.gender) as "0" | "1" | "2", companyId: String(item.companyId ?? 0), remark: item.remark ?? "", } } function buildContactsFromApi(list: AdminCustomerContact[]): CustomerContactFormRow[] { if (list.length === 0) { return [defaultContactRow(true)] } return list.map((c) => ({ id: c.id, contactType: c.contactType as CustomerContactFormRow["contactType"], contactValue: c.contactValue ?? "", remark: c.remark ?? "", isPrimary: c.isPrimary, })) } /** 过滤空行并保证至多一条主联系方式(有一条有值时至少一条主) */ export function normalizeContactsForSubmit(rows: CustomerContactFormRow[]): CustomerContactFormRow[] { const withValue = rows.filter((r) => r.contactValue.trim() !== "") if (withValue.length === 0) { return [] } const primaryIdx = withValue.findIndex((r) => r.isPrimary) if (primaryIdx < 0) { return withValue.map((r, i) => ({ ...r, isPrimary: i === 0 })) } return withValue.map((r, i) => ({ ...r, isPrimary: i === primaryIdx, })) } export type CustomerFormSavePayload = SaveCustomerProfilePayload function getGenderLabel(value: string) { return getEnumLabel(GenderLabels, Number(value) as Gender) } function getContactTypeLabel(value: string) { return ContactTypeLabels[value as ContactType] ?? value } type CustomerFormFieldsProps = { form: UseFormReturn fieldIdPrefix?: string remarkRows?: number } function CustomerFormFields({ form, fieldIdPrefix = "customer", remarkRows = 4, }: CustomerFormFieldsProps) { const { control, register, formState: { errors }, watch, setValue, getValues, } = form const { fields, append, remove } = useFieldArray({ control, name: "contacts" }) const id = (suffix: string) => `${fieldIdPrefix}-${suffix}` function setPrimaryIndex(index: number) { fields.forEach((_, i) => { setValue(`contacts.${i}.isPrimary`, i === index) }) } function addContactRow() { append(defaultContactRow(fields.length === 0)) } function removeContactRow(index: number) { const wasPrimary = watch(`contacts.${index}.isPrimary`) remove(index) if (wasPrimary) { requestAnimationFrame(() => { const list = getValues("contacts") if (list.length > 0) { list.forEach((_, i) => setValue(`contacts.${i}.isPrimary`, i === 0)) } }) } } return (

客户信息

客户名称
性别 ( )} /> 所属公司 ( )} />
备注