import { useState, useEffect } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faUserPen } from '@fortawesome/pro-duotone-svg-icons'; import { Input } from '@/components/base/input/input'; import { Select } from '@/components/base/select/select'; import { TextArea } from '@/components/base/textarea/textarea'; import { Checkbox } from '@/components/base/checkbox/checkbox'; import { Button } from '@/components/base/buttons/button'; import { EditPatientConfirmModal } from '@/components/modals/edit-patient-confirm-modal'; import { apiClient } from '@/lib/api-client'; import { notify } from '@/lib/toast'; type EnquiryFormProps = { isOpen: boolean; onOpenChange: (open: boolean) => void; callerPhone?: string | null; // Pre-populated caller name (from caller-resolution). When set, the // patient-name field is locked behind the Edit-confirm modal to // prevent accidental rename-on-save. When empty or null, the field // starts unlocked because there's no existing name to protect. leadName?: string | null; leadId?: string | null; patientId?: string | null; agentName?: string | null; onSaved?: () => void; }; export const EnquiryForm = ({ isOpen, onOpenChange, callerPhone, leadName, leadId: propLeadId, patientId, agentName, onSaved }: EnquiryFormProps) => { // Initial name captured at form open — used to detect whether the // agent actually changed the name before committing any updatePatient / // updateLead.contactName mutations. See also appointment-form.tsx. const initialLeadName = (leadName ?? '').trim(); const [patientName, setPatientName] = useState(leadName ?? ''); const [isNameEditable, setIsNameEditable] = useState(initialLeadName.length === 0); const [editConfirmOpen, setEditConfirmOpen] = useState(false); const [source, setSource] = useState('Phone Inquiry'); const [queryAsked, setQueryAsked] = useState(''); const [isExisting, setIsExisting] = useState(false); const [registeredPhone, setRegisteredPhone] = useState(callerPhone ?? ''); const [department, setDepartment] = useState(null); const [doctor, setDoctor] = useState(null); const [followUpNeeded, setFollowUpNeeded] = useState(false); const [followUpDate, setFollowUpDate] = useState(''); const [isSaving, setIsSaving] = useState(false); const [error, setError] = useState(null); // Fetch doctors for department/doctor dropdowns const [doctors, setDoctors] = useState>([]); useEffect(() => { if (!isOpen) return; apiClient.graphql<{ doctors: { edges: Array<{ node: any }> } }>( `{ doctors(first: 50) { edges { node { id name fullName { firstName lastName } department } } } }`, ).then(data => { setDoctors(data.doctors.edges.map(e => ({ id: e.node.id, name: e.node.fullName ? `Dr. ${e.node.fullName.firstName} ${e.node.fullName.lastName}`.trim() : e.node.name, department: e.node.department ?? '', }))); }).catch(() => {}); }, [isOpen]); const departmentItems = [...new Set(doctors.map(d => d.department).filter(Boolean))] .map(dept => ({ id: dept, label: dept.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()) })); const filteredDoctors = department ? doctors.filter(d => d.department === department) : doctors; const doctorItems = filteredDoctors.map(d => ({ id: d.id, label: d.name })); const handleSave = async () => { if (!patientName.trim() || !queryAsked.trim()) { setError('Please fill in required fields: patient name and query.'); return; } setIsSaving(true); setError(null); try { // Use passed leadId or resolve from phone let leadId: string | null = propLeadId ?? null; if (!leadId && registeredPhone) { const resolved = await apiClient.post<{ leadId: string; patientId: string }>('/api/caller/resolve', { phone: registeredPhone }, { silent: true }); leadId = resolved.leadId; } // Determine whether the agent actually renamed the patient. // Only a non-empty, changed-from-initial name counts — empty // strings or an unchanged name never trigger the rename // chain, even if the field was unlocked. const trimmedName = patientName.trim(); const nameChanged = isNameEditable && trimmedName.length > 0 && trimmedName !== initialLeadName; const nameParts = { firstName: trimmedName.split(' ')[0], lastName: trimmedName.split(' ').slice(1).join(' ') || '', }; if (leadId) { // Update existing lead with enquiry details. Only touches // contactName if the agent explicitly renamed — otherwise // we leave the existing caller identity alone. await apiClient.graphql( `mutation($id: UUID!, $data: LeadUpdateInput!) { updateLead(id: $id, data: $data) { id } }`, { id: leadId, data: { name: `Enquiry — ${trimmedName || 'Unknown caller'}`, source: 'PHONE', status: 'CONTACTED', interestedService: queryAsked.substring(0, 100), ...(nameChanged ? { contactName: nameParts } : {}), }, }, ); } else { // No matched lead — create a fresh one. For net-new leads // we always populate contactName from the typed value // (there's no existing record to protect). await apiClient.graphql( `mutation($data: LeadCreateInput!) { createLead(data: $data) { id } }`, { data: { name: `Enquiry — ${trimmedName || 'Unknown caller'}`, contactName: nameParts, contactPhone: registeredPhone ? { primaryPhoneNumber: registeredPhone } : undefined, source: 'PHONE', status: 'CONTACTED', interestedService: queryAsked.substring(0, 100), }, }, ); } // Update linked patient's name ONLY if the agent explicitly // renamed. Fixes the long-standing bug where typing a name // into this form silently overwrote the existing patient // record. if (nameChanged && patientId) { await apiClient.graphql( `mutation($id: UUID!, $data: PatientUpdateInput!) { updatePatient(id: $id, data: $data) { id } }`, { id: patientId, data: { fullName: nameParts, }, }, ).catch((err: unknown) => console.warn('Failed to update patient name:', err)); } // Post-save side-effects. If the agent actually renamed the // patient, kick off AI summary regen + cache invalidation. // Otherwise just invalidate the cache so the status update // propagates. if (nameChanged && leadId) { apiClient.post(`/api/lead/${leadId}/enrich`, { phone: callerPhone ?? undefined }, { silent: true }).catch(() => {}); } else if (callerPhone) { apiClient.post('/api/caller/invalidate', { phone: callerPhone }, { silent: true }).catch(() => {}); } // Create follow-up if needed if (followUpNeeded) { if (!followUpDate) { setError('Please select a follow-up date.'); setIsSaving(false); return; } await apiClient.graphql( `mutation($data: FollowUpCreateInput!) { createFollowUp(data: $data) { id } }`, { data: { name: `Follow-up — ${patientName}`, typeCustom: 'CALLBACK', status: 'PENDING', priority: 'NORMAL', assignedAgent: agentName ?? undefined, scheduledAt: new Date(`${followUpDate}T09:00:00`).toISOString(), patientId: patientId ?? undefined, }, }, { silent: true }, ); } notify.success('Enquiry Logged', 'Contact details and query captured'); onSaved?.(); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to save enquiry'); } finally { setIsSaving(false); } }; if (!isOpen) return null; return (
{/* Form fields — scrollable */}
{/* Patient name — locked by default for existing callers, unlocked for new callers with no prior name on record. The Edit button opens a confirm modal before unlocking; see EditPatientConfirmModal for the rationale. */}
{!isNameEditable && initialLeadName.length > 0 && ( )}