PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.2
MxChat – AI Chatbot & Content Generation for WordPress v3.2.2
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | js/mxchat_transcripts.js +521 -17 3.2.13.2.2 View file →
@@ -178,9 +178,11 @@
178 178 $('.mxch-chat-checkbox').prop('checked', isChecked);
179 179
180 180 if (isChecked) {
181 181 $('.mxch-chat-item').each(function() {
182 - selectedSessions.add($(this).data('session-id'));
182 + // Use .attr() — jQuery's .data() coerces "null"/"true"/numeric strings to JS types,
183 + // which causes fetch/delete of those sessions to silently fail.
184 + selectedSessions.add($(this).attr('data-session-id'));
183 185 $(this).addClass('selected');
184 186 });
185 187 $('#mxch-chat-list').addClass('selection-mode');
186 188 } else {
@@ -221,22 +223,48 @@
221 223 $(this).find('svg').css('transform', currentSortOrder === 'asc' ? 'rotate(180deg)' : 'rotate(0deg)');
222 224 loadChatList(currentPage, $('#mxch-search-transcripts').val());
223 225 });
224 226
225 - // Delete selected button
227 + // Delete selected button — opens the shared confirm modal with the "also delete lead" checkbox.
226 228 $('#mxch-delete-selected').on('click', function() {
227 229 const count = selectedSessions.size;
228 230 if (count === 0) return;
231 + openTranscriptConfirm(Array.from(selectedSessions), count);
232 + });
229 233
230 - if (!confirm('Are you sure you want to delete ' + count + ' conversation(s)? This action cannot be undone.')) {
231 - return;
232 - }
234 + // Transcript delete confirm (shared by bulk + individual) ---------------------------
235 + let transcriptConfirmSessionIds = [];
233 236
234 - deleteMultipleSessions(Array.from(selectedSessions));
237 + function openTranscriptConfirm(sessionIds, count) {
238 + transcriptConfirmSessionIds = sessionIds.slice();
239 + const n = count || sessionIds.length;
240 + $('#mxch-transcript-confirm-title').text(n === 1 ? 'Delete conversation?' : 'Delete ' + n + ' conversations?');
241 + $('#mxch-transcript-confirm-body').text(
242 + n === 1
243 + ? 'This removes the conversation and its messages.'
244 + : 'This removes ' + n + ' conversations and their messages.'
245 + );
246 + $('#mxch-transcript-also-delete-lead').prop('checked', false);
247 + $('#mxch-transcript-confirm').fadeIn(120);
248 + }
249 +
250 + function closeTranscriptConfirm() {
251 + $('#mxch-transcript-confirm').fadeOut(120);
252 + transcriptConfirmSessionIds = [];
253 + }
254 +
255 + $(document).on('click', '[data-mxch-transcript-close]', closeTranscriptConfirm);
256 +
257 + $('#mxch-transcript-confirm-go').on('click', function() {
258 + const ids = transcriptConfirmSessionIds.slice();
259 + if (!ids.length) { closeTranscriptConfirm(); return; }
260 + const alsoDeleteLead = $('#mxch-transcript-also-delete-lead').is(':checked');
261 + closeTranscriptConfirm();
262 + deleteMultipleSessions(ids, alsoDeleteLead);
235 263 });
236 264
237 265 // Delete multiple sessions
238 - function deleteMultipleSessions(sessionIds) {
266 + function deleteMultipleSessions(sessionIds, alsoDeleteLead) {
239 267 $.ajax({
240 268 url: ajaxurl,
241 269 type: 'POST',
242 270 data: {
@@ -241,8 +269,9 @@
241 269 type: 'POST',
242 270 data: {
243 271 action: 'mxchat_delete_chat_history',
244 272 delete_session_ids: sessionIds,
273 + also_delete_lead: alsoDeleteLead ? '1' : '0',
245 274 security: $('#mxchat_delete_chat_nonce').val()
246 275 },
247 276 success: function(response) {
248 277 try {
@@ -263,8 +292,13 @@
263 292 }
264 293
265 294 // Reload list
266 295 loadChatList(currentPage, $('#mxch-search-transcripts').val());
296 +
297 + // The Leads tab shares this data (Chat deleted pill, nav badge,
298 + // stats) — invalidate it so switching tabs re-fetches instead of
299 + // showing stale "active lead" rows.
300 + invalidateLeadsData();
267 301 } else if (jsonResponse.error) {
268 302 alert('Error: ' + jsonResponse.error);
269 303 }
270 304 } catch (e) {
@@ -345,9 +379,9 @@
345 379 // Attach checkbox handlers
346 380 $('.mxch-chat-checkbox').on('click', function(e) {
347 381 e.stopPropagation(); // Prevent triggering chat item click
348 382 const $item = $(this).closest('.mxch-chat-item');
349 - const sessionId = $item.data('session-id');
383 + const sessionId = $item.attr('data-session-id');
350 384
351 385 if ($(this).is(':checked')) {
352 386 selectedSessions.add(sessionId);
353 387 $item.addClass('selected');
@@ -363,9 +397,9 @@
363 397 $('.mxch-chat-item').on('click', function(e) {
364 398 // Don't trigger if clicking on checkbox
365 399 if ($(e.target).is('.mxch-chat-checkbox')) return;
366 400
367 - const sessionId = $(this).data('session-id');
401 + const sessionId = $(this).attr('data-session-id');
368 402 selectChat(sessionId);
369 403
370 404 // Update active state
371 405 $('.mxch-chat-item').removeClass('active');
@@ -561,21 +595,16 @@
561 595 $btn.addClass('active');
562 596 }
563 597 });
564 598
565 - // Delete current chat
599 + // Delete current chat — opens the shared confirm modal.
566 600 $('#mxch-delete-current').on('click', function() {
567 601 if (!currentSessionId) return;
568 -
569 - if (!confirm('Are you sure you want to delete this conversation? This action cannot be undone.')) {
570 - return;
571 - }
572 -
573 - deleteSession(currentSessionId);
602 + openTranscriptConfirm([currentSessionId], 1);
574 603 });
575 604
576 605 // Delete session function
577 - function deleteSession(sessionId) {
606 + function deleteSession(sessionId, alsoDeleteLead) {
578 607 $.ajax({
579 608 url: ajaxurl,
580 609 type: 'POST',
581 610 data: {
@@ -580,8 +609,9 @@
580 609 type: 'POST',
581 610 data: {
582 611 action: 'mxchat_delete_chat_history',
583 612 delete_session_ids: [sessionId],
613 + also_delete_lead: alsoDeleteLead ? '1' : '0',
584 614 security: $('#mxchat_delete_chat_nonce').val()
585 615 },
586 616 success: function(response) {
587 617 try {
@@ -1274,6 +1304,480 @@
1274 1304 clearTimeout(resizeTimeout);
1275 1305 resizeTimeout = setTimeout(function() {
1276 1306 initActivityChart();
1277 1307 }, 250);
1308 + });
1309 +
1310 + // ==========================================================================
1311 + // Leads Tab
1312 + // ==========================================================================
1313 +
1314 + const leadsState = {
1315 + loaded: false,
1316 + page: 1,
1317 + perPage: 25,
1318 + totalPages: 1,
1319 + totalCount: 0,
1320 + selected: new Set(),
1321 + filters: {
1322 + search: '',
1323 + dateRange: 'all',
1324 + status: 'all',
1325 + pageUrl: '',
1326 + pageTitle: ''
1327 + },
1328 + pendingDelete: [],
1329 + leadsRows: [] // last-rendered rows for quick lookup
1330 + };
1331 +
1332 + function $leads() { return $('#leads'); }
1333 +
1334 + // Called after a transcript delete from the All Chats side. Marks the Leads tab
1335 + // data stale so the next tab visit re-fetches, and refreshes immediately if the
1336 + // Leads tab happens to already be visible.
1337 + function invalidateLeadsData() {
1338 + leadsState.loaded = false;
1339 + if ($('#leads').hasClass('active')) {
1340 + loadLeads(1);
1341 + }
1342 + }
1343 +
1344 + function escapeHtmlLeads(s) {
1345 + if (s === null || typeof s === 'undefined') return '';
1346 + return String(s)
1347 + .replace(/&/g, '&')
1348 + .replace(/</g, '&lt;')
1349 + .replace(/>/g, '&gt;')
1350 + .replace(/"/g, '&quot;')
1351 + .replace(/'/g, '&#039;');
1352 + }
1353 +
1354 + function leadsFiltersActive() {
1355 + const f = leadsState.filters;
1356 + return f.search !== '' || f.dateRange !== 'all' || f.status !== 'all' || f.pageUrl !== '';
1357 + }
1358 +
1359 + function updateClearFiltersButton() {
1360 + if (leadsFiltersActive()) {
1361 + $('#mxch-leads-clear-filters').show();
1362 + } else {
1363 + $('#mxch-leads-clear-filters').hide();
1364 + }
1365 + }
1366 +
1367 + function setPageFilterChip(url, title) {
1368 + leadsState.filters.pageUrl = url || '';
1369 + leadsState.filters.pageTitle = title || url || '';
1370 + const $chip = $('#mxch-leads-active-page-filter');
1371 + if (url) {
1372 + $chip.find('.mxch-leads-page-chip-label').text('Page: ' + (title || url));
1373 + $chip.show();
1374 + } else {
1375 + $chip.hide();
1376 + }
1377 + updateClearFiltersButton();
1378 + }
1379 +
1380 + function loadLeads(page) {
1381 + if (typeof page === 'number') leadsState.page = page;
1382 +
1383 + const $tbody = $('#mxch-leads-tbody');
1384 + $tbody.html('<tr><td colspan="6" class="mxch-leads-loading"><span class="spinner is-active"></span></td></tr>');
1385 +
1386 + $.ajax({
1387 + url: ajaxurl,
1388 + type: 'POST',
1389 + data: {
1390 + action: 'mxchat_fetch_leads',
1391 + page: leadsState.page,
1392 + per_page: leadsState.perPage,
1393 + search: leadsState.filters.search,
1394 + date_range: leadsState.filters.dateRange,
1395 + status: leadsState.filters.status,
1396 + page_url: leadsState.filters.pageUrl
1397 + },
1398 + success: function(response) {
1399 + leadsState.loaded = true;
1400 + if (!response || !response.success) {
1401 + $tbody.html('<tr><td colspan="7" class="mxch-leads-empty">Error loading leads</td></tr>');
1402 + return;
1403 + }
1404 + leadsState.totalPages = response.total_pages || 1;
1405 + leadsState.totalCount = response.total_count || 0;
1406 + leadsState.leadsRows = response.leads || [];
1407 +
1408 + renderLeadsStats(response.stats || {});
1409 + renderLeadsTopPages(response.top_pages || []);
1410 + renderLeadsTable(response.leads || []);
1411 + renderLeadsCount(response.showing_start, response.showing_end, response.total_count);
1412 + renderLeadsPagination(response.page, response.total_pages);
1413 +
1414 + // Nav badge
1415 + if (response.stats && typeof response.stats.total_leads === 'number') {
1416 + const $badge = $('#mxch-leads-nav-badge');
1417 + if (response.stats.total_leads > 0) {
1418 + $badge.text(response.stats.total_leads).show();
1419 + } else {
1420 + $badge.hide();
1421 + }
1422 + }
1423 + },
1424 + error: function() {
1425 + $tbody.html('<tr><td colspan="7" class="mxch-leads-empty">Error loading leads</td></tr>');
1426 + }
1427 + });
1428 + }
1429 +
1430 + function renderLeadsStats(stats) {
1431 + $('#mxch-leads-stat-total').text(stats.total_leads || 0);
1432 + $('#mxch-leads-stat-new').text(stats.new_this_week || 0);
1433 + $('#mxch-leads-stat-avg').text(stats.avg_convos || 0);
1434 + const pct = stats.orphan_pct || 0;
1435 + $('#mxch-leads-stat-orphan').text(pct + '%');
1436 + const orphanCount = stats.orphan_count || 0;
1437 + $('#mxch-leads-stat-orphan-sub').text(orphanCount + (orphanCount === 1 ? ' lead captured but never chatted' : ' leads captured but never chatted'));
1438 + }
1439 +
1440 + function renderLeadsTopPages(pages) {
1441 + const $wrap = $('#mxch-leads-toppages-list');
1442 + if (!pages || pages.length === 0) {
1443 + $wrap.html('<div class="mxch-leads-empty-mini">No page data yet.</div>');
1444 + return;
1445 + }
1446 + let html = '';
1447 + pages.forEach(function(p) {
1448 + const isActive = leadsState.filters.pageUrl === p.url ? ' is-active' : '';
1449 + html += `
1450 + <button type="button" class="mxch-leads-toppage-row${isActive}" data-url="${escapeHtmlLeads(p.url)}" data-title="${escapeHtmlLeads(p.title)}">
1451 + <span class="mxch-leads-toppage-title">${escapeHtmlLeads(p.title || p.url)}</span>
1452 + <span class="mxch-leads-toppage-count">${p.lead_count}</span>
1453 + </button>
1454 + `;
1455 + });
1456 + $wrap.html(html);
1457 + }
1458 +
1459 + function renderLeadsTable(rows) {
1460 + const $tbody = $('#mxch-leads-tbody');
1461 + if (!rows || rows.length === 0) {
1462 + $tbody.html(`
1463 + <tr><td colspan="6" class="mxch-leads-empty">
1464 + <div class="mxch-leads-empty-wrap">
1465 + <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
1466 + <p>No leads match the current filters.</p>
1467 + </div>
1468 + </td></tr>
1469 + `);
1470 + return;
1471 + }
1472 +
1473 + let html = '';
1474 + rows.forEach(function(r) {
1475 + const emailKey = (r.email || '').toLowerCase();
1476 + const isChecked = leadsState.selected.has(emailKey) ? ' checked' : '';
1477 + // Status: 'active' (has conversations), 'chat_deleted' (admin removed the chat), 'orphan' (no chat ever).
1478 + const status = r.status || (r.is_orphan ? 'orphan' : 'active');
1479 + const isOrphan = (status === 'orphan');
1480 + const isChatDeleted = (status === 'chat_deleted');
1481 + const nameLine = r.name
1482 + ? `<span class="mxch-leads-lead-name">${escapeHtmlLeads(r.name)}</span>`
1483 + : '';
1484 + const leadCell = `
1485 + <div class="mxch-leads-lead-cell">
1486 + <span class="mxch-leads-lead-email" title="${escapeHtmlLeads(r.email)}">${escapeHtmlLeads(r.email)}</span>
1487 + ${nameLine}
1488 + </div>`;
1489 + let countCell;
1490 + if (isOrphan) {
1491 + countCell = `<span class="mxch-leads-pill mxch-leads-pill-orphan">Orphan</span>`;
1492 + } else if (isChatDeleted) {
1493 + countCell = `<span class="mxch-leads-pill mxch-leads-pill-deleted" title="Chat was deleted by an admin">Chat deleted</span>`;
1494 + } else {
1495 + countCell = `<span class="mxch-leads-pill">${r.conversation_count}</span>`;
1496 + }
1497 + const lastCell = escapeHtmlLeads(r.last_seen_display || (isOrphan ? 'No conversation yet' : ''));
1498 + const pageCell = r.top_page_url
1499 + ? `<a href="${escapeHtmlLeads(r.top_page_url)}" target="_blank" rel="noopener" class="mxch-leads-page-link" title="${escapeHtmlLeads(r.top_page_url)}">${escapeHtmlLeads(r.top_page_title || r.top_page_url)}</a>`
1500 + : '<span class="mxch-leads-muted">—</span>';
1501 + // View Convo only for active leads (orphans and chat_deleted have no viewable session).
1502 + const viewBtn = (status === 'active' && r.latest_session_id)
1503 + ? `<button type="button" class="mxch-btn mxch-btn-ghost mxch-btn-sm mxch-leads-view" data-session-id="${escapeHtmlLeads(r.latest_session_id)}" title="View latest conversation">
1504 + <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>
1505 + <span>View convo</span>
1506 + </button>`
1507 + : '';
1508 + 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">
1509 + <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>
1510 + </button>`;
1511 +
1512 + const rowStateClass = isOrphan ? ' is-orphan' : (isChatDeleted ? ' is-chat-deleted' : '');
1513 + html += `
1514 + <tr class="mxch-leads-row${rowStateClass}" data-email="${escapeHtmlLeads(r.email)}">
1515 + <td class="mxch-leads-col-check"><input type="checkbox" class="mxch-leads-rowcheck"${isChecked}></td>
1516 + <td class="mxch-leads-col-lead">${leadCell}</td>
1517 + <td class="mxch-leads-col-count">${countCell}</td>
1518 + <td class="mxch-leads-col-last">${lastCell}</td>
1519 + <td class="mxch-leads-col-page">${pageCell}</td>
1520 + <td class="mxch-leads-col-actions">${viewBtn}${deleteBtn}</td>
1521 + </tr>
1522 + `;
1523 + });
1524 +
1525 + $tbody.html(html);
1526 + updateLeadsSelectionUI();
1527 + }
1528 +
1529 + function renderLeadsCount(start, end, total) {
1530 + if (!total) {
1531 + $('#mxch-leads-count').text('0 leads');
1532 + } else {
1533 + $('#mxch-leads-count').text(start + '-' + end + ' / ' + total + ' leads');
1534 + }
1535 + }
1536 +
1537 + function renderLeadsPagination(currentPage, totalPages) {
1538 + const $c = $('#mxch-leads-pagination');
1539 + if (!totalPages || totalPages <= 1) { $c.html(''); return; }
1540 + let html = '<div class="mxch-pagination-btns">';
1541 + if (currentPage > 1) {
1542 + html += `<button class="mxch-page-btn" data-page="${currentPage - 1}">&laquo;</button>`;
1543 + }
1544 + html += `<span class="mxch-page-info">${currentPage} / ${totalPages}</span>`;
1545 + if (currentPage < totalPages) {
1546 + html += `<button class="mxch-page-btn" data-page="${currentPage + 1}">&raquo;</button>`;
1547 + }
1548 + html += '</div>';
1549 + $c.html(html);
1550 + }
1551 +
1552 + function updateLeadsSelectionUI() {
1553 + const count = leadsState.selected.size;
1554 + const $countEl = $('#mxch-leads-selected-count');
1555 + const $del = $('#mxch-leads-delete-selected');
1556 + if (count > 0) {
1557 + $countEl.text(count + ' selected').addClass('has-selection');
1558 + $del.prop('disabled', false);
1559 + } else {
1560 + $countEl.text('0').removeClass('has-selection');
1561 + $del.prop('disabled', true);
1562 + }
1563 + // Selected-scope export menu items
1564 + $('#mxch-leads-export-menu button[data-scope="selected"]').prop('disabled', count === 0);
1565 +
1566 + // Select-all checkbox state
1567 + const $checks = $('.mxch-leads-rowcheck');
1568 + const checked = $checks.filter(':checked').length;
1569 + const total = $checks.length;
1570 + $('#mxch-leads-select-all').prop('checked', total > 0 && checked === total);
1571 + $('#mxch-leads-select-all').prop('indeterminate', checked > 0 && checked < total);
1572 + }
1573 +
1574 + // Trigger leads load when switching to the tab (works alongside the main nav handler above).
1575 + $('.mxch-nav-link[data-target="leads"], .mxch-mobile-nav-link[data-target="leads"]').on('click', function() {
1576 + if (!leadsState.loaded) {
1577 + loadLeads(1);
1578 + }
1579 + });
1580 +
1581 + // Filter: search (debounced)
1582 + let leadsSearchTimer;
1583 + $('#mxch-leads-search').on('input', function() {
1584 + clearTimeout(leadsSearchTimer);
1585 + const val = $(this).val();
1586 + leadsSearchTimer = setTimeout(function() {
1587 + leadsState.filters.search = (val || '').trim();
1588 + updateClearFiltersButton();
1589 + loadLeads(1);
1590 + }, 300);
1591 + });
1592 +
1593 + // Filter: date range
1594 + $('#mxch-leads-date-range').on('change', function() {
1595 + leadsState.filters.dateRange = $(this).val();
1596 + updateClearFiltersButton();
1597 + loadLeads(1);
1598 + });
1599 +
1600 + // Filter: status
1601 + $('#mxch-leads-status').on('change', function() {
1602 + leadsState.filters.status = $(this).val();
1603 + updateClearFiltersButton();
1604 + loadLeads(1);
1605 + });
1606 +
1607 + // Clear filters
1608 + $('#mxch-leads-clear-filters').on('click', function() {
1609 + leadsState.filters = { search: '', dateRange: 'all', status: 'all', pageUrl: '', pageTitle: '' };
1610 + $('#mxch-leads-search').val('');
1611 + $('#mxch-leads-date-range').val('all');
1612 + $('#mxch-leads-status').val('all');
1613 + setPageFilterChip('', '');
1614 + loadLeads(1);
1615 + });
1616 +
1617 + // Remove page chip
1618 + $leads().on('click', '.mxch-leads-page-chip-remove', function() {
1619 + setPageFilterChip('', '');
1620 + loadLeads(1);
1621 + });
1622 +
1623 + // Top Pages click -> set filter
1624 + $leads().on('click', '.mxch-leads-toppage-row', function() {
1625 + const url = $(this).data('url') || '';
1626 + const title = $(this).data('title') || '';
1627 + setPageFilterChip(url, title);
1628 + loadLeads(1);
1629 + });
1630 +
1631 + // Pagination click
1632 + $leads().on('click', '#mxch-leads-pagination .mxch-page-btn', function() {
1633 + const p = parseInt($(this).data('page'), 10);
1634 + if (p > 0) loadLeads(p);
1635 + });
1636 +
1637 + // Select-all
1638 + $('#mxch-leads-select-all').on('change', function() {
1639 + const on = $(this).is(':checked');
1640 + $('.mxch-leads-rowcheck').prop('checked', on);
1641 + $('.mxch-leads-row').each(function() {
1642 + const email = ($(this).data('email') || '').toString().toLowerCase();
1643 + if (on) {
1644 + leadsState.selected.add(email);
1645 + } else {
1646 + leadsState.selected.delete(email);
1647 + }
1648 + });
1649 + updateLeadsSelectionUI();
1650 + });
1651 +
1652 + // Row checkbox
1653 + $leads().on('change', '.mxch-leads-rowcheck', function() {
1654 + const email = ($(this).closest('.mxch-leads-row').data('email') || '').toString().toLowerCase();
1655 + if ($(this).is(':checked')) {
1656 + leadsState.selected.add(email);
1657 + } else {
1658 + leadsState.selected.delete(email);
1659 + }
1660 + updateLeadsSelectionUI();
1661 + });
1662 +
1663 + // View convo -> jump to All Chats tab and open the session
1664 + $leads().on('click', '.mxch-leads-view', function() {
1665 + const sid = $(this).attr('data-session-id');
1666 + if (!sid) return;
1667 + $('.mxch-nav-link[data-target="all-chats"]').trigger('click');
1668 + // selectChat is defined earlier in this closure
1669 + if (typeof selectChat === 'function') {
1670 + setTimeout(function() { selectChat(sid); }, 30);
1671 + }
1672 + });
1673 +
1674 + // Row delete -> confirm for one
1675 + $leads().on('click', '.mxch-leads-delete-row', function() {
1676 + const email = $(this).data('email');
1677 + if (!email) return;
1678 + openLeadsConfirm([String(email)]);
1679 + });
1680 +
1681 + // Bulk delete -> confirm for N
1682 + $('#mxch-leads-delete-selected').on('click', function() {
1683 + if (leadsState.selected.size === 0) return;
1684 + openLeadsConfirm(Array.from(leadsState.selected));
1685 + });
1686 +
1687 + function openLeadsConfirm(emails) {
1688 + leadsState.pendingDelete = emails;
1689 + const count = emails.length;
1690 + const msg = count === 1
1691 + ? 'Delete lead "' + emails[0] + '" and all of their conversations?'
1692 + : 'Delete ' + count + ' leads and all of their conversations?';
1693 + $('#mxch-leads-confirm-body').text(msg);
1694 + $('#mxch-leads-confirm').fadeIn(120);
1695 + }
1696 +
1697 + function closeLeadsConfirm() {
1698 + $('#mxch-leads-confirm').fadeOut(120);
1699 + leadsState.pendingDelete = [];
1700 + }
1701 +
1702 + $leads().on('click', '[data-mxch-leads-close]', closeLeadsConfirm);
1703 +
1704 + $('#mxch-leads-confirm-go').on('click', function() {
1705 + const emails = leadsState.pendingDelete.slice();
1706 + if (!emails.length) { closeLeadsConfirm(); return; }
1707 +
1708 + const $btn = $(this).prop('disabled', true).text('Deleting...');
1709 +
1710 + $.ajax({
1711 + url: ajaxurl,
1712 + type: 'POST',
1713 + data: {
1714 + action: 'mxchat_delete_leads',
1715 + security: $('#mxchat_leads_delete_nonce').val(),
1716 + emails: emails
1717 + },
1718 + success: function(response) {
1719 + $btn.prop('disabled', false).text('Delete permanently');
1720 + closeLeadsConfirm();
1721 + if (response && response.success) {
1722 + emails.forEach(function(e) { leadsState.selected.delete(e.toLowerCase()); });
1723 + loadLeads(leadsState.page);
1724 + } else {
1725 + alert((response && response.data && response.data.message) || 'Failed to delete leads.');
1726 + }
1727 + },
1728 + error: function() {
1729 + $btn.prop('disabled', false).text('Delete permanently');
1730 + alert('Network error while deleting.');
1731 + }
1732 + });
1733 + });
1734 +
1735 + // Export dropdown
1736 + $('#mxch-leads-export-btn').on('click', function(e) {
1737 + e.stopPropagation();
1738 + $('#mxch-leads-export-menu').toggleClass('is-open');
1739 + });
1740 +
1741 + $(document).on('click', function() {
1742 + $('#mxch-leads-export-menu').removeClass('is-open');
1743 + });
1744 +
1745 + $('#mxch-leads-export-menu').on('click', function(e) { e.stopPropagation(); });
1746 +
1747 + $('#mxch-leads-export-menu button').on('click', function() {
1748 + if ($(this).prop('disabled')) return;
1749 + const scope = $(this).data('scope') || 'all';
1750 + const fields = $(this).data('fields') || 'email_and_name';
1751 + submitLeadsExport(scope, fields);
1752 + $('#mxch-leads-export-menu').removeClass('is-open');
1753 + });
1754 +
1755 + function submitLeadsExport(scope, fields) {
1756 + const $form = $('<form>', { method: 'POST', action: ajaxurl, style: 'display:none;' });
1757 + $form.append($('<input>', { type: 'hidden', name: 'action', value: 'mxchat_export_leads' }));
1758 + $form.append($('<input>', { type: 'hidden', name: 'security', value: $('#mxchat_leads_export_nonce').val() }));
1759 + $form.append($('<input>', { type: 'hidden', name: 'scope', value: scope }));
1760 + $form.append($('<input>', { type: 'hidden', name: 'fields', value: fields }));
1761 + if (scope === 'selected') {
1762 + Array.from(leadsState.selected).forEach(function(e) {
1763 + $form.append($('<input>', { type: 'hidden', name: 'emails[]', value: e }));
1764 + });
1765 + }
1766 + $form.appendTo('body').submit().remove();
1767 + }
1768 +
1769 + // Preload leads metadata on page load (for the nav badge count only) without rendering.
1770 + // We keep this light — the full fetch only runs when the tab is clicked.
1771 + $.ajax({
1772 + url: ajaxurl,
1773 + type: 'POST',
1774 + data: { action: 'mxchat_fetch_leads', page: 1, per_page: 1 },
1775 + success: function(response) {
1776 + if (response && response.success && response.stats) {
1777 + const total = response.stats.total_leads || 0;
1778 + const $badge = $('#mxch-leads-nav-badge');
1779 + if (total > 0) $badge.text(total).show();
1780 + }
1781 + }
1278 1782 });
1279 1783 });