1// --- reCAPTCHA v2 callback must be global --- 2window.recaptchaCallback = function (token) { 3 var $ = window.jQuery; 4 5 // store token 6 $('.recaptcha-token').val(token); 7 8 // verify server-side (optional but you already do this) 9 $.ajax({ 10 url: '/wp-json/899/v1/verify-recaptcha', 11 method: 'POST', 12 data: { token: token } 13 }).done(function (resp) { 14 if (!resp || !resp.success) { 15 $('.recaptcha-token').val(''); // clear token on failure 16 alert('reCAPTCHA verification failed. Please try again.'); 17 } 18 }).always(function () { 19 // enable button if UPC + Lot + reCAPTCHA are present 20 var hasUPC = ($('.upc-value').val() || '').length > 0; 21 var hasLot = ($('.lot-code').val() || '').length > 0; 22 var recok = ($('.recaptcha-token').val() || '') !== ''; 23 $('.search-button').attr('disabled', !(hasUPC && hasLot && recok)); 24 }); 25}; 26 27 28 29 30 31jQuery(document).ready(function ($) { 32 var source_json = ''; 33 var manual_list = null; 34 var current_upc_rows = []; // rows for the currently selected product type 35 var batchesByUPC = {}; // { upc:string -> Array<batch> } 36 var current_lots = []; 37 var manualListLoaded = false; 38 var pendingProductEl = null; 39 /** 40 * --------------------------------------------------------------------------- 41 * Ensure UI pieces exist (we keep the hidden .upc-value/.lot-code/.buy-date 42 * for your logging; we render a visual search + dropdown next to them) 43 * --------------------------------------------------------------------------- 44 */ 45 function ensureUPCUI() { 46 var $upcField = $('.upc-field'); 47 if (!$upcField.length) return; 48 49 // Hide the raw numeric input used only for logging; we set its value programmatically 50 var $hiddenUPC = $upcField.find('input.upc-value'); 51 if ($hiddenUPC.length) { $hiddenUPC.css({ position: 'absolute', left: '-9999px' }); } 52 53 54 // Numeric search input (users type here) 55 if (!$('#upc-search').length) { 56 $upcField.append( 57 '<input id="upc-search" type="text" inputmode="numeric" ' + 58 'pattern="[0-9]*" placeholder="Type UPC to filter (numbers only)" ' + 59 'style="margin-top:8px;display:block;width:100%;">' 60 ); 61 } 62 63 // Visual results dropdown (ul/li style) 64 if (!$('#upc-results').length) { 65 $upcField.css('position', 'relative'); // for absolute dropdown if you style it 66 $upcField.append( 67 '<div id="upc-results" class="upc-results" role="listbox" ' + 68 'aria-expanded="false" style="display:none;"></div>' 69 ); 70 } 71 72 // Selected preview (name + upc + image) shown after user picks an item 73 if (!$('#upc-preview').length) { 74 $upcField.append( 75 '<div id="upc-preview" style="display:none;gap:12px;align-items:center;margin-top:10px;">' + 76 '<img id="upc-preview-img" alt="" style="width:52px;height:52px;object-fit:contain;display:none;">' + 77 '<div style="flex:1 1 auto">' + 78 '<div class="name" style="font-weight:600;"></div>' + 79 '<div class="upc" style="font-size:12px;color:#666;"></div>' + 80 '</div>' + 81 // NEW: clear button 82 '<button type="button" id="upc-clear" ' + 83 'style="flex:0 0 auto;border:0;background:#e8e8e8;padding:6px 10px;border-radius:4px;cursor:pointer;">Change</button>' + 84 '</div>' 85 ); 86 } 87 88 // LOT CODE select UI (we still keep your hidden .lot-code / .buy-date for logs) 89 var $lotInput = $('.lot-code'); 90 if ($lotInput.length) { $lotInput.css({ position: 'absolute', left: '-9999px' }); } // hide the raw input 91 92 // Keep original <select> as a hidden fallback 93 if (!$('#lot-code-select').length) { 94 $lotInput.after( 95 '<select id="lot-code-select" style="position:absolute;left:-9999px;top:auto;">' + 96 '<option value="">Select Best By Date â lot code</option>' + 97 '</select>' 98 ); 99 } 100 101 // Lot search + results + preview (mirrors UPC UX) 102 if (!$('#lot-ui').length) { 103 $('#lot-code-select').after( 104 105 '<div id="lot-ui" style="display:none">' + 106 '<div class="input-label">Best By Date - Lot Code*</div>' + 107 '<input id="lot-search" type="text" placeholder="Search Best By Date (e.g. 01DEC2027) or lot code..." ' + 108 'style="display:block;width:100%;margin-top:6px;">' + 109 '<div id="lot-results" class="upc-results" role="listbox" aria-expanded="false" style="display:none;"></div>' + 110 '<div id="lot-preview" style="display:none;gap:12px;align-items:baseline;margin-top:10px;margin-bottom:20px">' + 111 '<div class=""preview-inner>' + 112 '<div class="best" style="font-weight:600;"></div>' + 113 '<div class="lot" style="font-size:12px;color:#666;"></div>' + 114 '</div>' + 115 '<button type="button" id="lot-clear" ' + 116 'style="flex:0 0 auto;border:0;background:#e8e8e8;padding:6px 10px;border-radius:4px;cursor:pointer;">Change</button>' + 117 '</div>' + 118 '</div>' 119 ); 120 } 121 122 } 123 ensureUPCUI(); 124 125 /** 126 * --------------------------------------------------------------------------- 127 * Fetch batches (source_json) & index by UPC 128 * --------------------------------------------------------------------------- 129 */ 130 var endpoint = '/wp-json/899/v1/fetch-' + test_data.dataSource + '-content/'; 131 $.ajax({ 132 url: endpoint, 133 method: 'POST', 134 dataType: 'json', 135 success: function (data) { 136 source_json = data || {}; 137// console.log(source_json) 138 batchesByUPC = {}; 139 if (source_json && Array.isArray(source_json.batches)) {
140 source_json.batches.forEach(function (b) { 141 var u = normalizeUPC(b.upcCode); // <-- add single leading 0 if missing 142 if (!u) return; 143 (batchesByUPC[u] = batchesByUPC[u] || []).push(b); 144 }); 145 } 146 147 }, 148 error: function () { 149 fetchIP().then(ip => { 150 sendLogData({ 151 'upc': '', 'lot_code': '', 'bestby': '', 'ip_address': ip, 'action': '', 152 'logged_in_status': test_data.logged_in_status, 153 'product_type': '', 'failure_details': '', 'success_details': '', 154 'events': 'form_submission_failed_db_error' 155 }); 156 }); 157 } 158 }); 159 160 /** 161 * --------------------------------------------------------------------------- 162 * Fetch manual list (used to populate UPC dropdown) 163 * --------------------------------------------------------------------------- 164 */ 165 $.ajax({ 166 url: '/wp-json/test-log/v1/manual-product-list', 167 method: 'GET', 168 cache: false, 169 dataType: 'json', 170 success: function (resp) { 171 manual_list = (resp && resp.rows) ? resp.rows : []; 172 manualListLoaded = true; 173 if (pendingProductEl) { 174 var $el = pendingProductEl; 175 pendingProductEl = null; 176 $el.trigger('click'); 177 } 178 }, 179 error: function () { 180 fetchIP().then(ip => { 181 sendLogData({ 182 'upc': '', 'lot_code': '', 'bestby': '', 'ip_address': ip, 'action': '', 183 'logged_in_status': test_data.logged_in_status, 'product_type': '', 184 'failure_details': 'manual_product_list_fetch_failed', 'success_details': '', 185 'events': 'form_submission_failed_db_error' 186 }); 187 }); 188 } 189 }); 190 191 /** 192 * --------------------------------------------------------------------------- 193 * Utilities 194 * --------------------------------------------------------------------------- 195 */ 196 function sendLogData(data) { 197 $.ajax({ 198 url: test_data.rest_url, 199 method: 'POST', 200 data: JSON.stringify(data), 201 contentType: 'application/json', 202 success: function (r) { console.log('Success:', r); }, 203 error: function (xhr) { 204 console.log('Error:', xhr && xhr.responseText ? xhr.responseText : 'unknown'); 205 alert('Failed to send log data.'); 206 } 207 }); 208 } 209 210 function convertDateString(dateStr) { 211 const months = { 212 JAN: '01', FEB: '02', MAR: '03', APR: '04', MAY: '05', JUN: '06', 213 JUL: '07', AUG: '08', SEP: '09', OCT: '10', NOV: '11', DEC: '12' 214 }; 215 let day = dateStr.substring(0, 2); 216 let monthAbbr = dateStr.substring(2, 5); 217 let year = dateStr.substring(5); 218 let month = months[monthAbbr]; 219 if (!month) { alert('Bestby date is invalid'); throw new Error('Invalid Date'); } 220 return day + month + year; 221 } 222 223 function escapeHtml(s) { 224 return (s || '').replace(/[&<>"'`=\/]/g, function (c) { 225 return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', '`': '`', '=': '=' }[c]; 226 }); 227 } 228 229 function fetchIP() { return new Promise((resolve, reject) => { $.getJSON('https://api.ipify.org?format=json', d => resolve(d.ip)).fail(reject) }); } 230 231 // Put this next to escapeHtml / fetchIP helpers 232 function normalizeUPC(raw) { 233 var digits = (raw || '').toString().replace(/\D+/g, ''); 234 if (!digits) return ''; 235 // ensure ONE leading 0 only 236 return digits.charAt(0) === '0' ? digits : ('0' + digits); 237 } 238 239 function isDateLike(s) { 240 // matches 01DEC2027 (DDMMMYYYY, MMM uppercase) 241 return /^[0-3]\d[A-Z]{3}\d{4}$/.test((s || '').toString().trim().toUpperCase()); 242 } 243 244 // Some rows occasionally have bestBy/lotCode swapped. Correct them here. 245 function normalizeBestLot(bestIn, lotIn) { 246 var best = (bestIn || '').toString().trim(); 247 var lot = (lotIn || '').toString().trim(); 248 // If best is NOT a date but lot IS a date, swap them. 249 if (!isDateLike(best) && isDateLike(lot)) { 250 var t = best; best = lot; lot = t; 251 } 252 return { 253 best: best.toUpperCase().replace(/\s+/g, ''), 254 lot: lot.toLowerCase().replace(/\s+/g, '') 255 }; 256 } 257 258 function formatLabel(best, lot) { 259 // Always "Expiry â Lot" for display 260 return best + ' â ' + lot; 261 } 262 263 function renderLotList(rows) { 264 var $list = $('#lot-results'); if (!$list.length) return; 265 if (!rows || !rows.length) { 266 $list.empty().hide().attr('aria-expanded', 'false'); 267 return; 268 } 269 var html = rows.map(function (r) { 270 return '' + 271 '<div class="upc-item lot-item" role="option" tabindex="0" ' + 272 'data-best="' + escapeHtml(r.best) + '" ' + 273 'data-lot="' + escapeHtml(r.lot) + '">' + 274 '<div class="t" style="display:flex;justify-content:space-between;align-items:center;gap:12px;">' + 275 '<div class="lt" style="min-width:0">' + 276 '<div class="name" style="font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' 277 + escapeHtml(formatLabel(r.best, r.lot)) + '</div>' + 278 '</div>' + 279 '</div>' + 280 '</div>'; 281 }).join(''); 282 $list.html(html).show().attr('aria-expanded', 'true'); 283 } 284 285 function filterLotList(query) { 286 query = (query || '').toString().trim().toLowerCase(); 287 if (!query) return current_lots.slice(0, 500); 288 return current_lots.filter(function (r) { 289 return r.best.toLowerCase().indexOf(query) !== -1 290 || r.lot.toLowerCase().indexOf(query) !== -1; 291 }).slice(0, 500); 292 } 293 294 function selectLotFromList(best, lot) { 295 // write hidden fields used by logging 296 $('.lot-code').val(lot); 297 $('.buy-date').val(best); 298 299 // update preview 300 $('#lot-preview .best').text(best + ' - ' + lot); 301 // sync the hidden <select> to the chosen pair (fallback parity) 302 var $sel = $('#lot-code-select'); 303 var matched = false;
304 $sel.find('option').each(function () { 305 var $o = $(this); 306 if (($o.data('bestby') || '').toString().toUpperCase().replace(/\s+/g, '') === best 307 && ($o.data('lot') || '').toString().toLowerCase().replace(/\s+/g, '') === lot) { 308 $sel.val($o.attr('value')); 309 matched = true; 310 return false; 311 } 312 }); 313 if (!matched) { $sel.val(''); } // harmless if no option matches 314 315 // close list & hide search 316 $('#lot-results').hide().attr('aria-expanded', 'false'); 317 $('#lot-search').val('').hide(); 318 $('#lot-preview').css('display', 'flex'); 319 320 updateButtonState(); 321 } 322 323 /** 324 * --------------------------------------------------------------------------- 325 * UPC dropdown (UL/LI with images) â render + filter + select 326 * --------------------------------------------------------------------------- 327 */ 328 function renderUPCList(rows) { 329 var $list = $('#upc-results'); if (!$list.length) return; 330 331 if (!rows || !rows.length) { 332 $list.empty().hide().attr('aria-expanded', 'false'); 333 return; 334 } 335 336 // Build the list with real <img src="..."> so the browser caches them. 337 var html = rows.map(function (r) { 338 var imgHtml = r.image_url 339 ? '<img src="' + escapeHtml(r.image_url) + '" loading="lazy" alt="" ' + 340 'style="width:52px;height:52px;object-fit:contain;flex:0 0 auto;">' 341 : ''; 342 343 return '' + 344 '<div class="upc-item" role="option" tabindex="0" ' + 345 'data-upc="' + escapeHtml(r.upc) + '" ' + 346 'data-name="' + escapeHtml(r.short_name || '') + '" ' + 347 'data-img="' + escapeHtml(r.image_url || '') + '">' + 348 '<div class="t" style="display:flex;justify-content:space-between;align-items:center;gap:12px;">' + 349 '<div class="lt" style="min-width:0">' + 350 '<div class="name" style="font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' 351 + escapeHtml(r.short_name || '') + '</div>' + 352 '<div class="upc" style="font-size:12px;color:#666;">UPC #' + escapeHtml(r.upc) + '</div>' + 353 '</div>' + 354 imgHtml + 355 '</div>' + 356 '</div>'; 357 }).join(''); 358 359 $list.html(html).show().attr('aria-expanded', 'true'); 360 } 361 362 function filterUPCList(query) { 363 query = (query || '').toString(); 364 if (!query) return current_upc_rows.slice(0, 500); // cap 365 return current_upc_rows.filter(function (r) { 366 return (r.upc && r.upc.indexOf(query) !== -1); 367 }).slice(0, 500); 368 } 369 function selectUPCFromList(upc, name, img) { 370 // write into hidden .upc-value for logging 371 $('.upc-value').val(upc).attr('maxlength', (upc || '').length); 372 373 // update preview 374 $('#upc-preview .name').text(name || ''); 375 $('#upc-preview .upc').text(upc ? ('UPC #' + upc) : ''); 376 if (img) { 377 $('#upc-preview-img').attr('src', img).show(); 378 } else { 379 $('#upc-preview-img').attr('src', '').hide(); 380 } 381 382 // populate Lot Code select for this UPC 383 buildLotCodeSelect(upc); 384 385 // close list & hide search input; show preview 386 $('#upc-results').hide().attr('aria-expanded', 'false'); 387 $('#upc-search').val('').hide(); 388 $('#upc-preview').css('display', 'flex'); 389 390 $('#lot-preview').hide(); 391 $('#lot-search').val('').show(); 392 $('#lot-ui').slideDown(300) 393 updateButtonState(); 394 } 395 396 397 // Build Lot Code select from source_json batches 398 function buildLotCodeSelect(upc) { 399 var $sel = $('#lot-code-select'); if (!$sel.length) return; 400 $sel.empty().append('<option value="">Select Best By Date â lot code</option>'); 401 402 var matches = batchesByUPC[upc] || []; 403 404 // Normalize & index 405 current_lots = matches.map(function (b, i) { 406 var norm = normalizeBestLot(b.bestBy, b.lotCode); // swap if needed 407 // keep hidden fallback <select> synced 408 $sel.append( 409 '<option value="' + i + '" data-bestby="' + escapeHtml(norm.best) + '" data-lot="' + escapeHtml(norm.lot) + '">' + 410 escapeHtml(formatLabel(norm.best, norm.lot)) + 411 '</option>' 412 ); 413 return { idx: i, best: norm.best, lot: norm.lot }; 414 }); 415 416 // â Detect "no test data" 417 var validCount = current_lots.filter(function (r) { return r.best && r.lot; }).length; 418 var isPuffies = ($('.search-box .search-title').text().trim() === 'Puffies'); 419 var baseLabel = isPuffies ? 'Best By Date*' : 'Best By Date - Lot Code*'; 420 if (validCount === 0) { 421 $('#lot-ui .input-label').html(baseLabel + ' <span style="color:#c00;font-weight:600;">(No test data found)</span>'); 422 } else { 423 $('#lot-ui .input-label').text(baseLabel); 424 } 425 426 // Reset visible Lot UI 427 $('#lot-preview').hide(); 428 $('#lot-search').val('').show(); 429 renderLotList(current_lots); 430 } 431 432 433 /** 434 * --------------------------------------------------------------------------- 435 * Events: search input (numeric only), list select, outside click, lot select 436 * --------------------------------------------------------------------------- 437 */ 438 // Numeric-only and instant filter 439 $(document).on('input', '#upc-search', function () { 440 var digits = ($(this).val() || '').replace(/\D+/g, ''); // numeric-only 441 $(this).val(digits); 442 renderUPCList(filterUPCList(digits)); 443 }); 444 445 446 // Open list on focus/click into the search box 447 $(document).on('focus click', '#upc-search', function () { 448 var q = $('#upc-search').val(); 449 renderUPCList(filterUPCList(q)); 450 }); 451 452 // Pick an item (mouse) 453 $(document).on('click', '#upc-results .upc-item', function () { 454 var upc = normalizeUPC($(this).data('upc')); // <-- normalize here too 455 var name = ($(this).data('name') || '').toString(); 456 var img = ($(this).data('img') || '').toString(); 457 selectUPCFromList(upc, name, img); 458 }); 459 460 461 // Pick an item (keyboard) 462 $(document).on('keydown', function (e) { 463 if (e.key === 'Escape') { 464 $('#upc-results, #lot-results').hide().attr('aria-expanded', 'false'); 465 } 466 }); 467 // Close dropdowns when clicking outside 468 $(document).on('click', function (e) { 469 // UPC dropdown 470 if (!$(e.target).closest('.upc-field').length) { 471 $('#upc-results').hide().attr('aria-expanded', 'false'); 472 } 473 // LOT dropdown 474 if (!$(e.target).closest('#lot-ui').length) { 475 $('#lot-results').hide().attr('aria-expanded', 'false'); 476 } 477 }); 478 479 480 // Lot code select -> set hidden .lot-code & .buy-date 481 $(document).on('change', '#lot-code-select', function () { 482 var $opt = $(this).find('option:selected'); 483 var lot = ($opt.data('lot') || '').toString().replace(/\s+/g, '').toLowerCase(); 484 var best = ($opt.data('bestby') || '').toString().replace(/\s+/g, '').toUpperCase(); 485 486 $('.lot-code').val(lot); 487 $('.buy-date').val(best); 488 489 updateButtonState(); 490 }); 491 492 // Clear the selected UPC and return to the search input 493 // Clear the selected UPC and return to the search input 494 $(document).on('click', '#upc-clear', function () { 495 // clear hidden fields 496 $('.upc-value').val(''); 497 $('.lot-code').val(''); 498 $('.buy-date').val(''); 499 500 // reset UI 501 $('#upc-preview-img').attr('src', '').hide(); 502 $('#upc-preview .name').text(''); 503 $('#upc-preview .upc').text(''); 504 $('#upc-preview').hide(); 505 506 $('#lot-code-select').html('<option value="">Select Best By Date â lot code</option>'); 507 $('#upc-results').hide().empty().attr('aria-expanded', 'false'); 508 509 // show search again and focus it 510 $('#upc-search').val('').show().trigger('focus'); 511 512 // ð§ NEW: hard-reset Lot UI 513 current_lots = []; // clear in-memory lots 514 $('#lot-ui').hide(); // hide the Lot UI 515 $('#lot-results').hide().empty().attr('aria-expanded', 'false'); // close dropdown 516 $('#lot-search').val(''); // clear search box 517 $('#lot-preview').hide().find('.best,.lot').text(''); // clear preview texts 518 519 updateButtonState(); 520 }); 521 522 523 // Lot search input: filter as you type 524 $(document).on('input', '#lot-search', function () { 525 var q = ($(this).val() || ''); 526 renderLotList(filterLotList(q)); 527 }); 528 529 // Open lot list on focus/click
530 $(document).on('focus click', '#lot-search', function () { 531 var q = $('#lot-search').val(); 532 renderLotList(filterLotList(q)); 533 }); 534 535 // Select a lot (mouse) 536 $(document).on('click', '#lot-results .lot-item', function () { 537 var best = ($(this).data('best') || '').toString(); 538 var lot = ($(this).data('lot') || '').toString(); 539 selectLotFromList(best, lot); 540 }); 541 542 // Select a lot (keyboard) 543 $(document).on('keydown', '#lot-results .lot-item', function (e) { 544 if (e.key === 'Enter' || e.key === ' ') { 545 e.preventDefault(); 546 $(this).click(); 547 } 548 }); 549 550 // Clear lot selection & go back to search 551 $(document).on('click', '#lot-clear', function () { 552 $('.lot-code').val(''); 553 $('.buy-date').val(''); 554 555 $('#lot-preview .best').text(''); 556 $('#lot-preview .lot').text(''); 557 $('#lot-preview').hide(); 558 559 $('#lot-results').hide().empty().attr('aria-expanded', 'false'); 560 $('#lot-search').val('').show().trigger('focus'); 561 562 updateButtonState(); 563 }); 564 565 /** 566 * --------------------------------------------------------------------------- 567 * Product-type click (match by data-product-key = ACF name_key) 568 * Pad UPCs with a leading "0" if missing 569 * --------------------------------------------------------------------------- 570 */ 571 $(document).on('click', '.product-type', function () { 572 573 if (!manualListLoaded) { 574 e.preventDefault(); 575 pendingProductEl = $(this); 576 // Optional: light hint in UI (non-blocking) 577 $('#upc-results').hide().attr('aria-expanded', 'false'); 578 $('#upc-search').val(''); // keeps it clean 579 alert('UPC Data is loading...') 580 return; // bail now; we'll replay this click in success callback above 581 } 582 $('#upc-search').val('').show(); 583 $('#upc-results').hide().empty().attr('aria-expanded', 'false'); 584 $('#upc-preview-img').attr('src', '').hide(); 585 $('#upc-preview .name').text(''); $('#upc-preview .upc').text(''); 586 $('#upc-preview').hide(); 587 $('.upc-value,.lot-code,.buy-date').val(''); 588 $('#lot-code-select').html('<option value="">Select Best By Date â lot code</option>'); 589 $('#lot-ui').hide(); 590 591 592 var title = $(this).find('.product-title').text(); 593 var typeKey = $(this).data('product-key') || ''; // preferred exact key 594 595 // NEW: filter manual_list by the canonical product key and normalize UPCs 596 current_upc_rows = (Array.isArray(manual_list) ? manual_list : []) 597 .filter(function (r) { return (r.type_899 || '') === typeKey; }) 598 .map(function (r) { 599 return { 600 upc: normalizeUPC(r.upc), // <-- digits + single leading 0 601 short_name: r.short_name || '', 602 image_url: r.image_url || '', 603 type_899: r.type_899 || '' 604 }; 605 }); 606 607 // Fallback by visible title if nothing matched the key 608 if (!current_upc_rows.length && title) { 609 var t = (title || '').trim().toLowerCase(); 610 current_upc_rows = (Array.isArray(manual_list) ? manual_list : []) 611 .filter(function (r) { return ((r.type_899 || '').trim().toLowerCase() === t); }) 612 .map(function (r) { 613 return { 614 upc: normalizeUPC(r.upc), 615 short_name: r.short_name || '', 616 image_url: r.image_url || '', 617 type_899: r.type_899 || '' 618 }; 619 }); 620 } 621 622 623 // Show all for this type initially 624 renderUPCList(current_upc_rows); 625 626 // Safe reCAPTCHA reset 627 if (window.grecaptcha && typeof grecaptcha.reset === 'function') { try { grecaptcha.reset(); } catch (e) { } } 628 $('.recaptcha-token').val(''); 629 updateButtonState(); 630 631 var guideUrl = $(this).data('guide-url'); 632 $('.search-box').attr('data-selected-type', title); 633 $('.search-box .search-title').text(title); 634 $('.search-box .guide-img').attr('src', guideUrl); 635 636 if ($(this).data('comparative') == 1) { $('td.fda-col,td.eu-col').show(0); } 637 else { $('td.fda-col,td.eu-col').hide(0); } 638 639 if (!$('.search-box').is(':visible')) { 640 $('.search-box').slideDown(function () { 641 document.querySelector('.search-box').scrollIntoView({ behavior: 'smooth' }); 642 }); 643 } else { 644 document.querySelector('.search-box').scrollIntoView({ behavior: 'smooth' }); 645 } 646 }); 647 648 /** 649 * --------------------------------------------------------------------------- 650 * Validation & buttons 651 * --------------------------------------------------------------------------- 652 */ 653 var user_ip = ''; 654 fetchIP().then(ip => user_ip = ip).catch(() => user_ip = 'null'); 655 656 function updateButtonState() { 657 var hasUPC = ($('.upc-value').val() || '').length > 0; 658 var hasLot = ($('.lot-code').val() || '').length > 0; 659 var recaptchaVerified = $('.recaptcha-token').val() !== ''; 660 661 $('.search-button').attr('disabled', !(hasUPC && hasLot && recaptchaVerified)); 662 } 663 664 665 666 // Keep button state fresh as user types (buy-date stays as-is; we fill via lot select) 667 $('.input-field input').on('input', function () { updateButtonState(); }); 668 669 /** 670 * --------------------------------------------------------------------------- 671 * Search button: keep your original matching/logging logic (unchanged) 672 * --------------------------------------------------------------------------- 673 */ 674 $('.search-button').on('click', function () { 675 var upcInput = ($('.upc-value').val() || '').toString(); 676 var lot_code = ($('.lot-code').val() || '').toString(); 677 var bestby = ($('.buy-date').val() || '').toString(); 678 var product_type = $(this).closest('.search-box').data('selected-type'); 679 680 var data = { 681 'upc': upcInput, 682 'lot_code': lot_code, 683 'bestby': bestby, 684 'ip_address': (typeof user_ip !== 'undefined' ? user_ip : ''), 685 'action': '', 686 'logged_in_status': test_data.logged_in_status, 687 'product_type': product_type, 688 'failure_details': '', 689 'success_details': '', 690 'events': '' 691 }; 692 693 // Find the exact batch (should always exist since selections came from source_json) 694 var foundBatch = null; 695 var list = batchesByUPC[upcInput] || []; 696 for (var i = 0; i < list.length; i++) { 697 var b = list[i]; 698 var lot = (b.lotCode || '').toString().replace(/\s+/g, '').toLowerCase(); 699 var bb = (b.bestBy || '').toString().replace(/\s+/g, '').toUpperCase(); 700 if (lot === lot_code && bb === bestby) { foundBatch = b; break; } 701 } 702 703 if (!foundBatch) {
704 // Unexpected edge case: log once as generic failure 705 data.action = 'failed'; 706 data.failure_details = 'Selected UPC/Lot/BestBy not found in batches'; 707 data.events = 'form_submission_failed_db_error'; 708 sendLogData(data); 709 alert('Sorry, something went wrong. Please try again.'); 710 return; 711 } 712 713 // Fill the popup results 714 var results = foundBatch.results || []; 715 $('input.popup-checkbox').show(0); 716 for (var j = 0; j < results.length; j++) { 717 var r = results[j]; 718 var id = (r.name || '').replace(/ /g, '-'); 719 var val = (r.actualValue || '') === '' ? 'n/a' : r.actualValue; 720 721 if (val === 'n/a') { 722 $('.' + id + ' .popup-checkbox').hide(0); 723 } else { 724 var m = val.match(/([<>]?)(\d+(\.\d+)?)/); 725 if (m) { var p = m[1], n = m[2]; if (!n.includes('.')) n += '.0'; val = p + n; } 726 } 727 $('#' + id).text(val); 728 } 729 730 // Success path + popup 731 data.action = 'success'; 732 data.success_details = 'Search Success - From dropdown selection'; 733 data.events = 'form_submission_success,greensearchbox_clicked_success,'; 734 sendLogData(data); 735 736 $('.no-match').hide(0); 737 $('.page1').show(0); 738 $('.page2').hide(0); 739 740 var recaptchaVerified = $('.recaptcha-token').val() !== ''; 741 if (!recaptchaVerified) { alert('Please Verify Google Captcha!'); return; } 742 743 $.magnificPopup.open({ 744 items: { src: '#popup-page1' }, 745 type: 'inline', 746 removalDelay: 100, 747 mainClass: 'mfp-fade', 748 callbacks: { 749 close: function () { 750 $('.search-box').hide(0, function () { 751 $('#flavor_area').hide(0); 752 $('.upc-value,.lot-code,input.buy-date').val(''); 753 $('html, body').animate({ scrollTop: $("#product_types").offset().top }, 2000); 754 }); 755 } 756 } 757 }); 758 759 if (Array.isArray(test_data.rewardcodes) && test_data.rewardcodes.indexOf(upcInput) !== -1) { 760 $('.top_box').show(); 761 } else { 762 $('.top_box').hide(); 763 } 764 }); 765 766 767 /** 768 * --------------------------------------------------------------------------- 769 * Popup radios logging (unchanged) 770 * --------------------------------------------------------------------------- 771 */ 772 function convertDateToISO8601(dateStr) { 773 const months = { JAN: '01', FEB: '02', MAR: '03', APR: '04', MAY: '05', JUN: '06', JUL: '07', AUG: '08', SEP: '09', OCT: '10', NOV: '11', DEC: '12' }; 774 const day = dateStr.substring(0, 2), monthAbbr = dateStr.substring(2, 5).toUpperCase(), year = dateStr.substring(5);
775 const month = months[monthAbbr]; if (!month) throw new Error('Invalid month'); 776 return `${year}-${month}-${day}`; 777 } 778 $('.form-border input').on('click', function () { 779 var rawBestBuyDate = $('.buy-date').val().replace(/\s+/g, '').toUpperCase(); 780 var s_title = $('h2.search-title').text(); 781 if (s_title === 'Pouches with Meat') { 782 rawBestBuyDate = rawBestBuyDate.substring(0, 5) + '20' + rawBestBuyDate.substring(5); 783 } 784 var parts = convertDateToISO8601(rawBestBuyDate).split('-'); 785 var dObj = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10)); 786 var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; 787 var d = dObj.getDate(), suffix = (d >= 11 && d <= 13) ? 'th' : ({ 1: 'st', 2: 'nd', 3: 'rd' }[d % 10] || 'th'); 788 var readableDate = d + suffix + ' ' + monthNames[dObj.getMonth()] + ', ' + dObj.getFullYear(); 789 790 var upcSelected = $('.upc-value').val(); 791 var lotCode = $('.lot-code').val(); 792 793 $('.resultData').text(`Your Results for ${upcSelected}, lot code ${lotCode}, best by date ${readableDate}`); 794 $('.page1').slideUp(500); $('.page2').slideDown(500); 795 796 var value = $(this).val(); 797 var product_type = $('.search-box').data('selected-type'); 798 799 fetchIP().then(ip => { 800 var bestby = convertDateString($('.buy-date').val().replace(/\s+/g, '').toUpperCase()); 801 sendLogData({ 802 'upc': upcSelected, 803 'lot_code': lotCode, 804 'bestby': bestby, 805 'ip_address': ip, 806 'action': 'success', 807 'logged_in_status': test_data.logged_in_status, 808 'product_type': product_type, 809 'failure_details': '', 810 'success_details': '', 811 'events': 'understandingbox_click_' + value 812 }); 813 }); 814 }); 815});
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.