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; // Called after a successful save. Passes back the list of actions that // were actually recorded — the parent uses this to drive the disposition // priority + lock logic. Always includes 'ENQUIRY'; adds 'FOLLOWUP' when // the agent scheduled a callback. onSaved?: (actions: Array<'ENQUIRY' | 'FOLLOWUP'>) => 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 { // Resolve caller. Resolver returns isNew=true when no Lead/ // Patient exists for this phone — in that case we create both // records inline with the typed name. Otherwise we update the // existing records. let leadId: string | null = propLeadId ?? null; let resolvedPatientId: string | null = patientId || null; let isNew = false; if ((!leadId || !resolvedPatientId) && registeredPhone) { const resolved = await apiClient.post<{ leadId: string; patientId: string; isNew: boolean }>('/api/caller/resolve', { phone: registeredPhone }, { silent: true }); leadId = leadId || resolved.leadId || null; resolvedPatientId = resolvedPatientId || resolved.patientId || null; isNew = !!resolved.isNew && !leadId; } 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 (isNew) { // Net-new caller — create Patient + Lead with the typed // name. Name is required (validated above). if (!trimmedName) { setError('Please enter the patient name.'); setIsSaving(false); return; } try { const phoneE164 = registeredPhone ? `+91${registeredPhone.replace(/^\+?91/, '').replace(/\D/g, '').slice(-10)}` : undefined; const patientData: Record = { name: trimmedName, fullName: nameParts, patientType: 'NEW', }; if (phoneE164) patientData.phones = { primaryPhoneNumber: phoneE164 }; const pResult = await apiClient.graphql<{ createPatient: { id: string } }>( `mutation($data: PatientCreateInput!) { createPatient(data: $data) { id } }`, { data: patientData }, ); resolvedPatientId = pResult.createPatient.id; } catch (err) { console.warn('Failed to create patient:', err); } const leadData: Record = { name: `Enquiry — ${trimmedName}`, contactName: nameParts, source: 'PHONE', status: 'CONTACTED', interestedService: queryAsked.substring(0, 100), }; if (registeredPhone) leadData.contactPhone = { primaryPhoneNumber: registeredPhone }; if (resolvedPatientId) leadData.patientId = resolvedPatientId; const lResult = await apiClient.graphql<{ createLead: { id: string } }>( `mutation($data: LeadCreateInput!) { createLead(data: $data) { id } }`, { data: leadData }, ); leadId = lResult.createLead.id; } else if (leadId) { // Existing lead — update with enquiry details. Only touch // contactName when the agent explicitly renamed (the name // field is locked behind the Edit confirm modal for // existing records). 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 } : {}), }, }, ); } // Update linked patient's name when the agent renamed (edit // confirm path) on an existing record. Skipped for isNew // because the patient was just created with the right name. if (!isNew && nameChanged && resolvedPatientId && trimmedName) { await apiClient.graphql( `mutation($id: UUID!, $data: PatientUpdateInput!) { updatePatient(id: $id, data: $data) { id } }`, { id: resolvedPatientId, data: { fullName: nameParts, }, }, ).catch((err: unknown) => console.warn('Failed to update patient name:', err)); } // Post-save side-effect. If the agent actually renamed the // patient, kick off AI summary regen. Fire-and-forget. if (nameChanged && leadId) { apiClient.post(`/api/lead/${leadId}/enrich`, { phone: callerPhone ?? undefined }, { silent: true }).catch(() => {}); } // Create follow-up if needed if (followUpNeeded) { if (!followUpDate) { setError('Please select a follow-up date.'); setIsSaving(false); return; } const today = new Date().toISOString().split('T')[0]; if (followUpDate < today) { setError('Follow-up date cannot be in the past.'); 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: resolvedPatientId || undefined, }, }, { silent: true }, ); } notify.success('Enquiry Logged', 'Contact details and query captured'); const actions: Array<'ENQUIRY' | 'FOLLOWUP'> = ['ENQUIRY']; if (followUpNeeded) actions.push('FOLLOWUP'); onSaved?.(actions); } 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 && ( )}