import React, { useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Input } from '@/components/ui/Input'; import { Select } from '@/components/ui/Select'; import { Button } from '@/components/ui/Button'; import { useToast } from '@/components/ui/Toast'; import { type UnifiedContact, useCreateUnifiedContact, useUpdateUnifiedContact, } from '@/api/hooks'; import { CustomFieldRenderer } from '@/components/contacts/CustomFieldRenderer'; import { useCustomFields, useUpdateCustomFields } from '@/api/customFields'; import { usePluginStore } from '@/store/pluginStore'; export interface ContactEditFormProps { onClose: () => void; contact?: UnifiedContact | null; onSaved?: (id: string) => void; } // ── Zod Schema ── const phoneRegex = /^[+]?[\d\s\-().]{6,20}$/; const contactSchema = z.object({ type: z.enum(['company', 'person']), name: z.string().optional().default(''), firstname: z.string().optional().default(''), surname: z.string().optional().default(''), code: z.string().optional().default(''), email_1: z.string().email('Invalid email').or(z.literal('')).optional().default(''), email_2: z.string().email('Invalid email').or(z.literal('')).optional().default(''), phone_1: z.string().regex(phoneRegex, 'Invalid phone').or(z.literal('')).optional().default(''), phone_2: z.string().regex(phoneRegex, 'Invalid phone').or(z.literal('')).optional().default(''), website: z.string().optional().default(''), mailing_street: z.string().optional().default(''), mailing_number: z.string().optional().default(''), mailing_postalcode: z.string().optional().default(''), mailing_city: z.string().optional().default(''), mailing_country: z.string().optional().default(''), vat_code: z.string().optional().default(''), fiscal_code: z.string().optional().default(''), commerce_code: z.string().optional().default(''), bic: z.string().optional().default(''), bank_account: z.string().optional().default(''), tags: z.string().optional().default(''), projectnote: z.string().optional().default(''), contact_warning: z.string().optional().default(''), }).superRefine((data, ctx) => { if (data.type === 'company' && !data.name?.trim()) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Required', path: ['name'] }); } if (data.type === 'person' && !data.firstname?.trim() && !data.surname?.trim()) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Required', path: ['firstname'] }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Required', path: ['surname'] }); } }); type ContactFormData = z.infer; export function ContactEditForm({ onClose, contact, onSaved }: ContactEditFormProps) { const { t } = useTranslation(); const toast = useToast(); const createMutation = useCreateUnifiedContact(); const updateMutation = useUpdateUnifiedContact(); const isEdit = !!contact; const { register, handleSubmit, reset, watch, formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(contactSchema), defaultValues: { type: 'company', name: '', firstname: '', surname: '', code: '', email_1: '', email_2: '', phone_1: '', phone_2: '', website: '', mailing_street: '', mailing_number: '', mailing_postalcode: '', mailing_city: '', mailing_country: '', vat_code: '', fiscal_code: '', commerce_code: '', bic: '', bank_account: '', tags: '', projectnote: '', contact_warning: '', }, }); const currentType = watch('type'); // Custom fields const manifests = usePluginStore(s => s.manifests); const customFieldDefs = useMemo( () => manifests .flatMap((m) => m.custom_fields || []) .filter((cf) => cf.entity === 'contact'), [manifests] ); const { data: customFieldsData } = useCustomFields(contact?.id); const updateCustomFields = useUpdateCustomFields(); const [customValues, setCustomValues] = React.useState>({}); React.useEffect(() => { if (customFieldsData?.fields) { const vals: Record = {}; for (const f of customFieldsData.fields) { vals[f.name] = f.value ?? f.default_value ?? null; } setCustomValues(vals); } }, [customFieldsData]); const handleCustomFieldChange = (name: string, value: any) => { setCustomValues(prev => ({ ...prev, [name]: value })); }; // Reset form when contact changes useEffect(() => { reset({ type: (contact?.type as 'company' | 'person') || 'company', name: contact?.name || '', firstname: contact?.firstname || '', surname: contact?.surname || '', code: contact?.code || '', email_1: contact?.email_1 || '', email_2: contact?.email_2 || '', phone_1: contact?.phone_1 || '', phone_2: contact?.phone_2 || '', website: contact?.website || '', mailing_street: contact?.mailing_street || '', mailing_number: contact?.mailing_number || '', mailing_postalcode: contact?.mailing_postalcode || '', mailing_city: contact?.mailing_city || '', mailing_country: contact?.mailing_country || '', vat_code: contact?.vat_code || '', fiscal_code: contact?.fiscal_code || '', commerce_code: contact?.commerce_code || '', bic: contact?.bic || '', bank_account: contact?.bank_account || '', tags: contact?.tags || '', projectnote: contact?.projectnote || '', contact_warning: contact?.contact_warning || '', }); }, [contact, reset]); const onSubmit = async (formData: ContactFormData) => { const data: Partial = { type: formData.type, name: formData.type === 'company' ? formData.name || null : null, firstname: formData.type === 'person' ? formData.firstname || null : null, surname: formData.type === 'person' ? formData.surname || null : null, code: formData.code || null, email_1: formData.email_1 || null, email_2: formData.email_2 || null, phone_1: formData.phone_1 || null, phone_2: formData.phone_2 || null, website: formData.website || null, mailing_street: formData.mailing_street || null, mailing_number: formData.mailing_number || null, mailing_postalcode: formData.mailing_postalcode || null, mailing_city: formData.mailing_city || null, mailing_country: formData.mailing_country || null, vat_code: formData.vat_code || null, fiscal_code: formData.fiscal_code || null, commerce_code: formData.commerce_code || null, bic: formData.bic || null, bank_account: formData.bank_account || null, tags: formData.tags || null, projectnote: formData.projectnote || null, contact_warning: formData.contact_warning || null, }; try { let savedId: string | undefined; if (isEdit && contact) { await updateMutation.mutateAsync({ id: contact.id, data }); savedId = contact.id; toast.success(t('contacts.updated')); onSaved?.(contact.id); } else { const result = await createMutation.mutateAsync(data) as { id: string }; savedId = result.id; toast.success(t('contacts.created')); onSaved?.(result.id); } // Save custom fields if any definitions exist and we have a contact ID if (savedId && customFieldDefs.length > 0 && Object.keys(customValues).length > 0) { try { await updateCustomFields.mutateAsync({ contactId: savedId, values: customValues }); } catch (cfErr: any) { console.error('Custom fields save failed:', cfErr); } } onClose(); } catch (err: any) { toast.error(err.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed'))); } }; return (
{/* Type */} ) : (
)} {/* Code */} {/* Communication */}

{t('contacts.communication')}

{/* Mailing Address */}

{t('contacts.mailingAddress')}

{/* Financial */}

{t('contacts.financial')}

{/* Notes */}

{t('contacts.notes')}