Samples Check with Barcode Popup

Adds a rapid barcode entry popup for AG Grid filters. With persistent button injection logic.

// ==UserScript==
// @name         Samples Check with Barcode Popup
// @namespace    http://tampermonkey.net/
// @version      3.2
// @description  Adds a rapid barcode entry popup for AG Grid filters. With persistent button injection logic.
// @author       Gemini & You
// @match        *://his.kaauh.org/lab/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration ---
    const TARGET_SEARCH_PLACEHOLDER = 'Search by MRN / NationalId & IqamaId';
    const TARGET_BARCODE_COLUMN_HEADER = 'Barcode'; // <--- IMPORTANT: This should match the column title exactly.

    // --- Core Functions ---

    function clearAllFilters() {
        console.log("Clearing all filters.");
        const clearAndNotify = (inputElement) => {
            if (inputElement && inputElement.value !== '') {
                inputElement.value = '';
                const updateEvent = new Event('input', { bubbles: true });
                inputElement.dispatchEvent(updateEvent);
            }
        };
        const mainSearchInput = document.querySelector(`input[placeholder="${TARGET_SEARCH_PLACEHOLDER}"]`);
        clearAndNotify(mainSearchInput);
        const agGridFilters = document.querySelectorAll('input.ag-floating-filter-input');
        agGridFilters.forEach(clearAndNotify);
    }

    // --- Manual 'Delete' key listener ---
    document.addEventListener('keydown', (event) => {
        // Do not trigger if user is typing in the popup
        if (document.activeElement.id === 'barcode-popup-input') return;

        if (event.key === 'Delete') {
            console.log("Manual clear triggered by 'Delete' key.");
            clearAllFilters();
        }
    });


    // --- Barcode Popup Functionality ---

    function findBarcodeFilterInput() {
        // 1. Find the header cell containing the title "Barcode"
        const allTitleSpans = document.querySelectorAll('.ag-header-row[aria-rowindex="1"] .ag-header-cell-text');
        let titleHeaderCell = null;
        allTitleSpans.forEach(span => {
            if (span.textContent.trim() === TARGET_BARCODE_COLUMN_HEADER) {
                titleHeaderCell = span.closest('.ag-header-cell');
            }
        });

        if (!titleHeaderCell) {
            console.error(`Could not find a column header with the text: "${TARGET_BARCODE_COLUMN_HEADER}"`);
            return null;
        }

        // 2. Get the horizontal position (left style) of that header cell
        const targetLeftPosition = titleHeaderCell.style.left;
        if (!targetLeftPosition) {
            console.error('Found the barcode header, but it has no "left" style property to match with.');
            return null;
        }

        // 3. Find the corresponding filter cell in the second header row that has the same horizontal position
        const allFilterCells = document.querySelectorAll('.ag-header-row[aria-rowindex="2"] .ag-header-cell');
        let filterCell = null;
        allFilterCells.forEach(cell => {
            if (cell.style.left === targetLeftPosition) {
                filterCell = cell;
            }
        });

        if (!filterCell) {
            console.error(`Found barcode header at left=${targetLeftPosition}, but could not find a filter cell at the same position.`);
            return null;
        }

        // 4. Find the input field within that specific filter cell
        const filterInput = filterCell.querySelector('input.ag-floating-filter-input');
        if (!filterInput) {
            console.error('Found the correct filter cell, but no input field was inside it.');
            return null;
        }

        console.log(`Successfully found barcode filter input at left: ${targetLeftPosition}`);
        return filterInput;
    }


    function createBarcodePopup() {
        // Check if popup already exists and return it if it does
        let popupContainer = document.getElementById('barcode-popup-container');
        if (popupContainer) return popupContainer;

        popupContainer = document.createElement('div');
        popupContainer.id = 'barcode-popup-container';
        Object.assign(popupContainer.style, {
            position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
            backgroundColor: 'rgba(0, 0, 0, 0.5)', display: 'none',
            justifyContent: 'center', alignItems: 'center', zIndex: '10000'
        });

        const popupBox = document.createElement('div');
        Object.assign(popupBox.style, {
            background: '#fff', padding: '25px', borderRadius: '8px',
            boxShadow: '0 5px 15px rgba(0,0,0,0.3)', textAlign: 'center',
            width: '350px'
        });

        const popupTitle = document.createElement('h3');
        popupTitle.textContent = 'Rapid Barcode Entry';
        Object.assign(popupTitle.style, { margin: '0 0 15px 0', color: '#333' });

        const popupInput = document.createElement('input');
        popupInput.id = 'barcode-popup-input';
        popupInput.type = 'text';
        popupInput.placeholder = 'Scan or type barcode and press Enter';
        Object.assign(popupInput.style, {
            width: '100%', padding: '10px', fontSize: '16px',
            border: '2px solid #ccc', borderRadius: '4px'
        });

        popupBox.appendChild(popupTitle);
        popupBox.appendChild(popupInput);
        popupContainer.appendChild(popupBox);
        document.body.appendChild(popupContainer);

        popupContainer.addEventListener('click', (e) => {
            if (e.target === popupContainer) {
                popupContainer.style.display = 'none';
            }
        });

        popupInput.addEventListener('keydown', (event) => {
            if (event.key === 'Enter') {
                event.preventDefault();
                const barcodeValue = popupInput.value.trim();
                if (!barcodeValue) return;

                const barcodeFilterInput = findBarcodeFilterInput();

                if (barcodeFilterInput) {
                    barcodeFilterInput.value = barcodeValue;
                    barcodeFilterInput.dispatchEvent(new Event('input', { bubbles: true }));
                    popupInput.value = ''; // Clear for next scan
                } else {
                    console.error(`Critical Error: Could not find the barcode filter on the page. Make sure a column is named "${TARGET_BARCODE_COLUMN_HEADER}".`);
                    popupInput.style.borderColor = 'red';
                    setTimeout(() => { popupInput.style.borderColor = '#ccc'; }, 2000);
                }
            }
        });

        return popupContainer;
    }


    // --- UI Setup ---

    function addControls(container) {
        // Create the popup on first run, but don't show it.
        const popup = createBarcodePopup();

        // Add the Barcode Entry Button if it doesn't exist
        if (!document.getElementById('barcode-entry-btn')) {
            const barcodeButton = document.createElement('button');
            barcodeButton.id = 'barcode-entry-btn';
            barcodeButton.textContent = 'Barcode Entry';
            Object.assign(barcodeButton.style, {
                backgroundColor: '#5bc0de',
                transition: 'background-color 0.3s', padding: '6px 12px', fontSize: '13px',
                fontWeight: 'bold', borderRadius: '6px', color: '#ffffff',
                border: '1px solid #285e79', cursor: 'pointer', marginLeft: '8px'
            });

            barcodeButton.addEventListener('click', () => {
                popup.style.display = 'flex';
                document.getElementById('barcode-popup-input').focus();
            });
            container.appendChild(barcodeButton);
            console.log('Barcode Entry button added.');
        }
    }

    // --- Persistent Injection Logic ---
    function placeButton() {
        const buttonContainer = document.querySelector('.reset-button-container');
        if (buttonContainer) {
            addControls(buttonContainer);
        }
    }

    // Use a MutationObserver as the primary, efficient method.
    const observer = new MutationObserver(placeButton);
    observer.observe(document.body, { childList: true, subtree: true });

    // *** NEW: Use a setInterval as a persistent backup ***
    // This will repeatedly check and ensure the button is present,
    // catching cases the observer might miss on complex web apps.
    setInterval(placeButton, 1000); // Check every 1 second

})();