import { useEffect, useState } from 'react' import { Button, Dialog, FormField, FormGrid, Notice, useToast } from '@sims/ui' import { createClient, patchClient, type Client, type ClientContact } from '../api' interface ContactDraft { name: string; email: string; phone: string; role: string } const EMPTY_CONTACT: ContactDraft = { name: '', email: '', phone: '', role: '' } /** Create/edit a client — identity, address, notes, and the contacts repeater. */ export function ClientFormDialog(props: { open: boolean onClose: () => void initial?: Client onSaved: (c: Client) => void }) { const toast = useToast() const editing = props.initial !== undefined const [f, setF] = useState({ name: '', gstin: '', stateCode: '32', address: '', notes: '', anydesk: '', os: '', district: '', sector: '', }) const [contacts, setContacts] = useState([]) const [error, setError] = useState() const [saving, setSaving] = useState(false) useEffect(() => { if (!props.open) return const c = props.initial setError(undefined) setF({ name: c?.name ?? '', gstin: c?.gstin ?? '', stateCode: c?.stateCode ?? '32', address: c?.address ?? '', notes: c?.notes ?? '', anydesk: c?.anydesk ?? '', os: c?.os ?? '', district: c?.district ?? '', sector: c?.sector ?? '', }) setContacts((c?.contacts ?? []).map((ct) => ({ name: ct.name, email: ct.email ?? '', phone: ct.phone ?? '', role: ct.role ?? '', }))) }, [props.open, props.initial]) const set = (k: keyof typeof f) => (e: { target: { value: string } }) => setF((p) => ({ ...p, [k]: e.target.value })) const setContact = (i: number, k: keyof ContactDraft) => (e: { target: { value: string } }) => setContacts((prev) => prev.map((c, j) => (j === i ? { ...c, [k]: e.target.value } : c))) const save = () => { if (saving) return if (f.name.trim() === '') { setError('Name is required'); return } if (!/^\d{2}$/.test(f.stateCode.trim())) { setError('State code must be two digits'); return } const cleaned: ClientContact[] = contacts .filter((c) => c.name.trim() !== '' || c.email.trim() !== '' || c.phone.trim() !== '') .map((c) => ({ name: c.name.trim() !== '' ? c.name.trim() : (c.email.trim() !== '' ? c.email.trim() : c.phone.trim()), ...(c.email.trim() !== '' ? { email: c.email.trim() } : {}), ...(c.phone.trim() !== '' ? { phone: c.phone.trim() } : {}), ...(c.role.trim() !== '' ? { role: c.role.trim() } : {}), })) const gstin = f.gstin.trim() const body = { name: f.name.trim(), stateCode: f.stateCode.trim(), address: f.address, notes: f.notes, contacts: cleaned, // Support-access fields (D18 WS-F). PATCH clears on ''; POST omits blanks. ...(editing ? { anydesk: f.anydesk, os: f.os, district: f.district, sector: f.sector } : { ...(f.anydesk.trim() !== '' ? { anydesk: f.anydesk.trim() } : {}), ...(f.os.trim() !== '' ? { os: f.os.trim() } : {}), ...(f.district.trim() !== '' ? { district: f.district.trim() } : {}), ...(f.sector.trim() !== '' ? { sector: f.sector.trim() } : {}), }), // Server quirk: PATCH accepts gstin: null to clear (repos-clients skips the checksum // assert on null), but POST's ClientInput.gstin is a bare optional string — sending // null there crashes assertGstin's .toUpperCase() on null. So: editing may clear via // null; creating omits the key entirely when blank. ...(editing ? { gstin: gstin === '' ? null : gstin.toUpperCase() } : (gstin !== '' ? { gstin: gstin.toUpperCase() } : {})), } setError(undefined) setSaving(true) const call = editing ? patchClient(props.initial!.id, body) : createClient(body) call .then((c) => { toast.ok(editing ? 'Client saved' : `Client ${c.code} created`); props.onSaved(c); props.onClose() }) .catch((e: Error) => setError(e.message)) .finally(() => setSaving(false)) } return ( { if (!saving) props.onClose() }} title={editing ? `Edit ${props.initial!.name}` : 'New client'} size="lg" footer={ <> } >