Rapid Barcode Receiver (Resize Fix v2)

Uses !important CSS override to fix column resizing on pages with conflicting styles.

// ==UserScript==
// @name         Rapid Barcode Receiver (Resize Fix v2)
// @namespace    http://tampermonkey.net/
// @version      6.1
// @description  Uses !important CSS override to fix column resizing on pages with conflicting styles.
// @author       Gemini & User
// @match        *://*/*
// @grant        GM_addStyle
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/colResizable-1.6.min.js
// @license      MIT
// ==/UserScript==

// --- Utility function to wait for dynamically loaded elements ---
function waitForKeyElements(selector, callback, bWaitOnce) {
    var targetNodes, bDone = false;
    targetNodes = $(selector);
    if (targetNodes && targetNodes.length > 0) {
        targetNodes.each(function() { callback($(this)); });
        bDone = bWaitOnce;
    }
    var obs = new MutationObserver(function(mutations) {
        if (bDone) { obs.disconnect(); return; }
        mutations.forEach(function(mutation) {
            var newNodes = mutation.addedNodes;
            if (newNodes && newNodes.length > 0) {
                for (var i = 0, len = newNodes.length; i < len; i++) {
                    var newNode = newNodes[i];
                    if (newNode.nodeType === 1) {
                        var $node = $(newNode);
                        if ($node.is(selector)) {
                            callback($node);
                            if (bWaitOnce) bDone = true;
                        } else if ($node.find(selector).length > 0) {
                            $node.find(selector).each(function() { callback($(this)); });
                            if (bWaitOnce) bDone = true;
                        }
                    }
                }
            }
        });
    });
    obs.observe(document.body, { childList: true, subtree: true });
}

// --- Main script logic ---
function initializeScript() {
    const closeButtonSelector = "#closebtn-smplrecieve, #btnclose-smplcollection";

    waitForKeyElements(closeButtonSelector, (closeButton) => {
        if (closeButton.parent().find('#rapidReceiveBtn').length > 0) return;

        let rapidReceiveBtn = $('<button type="button" class="btn btn-color-1" id="rapidReceiveBtn">Rapid Receiver</button>');
        rapidReceiveBtn.css('margin-right', '5px');
        closeButton.before(rapidReceiveBtn);

        if ($('#rapidReceiveModal').length === 0) {
            let modalHTML = `
                <div id="rapidReceiveModal" class="rr-modal-backdrop">
                    <div class="rr-modal-content">
                        <div class="rr-modal-header">
                            <h2>Rapid Barcode Processor</h2>
                            <span class="rr-close-button">&times;</span>
                        </div>
                        <div class="rr-modal-body">
                            <input type="text" id="newBarcodeEntry" placeholder="Scan or paste barcodes here and press Enter">
                            <div id="rr-table-container">
                                <table id="barcodeTable">
                                    <thead>
                                        <tr>
                                            <th class="rr-th-no">No.</th>
                                            <th class="rr-th-barcode">Barcode</th>
                                            <th class="rr-th-location">Location</th>
                                            <th class="rr-th-status">Status</th>
                                        </tr>
                                    </thead>
                                    <tbody id="barcodeListBody"></tbody>
                                </table>
                            </div>
                        </div>
                        <div class="rr-modal-footer">
                            <span id="rr-counter" class="rr-counter-style">0 Barcodes Entered</span>
                            <div>
                                <button id="clearBarcodesBtn" class="btn btn-danger">Clear</button>
                                <button id="processBarcodesBtn" class="btn btn-success">Process</button>
                            </div>
                        </div>
                    </div>
                </div>
            `;
            $('body').append(modalHTML);

            GM_addStyle(`
                .rr-modal-backdrop { display: none; position: fixed; z-index: 9999; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.6); }
                .rr-modal-content { display: flex; flex-direction: column; background-color: #f8f9fa; margin: 5% auto; padding: 25px; border: none; width: 90%; max-width: 900px; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); height: 80vh; }
                .rr-modal-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #dee2e6; padding-bottom: 15px; margin-bottom: 15px; }
                .rr-modal-header h2 { margin: 0; font-size: 1.5rem; color: #343a40; }
                .rr-modal-body { flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; }
                .rr-modal-footer { display: flex; justify-content: space-between; align-items: center; border-top: 1px solid #dee2e6; padding-top: 15px; margin-top: 15px; }
                .rr-close-button { color: #aaa; font-size: 32px; font-weight: bold; cursor: pointer; line-height: 1; }
                .rr-close-button:hover { color: black; }
                #newBarcodeEntry { width: 100%; padding: 10px; font-size: 16px; margin-bottom: 15px; border: 1px solid #ced4da; border-radius: 4px; box-sizing: border-box; }
                #newBarcodeEntry:focus { border-color: #80bdff; outline: 0; box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25); }
                #rr-table-container { flex-grow: 1; overflow-y: auto; border: 1px solid #dee2e6; border-radius: 4px; background-color: #fff; }
                #barcodeTable { width: 100%; border-collapse: collapse; table-layout: fixed; }
                #barcodeTable th, #barcodeTable td { padding: 4px 15px; text-align: left; border-bottom: 1px solid #e9ecef; vertical-align: middle; }
                #barcodeTable th { background-color: #e9ecef; color: #495057; position: sticky; top: 0; }
                /* MODIFIED: Added !important to force vertical lines to appear */
                #barcodeTable th:not(:last-child), #barcodeTable td:not(:last-child) { border-right: 1px solid #dee2e6 !important; }
                #barcodeTable tbody tr:nth-child(even) { background-color: #f8f9fa; }
                .rr-th-no { width: 5%; }
                .rr-th-barcode { width: 35%; }
                .rr-th-location { width: 15%; }
                .rr-th-status { width: 45%; }
                td.status-cell { font-weight: bold; text-align: left; }
                td.status-cell.success { text-align: center; color: green; font-size: 1.2rem; }
                .rr-counter-style { font-size: 14px; font-weight: bold; color: #555; }
                #clearBarcodesBtn { margin-right: 10px; }
                .rr-error-text { color: #D8000C; font-weight: bold; font-size: 12px; }
                /* MODIFIED: Forceful grip style to ensure it is visible and clickable */
                .JCLRgrip {
                    background-color: #007bff !important;
                    width: 3px !important;
                    z-index: 99999 !important;
                }
            `);

            // Delay initialization to prevent conflicts
            setTimeout(() => {
                $('#barcodeTable').colResizable({
                    liveDrag: true,
                    gripInnerHtml: "<div class='JCLRgrip'></div>",
                    minWidth: 30 // Prevent columns from becoming too small
                });
            }, 100);

            // --- Event Listeners ---
            $('.rr-close-button').on('click', () => $('#rapidReceiveModal').hide());
            $(window).on('click', (event) => {
                if ($(event.target).is('#rapidReceiveModal')) $('#rapidReceiveModal').hide();
            });
            $('#processBarcodesBtn').on('click', processBarcodes);
            $('#clearBarcodesBtn').on('click', clearAllBarcodes);
            $('#newBarcodeEntry').on('keydown', handleBarcodeEntry);
            $('#newBarcodeEntry').on('paste', handleBarcodePaste);
        }

        $('#rapidReceiveBtn').on('click', openRapidReceiver);

    }, false);
}

function openRapidReceiver() {
    $('#rapidReceiveModal').show();
    clearAllBarcodes();
    $('#newBarcodeEntry').focus();
    $('#rr-counter').css('color', '');
    $('#processBarcodesBtn, #clearBarcodesBtn').prop('disabled', false);
    $('#processBarcodesBtn').text('Process');
}

function clearAllBarcodes() {
    $('#barcodeListBody').empty();
    $('#newBarcodeEntry').val('').prop('disabled', false);
    updateBarcodeCount();
}

function updateBarcodeCount() {
    const count = $('#barcodeListBody tr').length;
    $('#rr-counter').text(`${count} Barcodes Entered`);
}

function addBarcodeToTable(barcode) {
    barcode = barcode.trim();
    if (barcode === '') return;

    const currentBarcodes = new Set();
    $('#barcodeListBody td:nth-child(2)').each(function() {
        currentBarcodes.add($(this).text());
    });
    if (currentBarcodes.has(barcode)) return;

    const rowIndex = $('#barcodeListBody tr').length;
    const rowNum = rowIndex + 1;

    const letter = String.fromCharCode(65 + Math.floor(rowIndex / 10));
    const number = (rowIndex % 10) + 1;
    const location = `${letter}${number}`;

    const newRow = `
        <tr>
            <td>${rowNum}</td>
            <td>${barcode}</td>
            <td>${location}</td>
            <td class="status-cell"></td>
        </tr>
    `;
    $('#barcodeListBody').append(newRow);
    updateBarcodeCount();
}

function handleBarcodeEntry(event) {
    if (event.key === 'Enter') {
        event.preventDefault();
        const barcodeInput = $(this);
        const barcode = barcodeInput.val();
        addBarcodeToTable(barcode);
        barcodeInput.val('');
    }
}

function handleBarcodePaste(event) {
    event.preventDefault();
    const pastedText = (event.originalEvent || event).clipboardData.getData('text/plain');
    const barcodes = pastedText.split(/\r?\n/).filter(line => line.trim() !== '');
    barcodes.forEach(barcode => addBarcodeToTable(barcode));
    $(this).val('');
}


// --- Barcode processing logic ---
async function processBarcodes() {
    const barcodeInput = $('#barcodecollection');
    const processButton = $('#processBarcodesBtn');
    const clearButton = $('#clearBarcodesBtn');
    const entryInput = $('#newBarcodeEntry');
    const counterElement = $('#rr-counter');

    const rowsToProcess = $('#barcodeListBody tr').filter(function() {
        const status = $(this).find('td.status-cell').text().trim();
        return status !== '✔️';
    });

    if (rowsToProcess.length === 0) return alert('No new or failed barcodes to process.');
    if (barcodeInput.length === 0) return alert('Error: Main barcode input field "#barcodecollection" not found.');

    processButton.prop('disabled', true).text('Processing...');
    clearButton.prop('disabled', true);
    entryInput.prop('disabled', true);

    const INTER_BARCODE_DELAY = 1200;
    const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
    const dispatchEvent = (element, eventType) => element.dispatchEvent(new Event(eventType, { bubbles: true }));

    const simulateEnter = async (element) => {
        const commonEventProps = { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true };
        element.dispatchEvent(new KeyboardEvent('keydown', commonEventProps));
        await sleep(50);
        element.dispatchEvent(new KeyboardEvent('keyup', commonEventProps));
    };

    let processedCount = 0;
    for (const row of rowsToProcess) {
        processedCount++;
        counterElement.text(`Processing: ${processedCount} / ${rowsToProcess.length}`);

        const $row = $(row);
        const barcode = $row.find('td:nth-child(2)').text();
        const statusCell = $row.find('td.status-cell');
        statusCell.removeClass('success').html('...');

        const inputElement = barcodeInput[0];
        inputElement.value = barcode;
        dispatchEvent(inputElement, 'input');
        dispatchEvent(inputElement, 'change');
        await sleep(100);
        inputElement.focus();
        await simulateEnter(inputElement);

        await sleep(600);

        const $errorAlert = $("div.alert.alert-danger:visible");
        if ($errorAlert.length > 0) {
            const errorMessage = $errorAlert.find('strong').text().trim() || 'Unknown Error';
            statusCell.html(`<span class="rr-error-text">${errorMessage}</span>`);
        } else {
            statusCell.addClass('success').html('✔️');
        }

        $('.alert-dismissable .close').click();
        await sleep(INTER_BARCODE_DELAY - 600);
    }

    processButton.prop('disabled', false).text('Process');
    clearButton.prop('disabled', false);
    entryInput.prop('disabled', false).focus();
    counterElement.text('✅ Complete!').css('color', 'green');
}

// --- Logic to handle dynamic page loads ---
let lastUrl = location.href;
setInterval(() => {
    const currentUrl = location.href;
    if (currentUrl !== lastUrl) {
        lastUrl = currentUrl;
        initializeScript();
    }
}, 500);

// Initial run
initializeScript();