import { useEffect, useMemo, useState } from 'react'; import { apiRequest } from '../../../shared/services/apiClient'; import { getCurrentUser } from '../../auth/services/sessionService'; import { listContactProfiles } from '../../chat/services/contactProfileService'; import { DataPanel } from './DataPanel'; import { listTemplates } from '../services/templateService'; const inputStyle = { width: '100%', border: '1px solid var(--color-border)', borderRadius: 14, padding: '0.85rem 0.9rem', background: '#fff', color: 'var(--color-text)', fontWeight: 600, }; function getUserId(user) { const value = user?.databaseId || user?.id; const numeric = Number(value); return Number.isFinite(numeric) ? numeric : null; } function normalizePhoneToChatId(value) { const digits = String(value || '').replace(/\D/g, ''); if (!digits) return ''; return `${digits}@c.us`; } function getPhoneFromChatId(chatId) { return String(chatId || '').split('@')[0].replace(/\D/g, ''); } function normalizeContact(contact) { const phone = String(contact.phone || getPhoneFromChatId(contact.chat_id)).replace(/\D/g, ''); return { id: contact.chat_id || normalizePhoneToChatId(phone), name: contact.name || phone || 'Contato sem nome', company: contact.company || '', phone, chatId: contact.chat_id || normalizePhoneToChatId(phone), }; } function renderPreview(content, variables) { const name = variables?.nome || 'colaborador'; return String(content || '') .replace(/\{nome\}/gi, name || 'colaborador') .replace(/\{cliente\}/gi, name || 'colaborador') .replace(/\{data\}/gi, variables?.data || '{data}') .replace(/\{link\}/gi, variables?.link || '{link}') .replace(/\{variavel\}/gi, variables?.variavel || '{variavel}') .replace(/\{variável\}/gi, variables?.variavel || '{variável}'); } async function startAttendance(payload) { return apiRequest('/whatsapp/start-attendance', { method: 'POST', body: JSON.stringify(payload), fallbackMessage: 'Falha ao enviar disparo.', }); } export function MassMessagePanel({ areas, managedAreaNames = [], mode = 'admin', isMobile = false, }) { const currentUserId = getUserId(getCurrentUser()); const isAdmin = mode === 'admin'; const visibleAreaNames = isAdmin ? [] : managedAreaNames; const [templates, setTemplates] = useState([]); const [contacts, setContacts] = useState([]); const [selectedAreaId, setSelectedAreaId] = useState(''); const [selectedTemplateId, setSelectedTemplateId] = useState(''); const [defaultName, setDefaultName] = useState('colaborador'); const [templateDate, setTemplateDate] = useState(''); const [templateLink, setTemplateLink] = useState(''); const [templateCustomVariable, setTemplateCustomVariable] = useState(''); const [numbersText, setNumbersText] = useState(''); const [contactSearch, setContactSearch] = useState(''); const [selectedContactIds, setSelectedContactIds] = useState([]); const [isSending, setIsSending] = useState(false); const [results, setResults] = useState([]); const [status, setStatus] = useState(''); useEffect(() => { let isMounted = true; listTemplates() .then((data) => { if (!isMounted) return; const approved = Array.isArray(data) ? data.filter((template) => { const isApproved = template.status === 'approved'; const isManaged = !visibleAreaNames.length || visibleAreaNames.includes(template.area_nome); return isApproved && isManaged; }) : []; setTemplates(approved); setSelectedTemplateId((current) => current || (approved[0]?.id ? String(approved[0].id) : '')); }) .catch((error) => { if (isMounted) setStatus(error.message); }); return () => { isMounted = false; }; }, [visibleAreaNames.join('|')]); useEffect(() => { let isMounted = true; listContactProfiles() .then((data) => { if (!isMounted) return; setContacts(Array.isArray(data) ? data.map(normalizeContact).filter((contact) => contact.phone) : []); }) .catch((error) => { if (isMounted) setStatus(error.message); }); return () => { isMounted = false; }; }, []); const selectedTemplate = templates.find((template) => String(template.id) === String(selectedTemplateId)); const filteredTemplates = useMemo(() => { if (!selectedAreaId) return templates; return templates.filter((template) => String(template.area_id || '') === String(selectedAreaId)); }, [templates, selectedAreaId]); const numbers = useMemo( () => numbersText .split(/\r?\n|,|;/) .map((item) => item.trim()) .filter(Boolean), [numbersText], ); const selectedContacts = useMemo( () => contacts.filter((contact) => selectedContactIds.includes(contact.id)), [contacts, selectedContactIds], ); const filteredContacts = useMemo(() => { const search = contactSearch.trim().toLowerCase(); if (!search) return contacts; return contacts.filter((contact) => `${contact.name} ${contact.company} ${contact.phone}`.toLowerCase().includes(search), ); }, [contacts, contactSearch]); const recipients = useMemo(() => { const items = []; const seen = new Set(); numbers.forEach((number) => { const chatId = normalizePhoneToChatId(number); if (!chatId || seen.has(chatId)) return; seen.add(chatId); items.push({ id: chatId, number, chatId, name: defaultName, }); }); return items; }, [defaultName, numbers]); useEffect(() => { if (selectedTemplate && filteredTemplates.some((template) => String(template.id) === String(selectedTemplateId))) { return; } setSelectedTemplateId(filteredTemplates[0]?.id ? String(filteredTemplates[0].id) : ''); }, [filteredTemplates, selectedTemplate, selectedTemplateId]); function toggleContact(contact) { const phone = String(contact.phone || '').replace(/\D/g, ''); if (!phone) return; setSelectedContactIds((current) => { const isSelected = current.includes(contact.id); return isSelected ? current.filter((id) => id !== contact.id) : [...current, contact.id]; }); setNumbersText((current) => { const currentNumbers = current .split(/\r?\n|,|;/) .map((item) => item.trim()) .filter(Boolean); const exists = currentNumbers.some((item) => String(item).replace(/\D/g, '') === phone); const nextNumbers = exists ? currentNumbers.filter((item) => String(item).replace(/\D/g, '') !== phone) : [...currentNumbers, phone]; return nextNumbers.join('\n'); }); } function clearSelectedContacts() { const selectedPhones = new Set(selectedContacts.map((contact) => contact.phone)); setSelectedContactIds([]); setNumbersText((current) => current .split(/\r?\n|,|;/) .map((item) => item.trim()) .filter(Boolean) .filter((item) => !selectedPhones.has(String(item).replace(/\D/g, ''))) .join('\n'), ); } async function sendSelectedRecipients() { if (!currentUserId) { setStatus('Não foi possível identificar o usuário logado.'); return; } if (!selectedTemplateId) { setStatus('Selecione um template aprovado.'); return; } if (!recipients.length) { setStatus('Informe ao menos um número ou selecione contatos da agenda.'); return; } setIsSending(true); setResults([]); setStatus(''); const nextResults = []; for (const recipient of recipients) { if (!recipient.chatId) { nextResults.push({ number: recipient.number, status: 'erro', detail: 'Número inválido' }); continue; } try { await startAttendance({ to: recipient.chatId, templateId: Number(selectedTemplateId), userId: currentUserId, areaId: selectedTemplate?.area_id || null, variables: { nome: defaultName, cliente: defaultName, data: templateDate, link: templateLink, variavel: templateCustomVariable, 'variável': templateCustomVariable, }, }); nextResults.push({ number: recipient.number, status: 'enviado', detail: `${recipient.name || 'Contato'} - template enviado e atendimento iniciado`, }); } catch (error) { nextResults.push({ number: recipient.number, status: 'erro', detail: error.message }); } setResults([...nextResults]); } setStatus(`Disparo finalizado: ${nextResults.filter((item) => item.status === 'enviado').length} enviados.`); setIsSending(false); } return (