/* AAU CRM — Hàng đợi "Nghi trùng" (identity resolution). Gợi ý cặp lead có thể là 1 người (cùng SĐT/email, hoặc tên giống + khác kênh — vd MKT chat FB lấy SĐT → Sales add Zalo, tên FB ≠ tên Zalo). Sales QUYẾT: Gộp (1 contact, giữ alias) hoặc "Không phải" (không gợi lại). KHÔNG tự gộp ca mơ hồ. */ function DuplicateReview() { const live = !!(window.API && window.API.enabled); const [pairs, setPairs] = useState([]); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(''); const reload = () => { if (!live || !window.API.suspectedDuplicates) { setLoading(false); return; } setLoading(true); window.API.suspectedDuplicates().then(r => { setPairs((r && r.pairs) || []); }).catch(() => setPairs([])).finally(() => setLoading(false)); }; useEffect(() => { reload(); }, [live]); const chLabel = (ch) => (AAU.channels && AAU.channels[ch] || {}).label || ch; const chColor = (ch) => (AAU.channels && AAU.channels[ch] || {}).color || '#8a8f98'; const keyOf = (p) => p.a.id + '|' + p.b.id; const merge = async (p) => { if (!window.confirm(`Gộp "${p.a.name}" và "${p.b.name}" thành 1 contact? (giữ lead tiến xa hơn, tên kia thành bí danh)`)) return; setBusy('mg:' + keyOf(p)); try { await window.API.mergeLeads(p.a.id, p.b.id); if (window.API.refreshLeads) await window.API.refreshLeads(); reload(); } catch (e) { window.alert('Gộp lỗi: ' + ((e && e.message) || '')); } finally { setBusy(''); } }; const notDup = async (p) => { setBusy('nd:' + keyOf(p)); try { await window.API.markNotDuplicate(p.a.id, p.b.id); setPairs(ps => ps.filter(x => keyOf(x) !== keyOf(p))); } catch (e) { window.alert('Lỗi: ' + ((e && e.message) || '')); } finally { setBusy(''); } }; const LeadCol = ({ l }) => (
?api=…) để xem cặp nghi trùng từ backend.