KAAUH Lab PatientID TD Counter (Inline Colorful) - Extended

Inline colorful sample counter next to the close button in modal header and in the footer of the Sample Receive modal.

As of 2025-05-15. See the latest version.

// ==UserScript==
// @name          KAAUH Lab PatientID TD Counter (Inline Colorful) - Extended
// @namespace     Violentmonkey Scripts
// @version       1.7
// @description   Inline colorful sample counter next to the close button in modal header and in the footer of the Sample Receive modal.
// @match         https://his.kaauh.org/lab/*
// @author        Hamad AlShegifi
// @grant         none
// ==/UserScript==

(function () {
    'use strict';

    // Function to create the counter element
    function createCounterElement(id) {
        const wrapper = document.createElement('span');
        wrapper.id = id;
        wrapper.style.marginRight = 'auto'; // Pushes everything else to the right
        wrapper.style.fontSize = '18px';
        wrapper.style.fontWeight = 'bold';
        wrapper.style.display = 'inline-flex';
        wrapper.style.alignItems = 'center';
        wrapper.style.marginLeft = '10px'; // Add some space from the right

        const label = document.createElement('span');
        label.textContent = 'SAMPLES COUNT: ';
        label.style.marginRight = '6px';
        label.style.color = '#333';

        const badge = document.createElement('span');
        badge.className = 'sample-count-badge'; // Use class for badge
        badge.textContent = '0';
        badge.style.backgroundColor = '#6c757d'; // gray by default
        badge.style.color = '#fff';
        badge.style.padding = '2px 8px';
        badge.style.borderRadius = '12px';
        badge.style.fontSize = '18px';
        badge.style.minWidth = '24px';
        badge.style.textAlign = 'center';

        wrapper.appendChild(label);
        wrapper.appendChild(badge);

        return wrapper;
    }

    // Function to update a specific counter based on inputs within a specific modal
    function updateSpecificCounter(modalElement, counterElement, inputSelector) {
        const badge = counterElement.querySelector('.sample-count-badge');
        if (!badge || !modalElement || !modalElement.contains(counterElement)) {
             // If badge, modal or counter is gone, the interval cleanup will handle it
             return;
        }

        const inputs = modalElement.querySelectorAll(inputSelector);
        const count = inputs.length;
        badge.textContent = count;

        // Change color based on count
        badge.style.backgroundColor = count > 0 ? '#28a745' : '#6c757d'; // green if > 0, grey if 0
    }

    function observeModals() {
        // Keep track of active modal intervals to clear them when modals close
        const activeModalIntervals = new Map(); // Map: modalElement -> intervalId

        const observer = new MutationObserver(() => {
            // --- Observe Original Modal (Sample Receive - likely by sample ID) ---
            // This modal is identified by 'modal-container.show' and the close button '#closebtn-smplrecieve'
            const originalModal = document.querySelector('modal-container.show');
            const originalModalCloseBtn = originalModal ? originalModal.querySelector('#closebtn-smplrecieve') : null;
            const originalModalHeader = originalModalCloseBtn ? originalModalCloseBtn.parentElement : null;
            const originalCounterId = 'inline-patientid-counter-original';

            if (originalModal && originalModalHeader && !originalModalHeader.querySelector('#' + originalCounterId)) {
                // Found original modal, counter not present in header
                const counter = createCounterElement(originalCounterId);
                 // Insert the counter after the modal title for better placement
                const modalTitle = originalModalHeader.querySelector('.modal-title');
                 if (modalTitle) {
                     originalModalHeader.insertBefore(counter, modalTitle.nextSibling);
                 } else {
                    // Fallback if title not found, insert before close button
                     originalModalHeader.insertBefore(counter, originalModalCloseBtn);
                 }


                // Start updating this counter
                const interval = setInterval(() => {
                    // Check if the modal is still in the document body
                    if (!document.body.contains(originalModal)) {
                        // Modal is closed, clean up the interval and the counter
                        clearInterval(activeModalIntervals.get(originalModal));
                        activeModalIntervals.delete(originalModal);
                        const existingCounter = document.getElementById(originalCounterId);
                        if (existingCounter) existingCounter.remove();
                    } else {
                        // Update counter for this modal
                        // Selector for inputs in the original modal
                         const inputSelector = 'td input[formcontrolname="PatientID"]';
                        updateSpecificCounter(originalModal, counter, inputSelector);
                    }
                }, 500); // Update every 500ms

                activeModalIntervals.set(originalModal, interval);
            }

            // --- Observe New Modal (Sample Receive by CPL/Receptionist) ---
            // This modal is identified by its specific structure and footer buttons like '#btnclose-smplcollection'
            const newModalApp = document.querySelector('.modal-dialog.modal-xlg-fullwidth app-sample-receive-receptionist');
            // Find the top-level modal dialog element
            const newModal = newModalApp ? newModalApp.closest('.modal-dialog') : null;
            // Find the specific footer within this modal's app component
            const newModalFooter = newModalApp ? newModalApp.querySelector('.modal-footer') : null;
            const newCounterId = 'inline-patientid-counter-new';

            // Check if the new modal is present and its footer exists, and the counter is not already in the footer
            if (newModal && newModalFooter && !newModalFooter.querySelector('#' + newCounterId)) {
                 // Found new modal, counter not present in footer
                const counter = createCounterElement(newCounterId);
                 // Insert the counter as the first child in the footer to push buttons to the right
                 newModalFooter.insertBefore(counter, newModalFooter.firstChild);

                 // Start updating this counter
                 const interval = setInterval(() => {
                     // Check if the modal is still in the document body
                     if (!document.body.contains(newModal)) {
                         // Modal is closed, clean up the interval and the counter
                         clearInterval(activeModalIntervals.get(newModal));
                         activeModalIntervals.delete(newModal);
                         const existingCounter = document.getElementById(newCounterId);
                         if (existingCounter) existingCounter.remove();
                     } else {
                         // Update counter for this modal
                         // Selector for inputs in the new modal.
                         // Assuming PatientID inputs are within the table body with formarrayname="TubeTypeList"
                         const inputSelector = 'tbody[formarrayname="TubeTypeList"] input[formcontrolname="PatientID"]';
                         // If 'PatientID' inputs are not used in this table, you might need a different selector,
                         // e.g., to count rows: 'tbody[formarrayname="TubeTypeList"] tr'
                         updateSpecificCounter(newModal, counter, inputSelector);
                     }
                 }, 500); // Update every 500ms

                activeModalIntervals.set(newModal, interval);
            }
        });

        // Start observing the body for changes that indicate modals opening/closing
        // This will detect when the modal elements are added to or removed from the DOM
        observer.observe(document.body, { childList: true, subtree: true });
    }

    // Start observing once the page is loaded
    window.addEventListener('load', observeModals);
})();