// CRM Module
function CRM() {
const [clients, setClients] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const [selected, setSelected] = React.useState(null);
const [detailNotes, setDetailNotes] = React.useState([]);
const [note, setNote] = React.useState('');
const [addModal, setAddModal] = React.useState(false);
const [editModal, setEditModal] = React.useState(false);
const [delConfirm, setDelConfirm] = React.useState(false);
const [saving, setSaving] = React.useState(false);
const emptyForm = { name: '', contact: '', email: '', phone: '', city: '', segment: 'SMB', status: 'Prospect' };
const [form, setForm] = React.useState(emptyForm);
const [editForm, setEditForm] = React.useState(emptyForm);
const [toast, setToast] = React.useState(null);
const showToast = (msg, type = 'success') => { setToast({ msg, type }); setTimeout(() => setToast(null), 3000); };
React.useEffect(() => {
sb.from('clients').select('*').order('name').then(({ data }) => {
if (data) setClients(data);
setLoading(false);
});
}, []);
const openClient = (c) => {
setSelected(c);
setDetailNotes(c.notes || []);
setNote('');
};
const closeClient = () => { setSelected(null); setDetailNotes([]); setNote(''); };
const saveNote = async () => {
if (!note.trim() || !selected) return;
const updated = [...detailNotes, note];
setDetailNotes(updated);
setNote('');
await sb.from('clients').update({ notes: updated }).eq('id', selected.id);
setClients(prev => prev.map(cl => cl.id === selected.id ? { ...cl, notes: updated } : cl));
showToast('Anotação salva!');
};
const openEdit = () => {
setEditForm({ name: selected.name, contact: selected.contact || '', email: selected.email || '', phone: selected.phone || '', city: selected.city || '', segment: selected.segment || 'SMB', status: selected.status || 'Prospect' });
setEditModal(true);
};
const handleEditSave = async () => {
if (!editForm.name.trim()) { showToast('Preencha o nome.', 'danger'); return; }
setSaving(true);
const { data, error } = await sb.from('clients').update({
name: editForm.name, contact: editForm.contact, email: editForm.email,
phone: editForm.phone, city: editForm.city, segment: editForm.segment, status: editForm.status,
}).eq('id', selected.id).select().single();
setSaving(false);
if (error) { showToast('Erro ao salvar.', 'danger'); return; }
setClients(prev => prev.map(c => c.id === data.id ? data : c).sort((a, b) => a.name.localeCompare(b.name)));
setSelected(data);
setDetailNotes(data.notes || []);
setEditModal(false);
showToast('Cliente atualizado!');
};
const handleDelete = async () => {
const id = selected.id;
const nome = selected.name;
await sb.from('clients').delete().eq('id', id);
setClients(prev => prev.filter(c => c.id !== id));
setDelConfirm(false);
closeClient();
showToast(`${nome} excluído.`);
};
const handleCreate = async () => {
if (!form.name.trim()) { showToast('Preencha o nome do cliente.', 'danger'); return; }
setSaving(true);
const { data, error } = await sb.from('clients').insert({
name: form.name, contact: form.contact, email: form.email,
phone: form.phone, city: form.city, segment: form.segment,
status: form.status, events_count: 0, total_spent: 0, notes: [],
}).select().single();
setSaving(false);
if (error) { showToast('Erro ao adicionar cliente.', 'danger'); return; }
setClients(prev => [...prev, data].sort((a, b) => a.name.localeCompare(b.name)));
setAddModal(false);
setForm(emptyForm);
showToast(`${data.name} adicionado com sucesso!`);
};
const statusColor = { 'Ativo': 'success', 'Prospect': 'warning', 'Inativo': 'default' };
const segmentColor = { 'Enterprise': 'primary', 'SMB': 'default', 'PF': 'purple' };
const totalAtivos = clients.filter(c => c.status === 'Ativo').length;
const receitaTotal = clients.reduce((s, c) => s + (c.total_spent || 0), 0);
const ticketMedio = clients.length > 0 ? receitaTotal / clients.length : 0;
const timeline = [
{ icon: 'calendar', text: 'Evento confirmado', date: '22 Abr 2026', color: DS.colors.primary },
{ icon: 'budget', text: 'Orçamento enviado', date: '18 Abr 2026', color: DS.colors.warning },
{ icon: 'message', text: 'Reunião de briefing realizada', date: '10 Abr 2026', color: DS.colors.success },
];
return (
{/* Header */}
Clientes (CRM)
Gerencie relacionamentos e histórico de clientes
setAddModal(true)}>Novo cliente
{/* Stats */}
{[
{ label: 'Clientes ativos', value: totalAtivos, color: DS.colors.primary },
{ label: 'Receita total', value: 'R$ ' + (receitaTotal / 1000000).toFixed(1) + 'M', color: DS.colors.success },
{ label: 'Ticket médio', value: 'R$ ' + Math.round(ticketMedio / 1000) + 'k', color: DS.colors.warning },
].map(s => (
{s.label}
{s.value}
))}
{/* Table */}
{loading ? : (
<>
EmpresaContatoÚltimo eventoEventosSegmentoStatusTotal
{clients.length === 0
?
: clients.map((c, i) => (
openClient(c)}
style={{ display: 'grid', gridTemplateColumns: '2fr 1.2fr 1fr 0.8fr 0.8fr 0.8fr 0.8fr', padding: '14px 24px', borderBottom: i < clients.length - 1 ? `1px solid ${DS.colors.border}` : 'none', alignItems: 'center', cursor: 'pointer', transition: 'background 0.1s' }}
onMouseEnter={e => e.currentTarget.style.background = '#F8FAFC'}
onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
>
{c.contact}
{(c.last_event || '').substring(0, 18)}{(c.last_event || '').length > 18 ? '…' : ''}
{c.events_count || 0}
{c.segment}
{c.status}
R$ {((c.total_spent || 0) / 1000).toFixed(0)}k
))
}
>
)}
{/* ── Popup detalhe do cliente ── */}
Editar
setDelConfirm(true)}>Excluir
)}>
{selected && (
{/* Coluna esquerda */}
{/* Stats */}
{[
{ label: 'Total investido', value: 'R$ ' + (selected.total_spent || 0).toLocaleString('pt-BR'), color: DS.colors.primary },
{ label: 'Eventos realizados', value: selected.events_count || 0, color: DS.colors.success },
{ label: 'Ticket médio', value: selected.events_count > 0 ? 'R$ ' + Math.round((selected.total_spent || 0) / selected.events_count).toLocaleString('pt-BR') : '—', color: DS.colors.warning },
].map(s => (
))}
{/* Timeline */}
Linha do tempo
{timeline.map((t, i) => (
{i < timeline.length - 1 &&
}
))}
{/* Notas */}
Anotações & Follow-up
{detailNotes.length === 0
?
Nenhuma anotação ainda.
: detailNotes.map((n, i) => (
{n}
))
}
setNote(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') saveNote(); }}
placeholder="Adicionar anotação..."
style={{ flex: 1, border: `1px solid ${DS.colors.border}`, borderRadius: DS.radius.sm, padding: '7px 11px', fontSize: 13, outline: 'none', fontFamily: 'inherit' }}
onFocus={e => e.target.style.borderColor = DS.colors.primary}
onBlur={e => e.target.style.borderColor = DS.colors.border}
/>
Salvar
{/* Coluna direita — informações */}
{/* Avatar + badges */}
{selected.name}
{selected.contact}
{selected.segment}
{selected.status}
selected.email ? window.open(`mailto:${selected.email}`) : showToast('E-mail não cadastrado.', 'danger')}>E-mail
selected.phone ? window.open(`tel:${selected.phone.replace(/\D/g, '')}`) : showToast('Telefone não cadastrado.', 'danger')}>Ligar
{/* Dados */}
Contato
{[
{ icon: 'mail', label: selected.email || '—' },
{ icon: 'phone', label: selected.phone || '—' },
{ icon: 'map', label: selected.city || '—' },
].map(r => (
{r.label}
))}
{/* Último evento */}
{selected.last_event && (
Último evento
{selected.last_event}
)}
)}
{/* Modal editar cliente */}
setEditModal(false)} title="Editar Cliente" width={560}>
setEditForm(f => ({ ...f, name: v }))} placeholder="Ex: TechCorp Brasil" />
setEditForm(f => ({ ...f, contact: v }))} placeholder="Ex: Marcelo Andrade" />
setEditForm(f => ({ ...f, city: v }))} placeholder="Ex: São Paulo" />
setEditForm(f => ({ ...f, email: v }))} placeholder="Ex: contato@empresa.com" />
setEditForm(f => ({ ...f, phone: v }))} placeholder="Ex: (11) 3291-4400" />
setEditModal(false)}>Cancelar
{saving ? 'Salvando…' : 'Salvar alterações'}
{/* Confirmação de exclusão */}
setDelConfirm(false)} title="Excluir cliente" width={400}>
Tem certeza que deseja excluir {selected?.name}? Esta ação não pode ser desfeita.
setDelConfirm(false)}>Cancelar
Excluir
{/* Modal novo cliente */}
{ setAddModal(false); setForm(emptyForm); }} title="Novo Cliente" width={560}>
setForm(f => ({ ...f, name: v }))} placeholder="Ex: TechCorp Brasil" />
setForm(f => ({ ...f, contact: v }))} placeholder="Ex: Marcelo Andrade" />
setForm(f => ({ ...f, city: v }))} placeholder="Ex: São Paulo" />
setForm(f => ({ ...f, email: v }))} placeholder="Ex: contato@empresa.com" />
setForm(f => ({ ...f, phone: v }))} placeholder="Ex: (11) 3291-4400" />
setForm(f => ({ ...f, segment: v }))} options={[
{ value: 'Enterprise', label: 'Enterprise' },
{ value: 'SMB', label: 'SMB' },
{ value: 'PF', label: 'Pessoa Física' },
]} />
setForm(f => ({ ...f, status: v }))} options={[
{ value: 'Prospect', label: 'Prospect' },
{ value: 'Ativo', label: 'Ativo' },
{ value: 'Inativo', label: 'Inativo' },
]} />
{ setAddModal(false); setForm(emptyForm); }}>Cancelar
{saving ? 'Salvando…' : 'Adicionar cliente'}
{toast && setToast(null)} />}
);
}
Object.assign(window, { CRM });