1// Orderlist â sorting + PDF download (uses event delegation for AJAX-loaded content) 2(function($) { 3 4 // ââ Table sorting ââ 5 $(document).on('click', '.b2b-lines th.sortable', function() { 6 var th = $(this); 7 var table = th.closest('table'); 8 var colIndex = th.index(); 9 var tbody = table.find('tbody'); 10 var rows = tbody.find('tr').get(); 11 var isAsc = th.hasClass('sort-asc'); 12 13 table.find('thead th').removeClass('sort-asc sort-desc'); 14 var dir = isAsc ? -1 : 1; 15 th.addClass(isAsc ? 'sort-desc' : 'sort-asc'); 16 17 var isNumeric = (colIndex === 0 || colIndex === 6 || colIndex === 7); 18 var isDate = (colIndex === 8); 19 20 rows.sort(function(a, b) { 21 var aText = a.cells[colIndex].textContent.trim(); 22 var bText = b.cells[colIndex].textContent.trim(); 23 if (isNumeric) { 24 var aVal = parseFloat(aText.replace(/[^\d.\-]/g, '')) || 0; 25 var bVal = parseFloat(bText.replace(/[^\d.\-]/g, '')) || 0; 26 return (aVal - bVal) * dir; 27 } 28 if (isDate) { 29 var aD = aText ? new Date(aText) : new Date(0); 30 var bD = bText ? new Date(bText) : new Date(0); 31 return (aD - bD) * dir; 32 } 33 return aText.localeCompare(bText) * dir; 34 }); 35 $.each(rows, function(i, row) { tbody.append(row); }); 36 }); 37 38 // Mark headers as sortable when b2b-lines content appears 39 function initSortHeaders() { 40 $('.b2b-lines thead th').addClass('sortable'); 41 } 42 43 // ââ PDF download ââ 44 var sectionNames = { 45 ordered: 'Ordered - To Process', 46 shelflist: 'Reserved', 47 preorderlist: 'Back - Pre-Order List', 48 sold: 'Sold - Dispatched' 49 }; 50 51 var jsPdfLoaded = false; 52 53 function loadJsPdf(callback) { 54 if (jsPdfLoaded && window.jspdf) { 55 callback(); 56 return; 57 } 58 var s1 = document.createElement('script'); 59 s1.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js'; 60 s1.onload = function() { 61 var s2 = document.createElement('script'); 62 s2.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js'; 63 s2.onload = function() { 64 jsPdfLoaded = true; 65 callback(); 66 }; 67 document.head.appendChild(s2); 68 }; 69 document.head.appendChild(s1); 70 } 71 72 function generatePdf(btn) { 73 var section = $(btn).data('section'); 74 var h4 = $(btn).closest('h4'); 75 var tableEl = h4.nextAll('.table-responsive').first().find('table')[0] 76 || h4.nextAll('table').first()[0]; 77 if (!tableEl) return; 78 79 var jsPDF = window.jspdf.jsPDF; 80 var doc = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' }); 81 82 var title = sectionNames[section] || section; 83 var dateStr = new Date().toLocaleDateString('en-GB'); 84 var shopName = $('.b2b-lines').data('shop-name') || ''; 85 var customerName = $('.b2b-lines').data('customer-name') || ''; 86 87 var yPos = 15; 88 if (shopName) { 89 doc.setFontSize(18); 90 doc.setTextColor(0); 91 doc.text(shopName, 14, yPos); 92 yPos += 8; 93 } 94 if (customerName) { 95 doc.setFontSize(12); 96 doc.setTextColor(80); 97 doc.text(customerName, 14, yPos); 98 yPos += 8; 99 } 100 doc.setFontSize(14); 101 doc.setTextColor(0); 102 doc.text(title, 14, yPos); 103 yPos += 6; 104 doc.setFontSize(9); 105 doc.setTextColor(120); 106 doc.text('Generated: ' + dateStr, 14, yPos); 107 doc.setTextColor(0); 108 yPos += 4; 109 110 var headers = []; 111 $(tableEl).find('thead th').each(function() { 112 headers.push($(this).text().replace(/[âââ]/g, '').trim()); 113 }); 114 115 var body = []; 116 $(tableEl).find('tbody tr').each(function() { 117 var row = []; 118 $(this).find('td').each(function() { row.push($(this).text().trim()); }); 119 body.push(row); 120 }); 121 122 var foot = []; 123 $(tableEl).find('tfoot tr').each(function() { 124 var row = []; 125 $(this).find('td').each(function() { 126 var colspan = parseInt($(this).attr('colspan')) || 1; 127 row.push($(this).text().trim()); 128 for (var c = 1; c < colspan; c++) { row.push(''); } 129 }); 130 foot.push(row); 131 }); 132 133 doc.autoTable({ 134 head: [headers], 135 body: body, 136 foot: foot, 137 startY: yPos, 138 theme: 'grid', 139 styles: { fontSize: 8, cellPadding: 2 }, 140 headStyles: { fillColor: [50, 50, 50], textColor: 255, fontStyle: 'bold' }, 141 footStyles: { fillColor: [240, 240, 240], textColor: 0, fontStyle: 'bold' }, 142 alternateRowStyles: { fillColor: [248, 248, 248] }, 143 columnStyles: { 144 0: { halign: 'center', cellWidth: 12 }, 145 6: { halign: 'right' }, 146 7: { halign: 'right' } 147 }, 148 didParseCell: function(data) { 149 // Apply alignment to all sections (head, body, foot) 150 if (data.column.index === 0) data.cell.styles.halign = 'center'; 151 if (data.column.index === 6) data.cell.styles.halign = 'right'; 152 if (data.column.index === 7) data.cell.styles.halign = 'right'; 153 } 154 }); 155 156 doc.save('b2b-' + section + '-' + dateStr.replace(/\//g, '-') + '.pdf'); 157 } 158
159 $(document).on('click', '.pdf-download', function(e) { 160 e.preventDefault(); 161 var btn = this; 162 $(btn).prop('disabled', true).html('<i class="fas fa-spinner fa-spin mr-1"></i>Loading...'); 163 loadJsPdf(function() { 164 generatePdf(btn); 165 $(btn).prop('disabled', false).html('<i class="fas fa-file-pdf mr-1"></i>PDF'); 166 }); 167 }); 168 169 // Init sorting when b2b-lines loads (called via MutationObserver or after AJAX) 170 var observer = new MutationObserver(function(mutations) { 171 mutations.forEach(function() { 172 if ($('.b2b-lines').length) { 173 initSortHeaders(); 174 } 175 }); 176 }); 177 var target = document.getElementById('scrollcontainer') || document.getElementById('maincontent'); 178 if (target) { 179 observer.observe(target, { childList: true, subtree: true }); 180 } 181 // Also init immediately if already present 182 initSortHeaders(); 183 184 // ââ Email section ââ 185 $(document).on('click', '.email-section', function(e) { 186 e.preventDefault(); 187 var $btn = $(this); 188 var section = $btn.data('section'); 189 var origHtml = $btn.html(); 190 191 if (!confirm('Email this section to your registered email address?')) return; 192 193 $btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin mr-1"></i>Sending...'); 194 195 $.post('/ajax_orderlist_email.php', { section: section }, function(data) { 196 if (data && data.success) { 197 $btn.html('<i class="fas fa-check mr-1"></i>Sent!'); 198 setTimeout(function() { 199 $btn.prop('disabled', false).html(origHtml); 200 }, 3000); 201 } else { 202 alert(data.message || 'Could not send email.'); 203 $btn.prop('disabled', false).html(origHtml); 204 } 205 }, 'json').fail(function() { 206 alert('Request failed. Please try again.'); 207 $btn.prop('disabled', false).html(origHtml); 208 }); 209 }); 210 211 // ââ Ship Reserved Items request ââ 212 $(document).on('click', '.ship-reserved-btn', function() { 213 var $btn = $(this); 214 var $status = $btn.siblings('.ship-reserved-status'); 215 216 if (!confirm('Send a request to ship all your reserved items?')) return; 217 218 $btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin mr-1"></i>Sending...'); 219 $status.text(''); 220 221 $.post('/ajax_ship_request.php', {}, function(data) { 222 if (data && data.success) { 223 $btn.removeClass('btn-success').addClass('btn-outline-success') 224 .html('<i class="fas fa-check mr-1"></i>Request Sent'); 225 $status.text(data.message).css('color', '#28a745'); 226 } else { 227 $btn.prop('disabled', false) 228 .html('<i class="fas fa-shipping-fast mr-1"></i>Please Ship My Reserved Items'); 229 $status.text(data.message || 'Something went wrong.').css('color', '#dc3545'); 230 } 231 }, 'json').fail(function() { 232 $btn.prop('disabled', false) 233 .html('<i class="fas fa-shipping-fast mr-1"></i>Please Ship My Reserved Items'); 234 $status.text('Request failed. Please try again.').css('color', '#dc3545'); 235 }); 236 }); 237 238})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.