PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.7
MxChat – AI Chatbot & Content Generation for WordPress v3.0.7
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 +19 -524 3.2.23.0.7 View file →
@@ -178,11 +178,9 @@
178 178 $('.mxch-chat-checkbox').prop('checked', isChecked);
179 179
180 180 if (isChecked) {
181 181 $('.mxch-chat-item').each(function() {
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'));
182 + selectedSessions.add($(this).data('session-id'));
185 183 $(this).addClass('selected');
186 184 });
187 185 $('#mxch-chat-list').addClass('selection-mode');
188 186 } else {
@@ -223,48 +221,22 @@
223 221 $(this).find('svg').css('transform', currentSortOrder === 'asc' ? 'rotate(180deg)' : 'rotate(0deg)');
224 222 loadChatList(currentPage, $('#mxch-search-transcripts').val());
225 223 });
226 224
227 - // Delete selected button — opens the shared confirm modal with the "also delete lead" checkbox.
225 + // Delete selected button
228 226 $('#mxch-delete-selected').on('click', function() {
229 227 const count = selectedSessions.size;
230 228 if (count === 0) return;
231 - openTranscriptConfirm(Array.from(selectedSessions), count);
232 - });
233 229
234 - // Transcript delete confirm (shared by bulk + individual) ---------------------------
235 - let transcriptConfirmSessionIds = [];
230 + if (!confirm('Are you sure you want to delete ' + count + ' conversation(s)? This action cannot be undone.')) {
231 + return;
232 + }
236 233
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);
234 + deleteMultipleSessions(Array.from(selectedSessions));
263 235 });
264 236
265 237 // Delete multiple sessions
266 - function deleteMultipleSessions(sessionIds, alsoDeleteLead) {
238 + function deleteMultipleSessions(sessionIds) {
267 239 $.ajax({
268 240 url: ajaxurl,
269 241 type: 'POST',
270 242 data: {
@@ -269,9 +241,8 @@
269 241 type: 'POST',
270 242 data: {
271 243 action: 'mxchat_delete_chat_history',
272 244 delete_session_ids: sessionIds,
273 - also_delete_lead: alsoDeleteLead ? '1' : '0',
274 245 security: $('#mxchat_delete_chat_nonce').val()
275 246 },
276 247 success: function(response) {
277 248 try {
@@ -292,13 +263,8 @@
292 263 }
293 264
294 265 // Reload list
295 266 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();
301 267 } else if (jsonResponse.error) {
302 268 alert('Error: ' + jsonResponse.error);
303 269 }
304 270 } catch (e) {
@@ -379,9 +345,9 @@
379 345 // Attach checkbox handlers
380 346 $('.mxch-chat-checkbox').on('click', function(e) {
381 347 e.stopPropagation(); // Prevent triggering chat item click
382 348 const $item = $(this).closest('.mxch-chat-item');
383 - const sessionId = $item.attr('data-session-id');
349 + const sessionId = $item.data('session-id');
384 350
385 351 if ($(this).is(':checked')) {
386 352 selectedSessions.add(sessionId);
387 353 $item.addClass('selected');
@@ -397,9 +363,9 @@
397 363 $('.mxch-chat-item').on('click', function(e) {
398 364 // Don't trigger if clicking on checkbox
399 365 if ($(e.target).is('.mxch-chat-checkbox')) return;
400 366
401 - const sessionId = $(this).attr('data-session-id');
367 + const sessionId = $(this).data('session-id');
402 368 selectChat(sessionId);
403 369
404 370 // Update active state
405 371 $('.mxch-chat-item').removeClass('active');
@@ -595,16 +561,21 @@
595 561 $btn.addClass('active');
596 562 }
597 563 });
598 564
599 - // Delete current chat — opens the shared confirm modal.
565 + // Delete current chat
600 566 $('#mxch-delete-current').on('click', function() {
601 567 if (!currentSessionId) return;
602 - openTranscriptConfirm([currentSessionId], 1);
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);
603 574 });
604 575
605 576 // Delete session function
606 - function deleteSession(sessionId, alsoDeleteLead) {
577 + function deleteSession(sessionId) {
607 578 $.ajax({
608 579 url: ajaxurl,
609 580 type: 'POST',
610 581 data: {
@@ -609,9 +580,8 @@
609 580 type: 'POST',
610 581 data: {
611 582 action: 'mxchat_delete_chat_history',
612 583 delete_session_ids: [sessionId],
613 - also_delete_lead: alsoDeleteLead ? '1' : '0',
614 584 security: $('#mxchat_delete_chat_nonce').val()
615 585 },
616 586 success: function(response) {
617 587 try {
@@ -958,14 +928,13 @@
958 928 });
959 929 });
960 930
961 931 const urlGroups = Object.values(groupedByUrl).sort((a, b) => b.bestScore - a.bestScore);
962 - const usedUrlCount = data.sources_used > 0 ? data.sources_used : urlGroups.filter(g => g.usedForContext).length;
963 - const chunksInfo = data.total_chunks_used > 0 ? data.total_chunks_used + ' chunks sent to AI' : '';
932 + const usedUrlCount = urlGroups.filter(g => g.usedForContext).length;
964 933
965 934 html += '<div class="mxch-rag-matches">';
966 935 html += '<h3>Retrieved Documents</h3>';
967 - html += '<p style="color: var(--mxch-text-secondary); font-size: 13px; margin-bottom: 16px;">' + usedUrlCount + ' source' + (usedUrlCount === 1 ? '' : 's') + ' used for response' + (chunksInfo ? ' &middot; ' + chunksInfo : '') + '</p>';
936 + html += '<p style="color: var(--mxch-text-secondary); font-size: 13px; margin-bottom: 16px;">' + usedUrlCount + ' entr' + (usedUrlCount === 1 ? 'y' : 'ies') + ' used for response</p>';
968 937
969 938 urlGroups.forEach(function(group) {
970 939 const cardClass = group.usedForContext ? 'mxch-rag-match-used' : 'mxch-rag-match-below';
971 940 const statusIcon = group.usedForContext ? '&#10003;' : '&#10007;';
@@ -1304,480 +1273,6 @@
1304 1273 clearTimeout(resizeTimeout);
1305 1274 resizeTimeout = setTimeout(function() {
1306 1275 initActivityChart();
1307 1276 }, 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, '&amp;')
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 - }
1782 1277 });
1783 1278 });