// ============================================================ // MOORE AI v13 — CONTACTS TAB // Requires: shared/utils.js, shared/components.jsx loaded first // ============================================================ const ContactsView = ({ contacts, loadingTabs, fetchContacts, setSelectedContact, showContactForm, setShowContactForm, contactForm, setContactForm, createContact }) => { const { useState, useEffect } = React; useEffect(() => { if (window.lucide) window.lucide.createIcons(); }); const [search, setSearch] = useState(''); const [activeContact, setActiveContact] = useState(null); const [activity, setActivity] = useState([]); const [loadingActivity, setLoadingActivity] = useState(false); const [noteInput, setNoteInput] = useState(''); const [sendType, setSendType] = useState('SMS'); const [sendMsg, setSendMsg] = useState(''); const [sending, setSending] = useState(false); const [activityTab, setActivityTab] = useState('all'); const [panelExpanded, setPanelExpanded] = useState(false); const filtered = contacts.filter(c => (c.name + c.email + c.phone + (c.tags||[]).join(' ')).toLowerCase().includes(search.toLowerCase()) ); const openContact = async (c) => { setActiveContact(c); setActivity([]); setActivityTab('all'); setPanelExpanded(false); loadActivity(c.id); }; const loadActivity = async (id) => { setLoadingActivity(true); try { const data = await API.fetchContactActivity(id); if (data.success) setActivity(data.activity || []); } catch(e) {} setLoadingActivity(false); }; const addContactNote = async () => { if (!noteInput.trim() || !activeContact) return; await API.addNote(activeContact.id, noteInput); setActivity(prev => [{ type: 'note', body: noteInput, date: new Date().toISOString(), id: Date.now() }, ...prev]); setNoteInput(''); }; const sendMessage = async () => { if (!sendMsg.trim() || !activeContact) return; setSending(true); try { await API.sendConversationMessage({ type: sendType, contactId: activeContact.id, message: sendMsg, subject: `Message from Jake` }); setActivity(prev => [{ type: sendType.toLowerCase(), body: sendMsg, date: new Date().toISOString(), direction: 'outbound', id: Date.now() }, ...prev]); setSendMsg(''); } catch(e) { alert('Send failed: ' + e.message); } setSending(false); }; const filteredActivity = activity.filter(a => { if (activityTab === 'sms') return a.type === 'sms'; if (activityTab === 'email') return a.type === 'email'; if (activityTab === 'notes') return a.type === 'note'; if (activityTab === 'tasks') return a.type === 'task'; if (activityTab === 'event') return a.type === 'event'; return true; }); const activityIcon = (type) => { if (type === 'sms') return 'message-square'; if (type === 'email') return 'mail'; if (type === 'call') return 'phone'; if (type === 'note') return 'file-text'; if (type === 'task') return 'check-square'; if (type === 'event') return 'zap'; return 'activity'; }; const activityColors = (type, direction) => { if (type === 'sms') return direction === 'inbound' ? 'text-emerald-300 bg-emerald-500/5 border-emerald-500/20' : 'text-blue-300 bg-blue-500/5 border-blue-500/20'; if (type === 'email') return direction === 'inbound' ? 'text-cyan-300 bg-cyan-500/5 border-cyan-500/20' : 'text-violet-300 bg-violet-500/5 border-violet-500/20'; if (type === 'call') return 'text-amber-300 bg-amber-500/5 border-amber-500/20'; if (type === 'note') return 'text-slate-300 bg-slate-800/50 border-slate-700/40'; if (type === 'task') return 'text-orange-300 bg-orange-500/5 border-orange-500/20'; if (type === 'event') return 'text-teal-300 bg-teal-500/5 border-teal-500/20'; return 'text-slate-300 bg-slate-800/50 border-slate-700/40'; }; const activityLabel = (item) => { if (item.type === 'sms') return item.direction === 'inbound' ? '← SMS' : '→ SMS'; if (item.type === 'email') return item.direction === 'inbound' ? '← Email' : '→ Email'; if (item.type === 'call') return item.direction === 'inbound' ? '← Call' : '→ Call'; if (item.type === 'note') return 'Note'; if (item.type === 'task') return item.completed ? '✓ Task' : 'Task'; if (item.type === 'event') return item.event || 'Event'; return item.type; }; // Filter tabs include events const ACTIVITY_TABS = [['all','All'],['sms','SMS'],['email','Email'],['notes','Notes'],['tasks','Tasks'],['event','Events']]; return (
{/* Contacts List */}
CRM Directory

Contacts

setSearch(e.target.value)} placeholder="Search name, email, phone, tags..." className="w-full bg-slate-900/60 border border-slate-800 rounded-xl pl-9 pr-4 py-2 text-xs text-slate-300 outline-none focus:border-blue-500/50 placeholder:text-slate-700" />
{showContactForm && (
New Contact
{[['firstName','First Name'],['lastName','Last Name'],['email','Email'],['phone','Phone']].map(([k,l]) => ( setContactForm(p=>({...p,[k]:e.target.value}))} placeholder={l} className="bg-slate-900/80 border border-slate-700/60 rounded-lg px-3 py-2 text-xs text-white outline-none focus:border-blue-500/50 placeholder:text-slate-700" /> ))}
)}
{loadingTabs.contacts ? (
) : ( {['Contact', 'Email', 'Phone', 'Tags', 'Added'].map((h, i) => ( ))} {filtered.map(c => ( openContact(c)} className={`border-b border-slate-800/30 cursor-pointer transition-colors hover:bg-slate-800/20 ${activeContact?.id === c.id ? 'bg-blue-600/5 border-l-2 border-blue-500' : ''}`}> ))}
{h}
{c.name}
{c.source &&
{c.source}
}
{c.email || '—'} {c.phone || '—'}
{(c.tags || []).slice(0, 2).map((t, i) => ( {t} ))}
{fmtDate(c.createdAt)}
)} {!loadingTabs.contacts && filtered.length === 0 && (
{contacts.length === 0 ? 'No contacts — click Refresh' : 'No matches'}
)}
{/* Contact Detail Panel */} {activeContact && (
Contact
{activeContact.name}
{activeContact.source || 'Direct'}
{/* Contact info */}
{activeContact.email && (
{activeContact.email}
)} {activeContact.phone && (
{activeContact.phone}
)} {(activeContact.tags||[]).length > 0 && (
{activeContact.tags.map((t,i) => ( {t} ))}
)}
ID: {activeContact.id}
{/* Send Message */}
Send Message
{['SMS', 'Email'].map(t => ( ))}