| @@ -1638,8 +1638,26 @@ | ||
| 1638 | 1638 | }); |
| 1639 | 1639 | $wrap.html(html); |
| 1640 | 1640 | } |
| 1641 | 1641 | |
| 1642 | + // A lead's key is its email address, or "session:<id>" when the capture | |
| 1643 | + // had no address (c0cfaf). Email keys are lowercased exactly as before; | |
| 1644 | + // session keys pass through untouched, because the server resolves them | |
| 1645 | + // back to a real session id and case matters there. Both shapes travel on | |
| 1646 | + // the same wire field the tab has always used, so the selection set, the | |
| 1647 | + // delete call and the export call need no new parameter. | |
| 1648 | + function leadKeyOf(r) { | |
| 1649 | + const email = (r && r.email ? String(r.email) : '').trim(); | |
| 1650 | + if (email) return email.toLowerCase(); | |
| 1651 | + if (r && r.lead_key) return String(r.lead_key); | |
| 1652 | + return 'session:' + (r && r.latest_session_id ? r.latest_session_id : ''); | |
| 1653 | + } | |
| 1654 | + | |
| 1655 | + function normalizeLeadKey(value) { | |
| 1656 | + const key = (value === undefined || value === null) ? '' : String(value); | |
| 1657 | + return key.indexOf('session:') === 0 ? key : key.toLowerCase(); | |
| 1658 | + } | |
| 1659 | + | |
| 1642 | 1660 | function renderLeadsTable(rows) { |
| 1643 | 1661 | const $tbody = $('#mxch-leads-tbody'); |
| 1644 | 1662 | if (!rows || rows.length === 0) { |
| 1645 | 1663 | $tbody.html(` |
| @@ -1654,9 +1672,9 @@ | ||
| 1654 | 1672 | } |
| 1655 | 1673 | |
| 1656 | 1674 | let html = ''; |
| 1657 | 1675 | rows.forEach(function(r) { |
| 1658 | - const emailKey = (r.email || '').toLowerCase(); | |
| 1676 | + const emailKey = leadKeyOf(r); | |
| 1659 | 1677 | const isChecked = leadsState.selected.has(emailKey) ? ' checked' : ''; |
| 1660 | 1678 | // Status: 'active' (has conversations), 'chat_deleted' (admin removed the chat), 'orphan' (no chat ever). |
| 1661 | 1679 | const status = r.status || (r.is_orphan ? 'orphan' : 'active'); |
| 1662 | 1680 | const isOrphan = (status === 'orphan'); |
| @@ -1663,12 +1681,21 @@ | ||
| 1663 | 1681 | const isChatDeleted = (status === 'chat_deleted'); |
| 1664 | 1682 | const nameLine = r.name |
| 1665 | 1683 | ? `<span class="mxch-leads-lead-name">${escapeHtmlLeads(r.name)}</span>` |
| 1666 | 1684 | : ''; |
| 1685 | + // A lead captured with no email address (c0cfaf) still has a | |
| 1686 | + // name; show that on the primary line rather than an empty cell, | |
| 1687 | + // and say plainly that there is no address. | |
| 1688 | + const primaryLine = r.email | |
| 1689 | + ? `<span class="mxch-leads-lead-email" title="${escapeHtmlLeads(r.email)}">${escapeHtmlLeads(r.email)}</span>` | |
| 1690 | + : `<span class="mxch-leads-lead-email">${escapeHtmlLeads(r.name || 'Unnamed lead')}</span>`; | |
| 1691 | + const secondaryLine = r.email | |
| 1692 | + ? nameLine | |
| 1693 | + : '<span class="mxch-leads-muted">No email address</span>'; | |
| 1667 | 1694 | const leadCell = ` |
| 1668 | 1695 | <div class="mxch-leads-lead-cell"> |
| 1669 | - <span class="mxch-leads-lead-email" title="${escapeHtmlLeads(r.email)}">${escapeHtmlLeads(r.email)}</span> | |
| 1670 | - ${nameLine} | |
| 1696 | + ${primaryLine} | |
| 1697 | + ${secondaryLine} | |
| 1671 | 1698 | </div>`; |
| 1672 | 1699 | let countCell; |
| 1673 | 1700 | if (isOrphan) { |
| 1674 | 1701 | countCell = `<span class="mxch-leads-pill mxch-leads-pill-orphan">Orphan</span>`; |
| @@ -1697,15 +1724,15 @@ | ||
| 1697 | 1724 | <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg> |
| 1698 | 1725 | <span>View convo</span> |
| 1699 | 1726 | </button>` |
| 1700 | 1727 | : ''; |
| 1701 | - const deleteBtn = `<button type="button" class="mxch-btn mxch-btn-ghost mxch-btn-sm mxch-btn-danger-ghost mxch-leads-delete-row" data-email="${escapeHtmlLeads(r.email)}" title="Delete lead"> | |
| 1728 | + const deleteBtn = `<button type="button" class="mxch-btn mxch-btn-ghost mxch-btn-sm mxch-btn-danger-ghost mxch-leads-delete-row" data-email="${escapeHtmlLeads(emailKey)}" title="Delete lead"> | |
| 1702 | 1729 | <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg> |
| 1703 | 1730 | </button>`; |
| 1704 | 1731 | |
| 1705 | 1732 | const rowStateClass = isOrphan ? ' is-orphan' : (isChatDeleted ? ' is-chat-deleted' : ''); |
| 1706 | 1733 | html += ` |
| 1707 | - <tr class="mxch-leads-row${rowStateClass}" data-email="${escapeHtmlLeads(r.email)}"> | |
| 1734 | + <tr class="mxch-leads-row${rowStateClass}" data-email="${escapeHtmlLeads(emailKey)}"> | |
| 1708 | 1735 | <td class="mxch-leads-col-check"><input type="checkbox" class="mxch-leads-rowcheck"${isChecked}></td> |
| 1709 | 1736 | <td class="mxch-leads-col-lead">${leadCell}</td> |
| 1710 | 1737 | <td class="mxch-leads-col-count">${countCell}</td> |
| 1711 | 1738 | <td class="mxch-leads-col-last">${lastCell}</td> |
| @@ -1832,9 +1859,9 @@ | ||
| 1832 | 1859 | $('#mxch-leads-select-all').on('change', function() { |
| 1833 | 1860 | const on = $(this).is(':checked'); |
| 1834 | 1861 | $('.mxch-leads-rowcheck').prop('checked', on); |
| 1835 | 1862 | $('.mxch-leads-row').each(function() { |
| 1836 | - const email = ($(this).data('email') || '').toString().toLowerCase(); | |
| 1863 | + const email = normalizeLeadKey($(this).data('email')); | |
| 1837 | 1864 | if (on) { |
| 1838 | 1865 | leadsState.selected.add(email); |
| 1839 | 1866 | } else { |
| 1840 | 1867 | leadsState.selected.delete(email); |
| @@ -1844,9 +1871,9 @@ | ||
| 1844 | 1871 | }); |
| 1845 | 1872 | |
| 1846 | 1873 | // Row checkbox |
| 1847 | 1874 | $leads().on('change', '.mxch-leads-rowcheck', function() { |
| 1848 | - const email = ($(this).closest('.mxch-leads-row').data('email') || '').toString().toLowerCase(); | |
| 1875 | + const email = normalizeLeadKey($(this).closest('.mxch-leads-row').data('email')); | |
| 1849 | 1876 | if ($(this).is(':checked')) { |
| 1850 | 1877 | leadsState.selected.add(email); |
| 1851 | 1878 | } else { |
| 1852 | 1879 | leadsState.selected.delete(email); |
| @@ -1912,9 +1939,9 @@ | ||
| 1912 | 1939 | success: function(response) { |
| 1913 | 1940 | $btn.prop('disabled', false).text('Delete permanently'); |
| 1914 | 1941 | closeLeadsConfirm(); |
| 1915 | 1942 | if (response && response.success) { |
| 1916 | - emails.forEach(function(e) { leadsState.selected.delete(e.toLowerCase()); }); | |
| 1943 | + emails.forEach(function(e) { leadsState.selected.delete(normalizeLeadKey(e)); }); | |
| 1917 | 1944 | loadLeads(leadsState.page); |
| 1918 | 1945 | } else { |
| 1919 | 1946 | alert((response && response.data && response.data.message) || 'Failed to delete leads.'); |
| 1920 | 1947 | } |