// Lead detail — conversation + Tommy draft + stage controls, wired to /leads/{id} // and the tommy approve/reject/coach endpoints. const STAGE_OPTIONS = [ 'NEW LEAD', 'FIRST TOUCH', 'INTERESTED', 'FOLLOW UP', 'DAY PASS SCHEDULED', 'NEEDS_RESCHEDULE', 'SHOWED', 'JOINED', 'MEMBER', 'DEAD', 'NO SHOW', ]; function LeadDetailView({ contactId, onNavigate, user }) { const [lead, setLead] = React.useState(null); const [approval, setApproval] = React.useState(null); const [loading, setLoading] = React.useState(true); const [draftText, setDraftText] = React.useState(''); const [typed, setTyped] = React.useState(''); const [sending, setSending] = React.useState(false); const [stageEdit, setStageEdit] = React.useState(false); const scrollRef = React.useRef(null); async function reload() { setLoading(true); try { const [l, appr] = await Promise.all([ api.get(`/api/v2/leads/${encodeURIComponent(contactId)}`), api.get('/api/v2/tommy/pending-approvals'), ]); const adapted = adaptLead(l); adapted.messages = l.messages || []; setLead(adapted); cacheContacts([adapted]); const mine = (appr?.approvals || []).find(a => a.contact_id === contactId); setApproval(mine || null); setDraftText(mine?.tommy_draft || ''); // Mark this thread read. api.post('/api/v2/tommy/mark-read', { contact_id: contactId }).catch(() => {}); } catch (e) { setLead(null); } finally { setLoading(false); } } React.useEffect(() => { reload(); /* eslint-disable-next-line */ }, [contactId]); React.useEffect(() => { function onEv(e) { const d = e.detail || {}; if (!d) return; if (d.contact_id === contactId || d.event_type === 'tommy_sent' || d.event_type === 'sms_inbound') { reload(); } } window.addEventListener('raw:event', onEv); return () => window.removeEventListener('raw:event', onEv); // eslint-disable-next-line react-hooks/exhaustive-deps }, [contactId]); React.useEffect(() => { scrollRef.current?.scrollTo({top: scrollRef.current.scrollHeight}); }, [lead?.messages?.length, approval?.id]); async function approve() { if (!approval) return; try { await api.post('/api/v2/tommy/approve', { approval_id: approval.id, message: draftText }); await reload(); } catch (e) { if (isQuietHoursError(e) && confirm(quietHoursPrompt(e, 'approve and send'))) { try { await api.post('/api/v2/tommy/approve', { approval_id: approval.id, message: draftText, override_quiet_hours: true, }); await reload(); return; } catch (e2) { alert(friendlyApiError(e2, 'Approve failed')); return; } } alert(friendlyApiError(e, 'Approve failed')); } } async function reject() { if (!approval) return; try { await api.post('/api/v2/tommy/reject', { approval_id: approval.id }); await reload(); } catch (e) { alert(friendlyApiError(e, 'Reject failed')); } } async function coach() { if (!approval) return; const note = prompt('What extra context should Tommy use?'); if (!note) return; try { await api.post('/api/v2/tommy/coach', { approval_id: approval.id, context_note: note }); await reload(); } catch (e) { alert(friendlyApiError(e, 'Coach failed')); } } async function dismiss() { if (!approval) return; try { await api.post('/api/v2/tommy/dismiss', { approval_id: approval.id }); await reload(); } catch (e) { alert(friendlyApiError(e, 'Dismiss failed')); } } async function sendManual() { if (!typed.trim()) return; setSending(true); const text = typed.trim(); try { await api.post(`/api/v2/leads/${encodeURIComponent(contactId)}/message`, { message: text }); setTyped(''); await reload(); } catch (e) { if (isQuietHoursError(e) && confirm(quietHoursPrompt(e, 'send this SMS'))) { try { await api.post(`/api/v2/leads/${encodeURIComponent(contactId)}/message`, { message: text, override_quiet_hours: true, }); setTyped(''); await reload(); } catch (e2) { alert(friendlyApiError(e2, 'Send failed')); } } else { alert(friendlyApiError(e, 'Send failed')); } } finally { setSending(false); } } async function promptTommy() { try { await api.post(`/api/v2/leads/${encodeURIComponent(contactId)}/prompt-tommy`, {}); setTimeout(reload, 800); } catch (e) { alert(friendlyApiError(e, 'Prompt failed')); } } async function changeStage(next) { try { await api.patch(`/api/v2/leads/${encodeURIComponent(contactId)}/stage`, { stage: next }); setStageEdit(false); await reload(); } catch (e) { alert(friendlyApiError(e, 'Stage change failed')); } } async function toggleHot() { if (!lead) return; const next = !lead.hot; try { await api.patch(`/api/v2/leads/${encodeURIComponent(contactId)}`, { hot: next }); await reload(); } catch (e) { alert(friendlyApiError(e, 'Failed to toggle hot')); } } if (loading && !lead) { return