Enhanced AG Grid Functionality

Adds vibrant row hover effect (overriding all cell colors), conditional status cell styling for test status and sample status, and static red background for emergency rows in AG Grid.

目前为 2025-04-17 提交的版本。查看 最新版本

// ==UserScript==
// @name         Enhanced AG Grid Functionality
// @version      4.3
// @description  Adds vibrant row hover effect (overriding all cell colors), conditional status cell styling for test status and sample status, and static red background for emergency rows in AG Grid.
// @match        https://his.kaauh.org/lab/*
// @author       Hamad AlShegifi
// @grant        GM_addStyle
// @namespace    http://tampermonkey.net/
// ==/UserScript==

(function () {
    'use strict';

    // Inject CSS for hover effect, status cell styling, and emergency highlighting
    GM_addStyle(`
        .ag-row {
            transition: background-color 0.3s ease;
        }
        .vibrant-hover {
            /* Background color and text color are now applied to cells within the hovered row */
        }
        .ag-row.vibrant-hover .ag-cell {
            background-color: #87dced !important; /* Light blue color */
            color: black !important; /* Black text color */
            font-weight: bold !important; /* Bold font */
        }
        .ag-cell[col-id="testStatus"].status-verified-level2,
        .ag-cell[col-id="testStatus"].status-verified-level1 {
            background-color: #90EE90 !important; /* Light green color */
        }
        .ag-cell[col-id="testStatus"].status-ordered {
            background-color: #FFFFE0 !important; /* Light yellow color */
        }
        .ag-cell[col-id="testStatus"].status-resulted {
            background-color: #FFA500 !important; /* Orange color */
            color: black !important; /* Ensure text is readable on orange */
        }
        .emergency-row {
            background-color: #ffe0e0 !important; /* Light red */
        }
        .ag-cell[col-id="sampleStatus"].status-received {
            background-color: #90EE90 !important; /* Light green color for Received sample status */
        }
    `);
    // List of col-id attributes to target for hover effect
    const targetColumnIdsForHover = [
        "orderNo", "testId", "testDescription", "clusterMrn",
        "hospitalMrn", "patientName", "dob", "nationalIqamaId",
        "department", "clinic", "doctor", "analyzer",
        "orderDateAndTime", "lastUpdatedDate", "sampleStatus",
        "referenceLab", "accessionNo", "barcode", "sequenceNo",
        "primaryPatientId", "referenceLabDesc", "testStatus",
        "orderLastModifiedOnEpoch", "orderCreatedOnEpoch", "equipmentName", "doctorName", "localMrn",
        "dateOfBirth", "idNumber"
    ];

    const columnsToUncheck = [
        'Lab Order No', 'Hospital MRN', 'DOB', 'Test ID', 'National/Iqama Id',
        'Department', 'Doctor', 'Analyzer', 'Reference Lab',
        'Accession No', 'Sequence No','Age','Container Type','Storage Condition'
    ];

    let hasRunOnce = false; // Prevents running the column unchecking code more than once

    function isSpecificPage() {
        return window.location.href.endsWith('/#/lab-orders/lab-test-analyzer');
    }

    function areColumnsChecked() {
        return columnsToUncheck.some(column => isColumnChecked(column));
    }

    function isColumnChecked(labelText) {
        const labels = document.querySelectorAll('.ag-column-tool-panel-column-label');
        for (const label of labels) {
            if (label.textContent.trim() === labelText) {
                const checkbox = label.parentElement.querySelector('.ag-icon-checkbox-checked');
                if (checkbox) return true; // Column is checked
            }
        }
        return false; // Column is not checked
    }

    function ensureColumnsUnchecked() {
        if (hasRunOnce || !isSpecificPage()) return;
        if (!areColumnsChecked()) return; // Do nothing if columns are not checked

        hasRunOnce = true;
        console.log("Unchecking checked columns...");

        setTimeout(() => {
            columnsToUncheck.forEach(column => clickColumnLabel(column));
        }, 1000);
    }

    function ensureOtherColumnsChecked() {
        if (!isSpecificPage()) return;
        console.log("Ensuring all other columns are checked...");

        const allLabels = document.querySelectorAll('.ag-column-tool-panel-column-label');
        allLabels.forEach(label => {
            const labelText = label.textContent.trim();
            if (!columnsToUncheck.includes(labelText)) {
                const checkbox = label.parentElement.querySelector('.ag-icon-checkbox-unchecked');
                if (checkbox) {
                    label.click(); // Click to check the column if unchecked
                }
            }
        });
    }

    function clickColumnLabel(labelText) {
        if (!isSpecificPage()) return;
        const labels = document.querySelectorAll('.ag-column-tool-panel-column-label');
        labels.forEach(label => {
            if (label.textContent.trim() === labelText) {
                const checkbox = label.parentElement.querySelector('.ag-icon-checkbox-checked');
                if (checkbox) {
                    label.click(); // Click only if checked
                }
            }
        });
    }

    function highlightEmergencyRows() {
        const rows = document.querySelectorAll('div[role="row"]');

        rows.forEach(row => {
            const clinicCell = row.querySelector('div[col-id="clinic"]');
            if (clinicCell && clinicCell.textContent.trim() === 'EMERGENCY') {
                row.classList.add('emergency-row');
            } else {
                row.classList.remove('emergency-row'); // Remove the class if the condition is no longer met
            }
        });
    }

    function initColumnToggle() {
        if (!isSpecificPage()) return;
        console.log("Checking if columns need to be unchecked and highlighting emergency rows...");

        let attempts = 0;
        const interval = setInterval(() => {
            if (document.querySelector('.ag-side-buttons')) {
                ensureColumnsUnchecked();
                ensureOtherColumnsChecked();
                highlightEmergencyRows(); // Call the highlighting function here
                clearInterval(interval);
            }
            if (++attempts > 10) {
                highlightEmergencyRows(); // Ensure highlighting runs even if sidebar isn't found quickly
                clearInterval(interval);
            }
        }, 500);

        // Also run highlighting on initial load attempt
        highlightEmergencyRows();
    }

    // Function to apply hover effects to the entire row when any target cell is hovered
    function applyHoverEffect() {
        document.querySelectorAll('.ag-cell').forEach(cell => {
            const colId = cell.getAttribute('col-id');
            if (colId && targetColumnIdsForHover.includes(colId)) {
                const row = cell.closest('.ag-row'); // Find the parent row
                if (row) {
                    // Add hover effect to the entire row on mouseenter
                    cell.addEventListener('mouseenter', () => {
                        row.classList.add('vibrant-hover'); // Add hover effect to the row
                    });
                    // Remove hover effect on mouseleave
                    cell.addEventListener('mouseleave', () => {
                        row.classList.remove('vibrant-hover'); // Remove hover effect from the row
                    });
                }
            }
        });
    }

    // Function to apply conditional background color to the "Status" column
    function applyStatusCellStyle() {
        document.querySelectorAll('.ag-cell[col-id="testStatus"]').forEach(cell => {
            const statusValue = cell.textContent.trim();
            cell.classList.remove('status-verified-level2', 'status-verified-level1', 'status-ordered', 'status-resulted'); // Remove previous styles

            if (statusValue === "VerifiedLevel2" || statusValue === "VerifiedLevel1") {
                cell.classList.add('status-verified-level2');
            } else if (statusValue === "Ordered") {
                cell.classList.add('status-ordered');
            } else if (statusValue === "Resulted") {
                cell.classList.add('status-resulted');
            }
        });

        // Apply style for Sample Status = Received
        document.querySelectorAll('.ag-cell[col-id="sampleStatus"]').forEach(cell => {
            if (cell.textContent.trim() === "Received") {
                cell.classList.add('status-received');
            } else {
                cell.classList.remove('status-received'); // Ensure the class is removed if the value changes
            }
        });
    }

    const observer = new MutationObserver(() => {
        applyHoverEffect();
        applyStatusCellStyle();
        highlightEmergencyRows(); // Call highlightEmergencyRows on every DOM change

        if (isSpecificPage()) {
            hasRunOnce = false; // Reset run flag for repeated executions
            initColumnToggle();
        }
    });

    // **Important: Observing the correct AG Grid body viewport**
    const gridBodyViewport = document.querySelector('.ag-body-viewport'); // Observe the grid body viewport
    if (gridBodyViewport) {
        observer.observe(gridBodyViewport, { childList: true, subtree: false }); // subtree should be false here as we are observing direct children (rows)
    } else {
        console.warn("AG Grid body viewport not found. Falling back to body observer.");
        observer.observe(document.body, { childList: true, subtree: true });
    }


    window.addEventListener('load', () => {
        applyHoverEffect();
        applyStatusCellStyle();
        if (isSpecificPage()) {
            initColumnToggle();
        }
    });

    // Function to set the dropdown value (keeping this separate as it seems independent)
    (function() {
        'use strict';

        // Function to set the dropdown value
        function setDropdownValue() {
            const dropdown = document.getElementById("dropdownPaginationPageSize");
            if (dropdown && dropdown.value !== "100") {
                dropdown.value = "100"; // Set the value to "100"

                // Trigger the 'change' event
                const event = new Event('change', { bubbles: true });
                dropdown.dispatchEvent(event);

                console.log("Dropdown value set to 100");
            }
        }

        // Function to observe changes in the DOM for the dropdown
        function observeDOMForDropdown() {
            const observer = new MutationObserver(() => {
                setDropdownValue(); // Check and set the dropdown value when changes are detected
            });

            // Observe the entire document for changes
            observer.observe(document.body, {
                childList: true,
                subtree: true,
            });

            console.log("MutationObserver is active for dropdown");
        }

        // Run the function when the page is fully loaded
        window.addEventListener('load', () => {
            setDropdownValue(); // Initial check
            observeDOMForDropdown(); // Start observing for dynamic changes
        });
    })();

})();
(function() {
    'use strict';

    // Add CSS styles
    GM_addStyle(`
        #ordered-tests-tooltip {
            position: absolute;
            bottom: -10px;
            left: 0;
            transform: translateY(100%);
            background: white;
            border: 1px solid #e0e0e0;
            border-radius: 6px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            z-index: 10000;
            font-family: 'Segoe UI', Arial, sans-serif;
            display: none;
            pointer-events: none;
            transition: opacity 0.2s ease;
            white-space: nowrap;
            max-width: none;
            overflow: visible;
        }
#ordered-tests-tooltip {
    /* existing styles */
    min-width: 300px;
}

.tooltip-header.resulted-header {
    background: #f0fff0;
    border-bottom: 1px solid #d0ffd0;
    color: #2e7d32;
}

.test-item.completed {
    color: #2e7d32;
}

.test-item.completed .test-icon {
    color: #4caf50;
    font-weight: bold;
    margin-right: 8px;
}

.test-item.pending {
    color: #d32f2f;
}

.no-tests {
    padding: 8px 0;
    color: #666;
    font-style: italic;
    text-align: center;
}
        .verify1-btn:hover #ordered-tests-tooltip {
            display: block;
            opacity: 1;
        }

        .tooltip-header {
            background: #fff1f1;
            border-bottom: 1px solid #ffd6d6;
            padding: 12px 15px;
            font-weight: 600;
            color: #d32f2f;
            display: flex;
            align-items: center;
            gap: 10px;
            white-space: nowrap;
        }

        .tooltip-content {
            padding: 10px 15px;
            white-space: nowrap;
        }

        .test-item {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 2px 0;
    font-size: 13px;
}

        .test-item:last-child {
            border-bottom: none;
        }

        .test-bullet {
            color: #ff5252;
            font-size: 14px;
        }

        .tooltip-footer {
            background: #f9f9f9;
            border-top: 1px solid #eee;
            padding: 8px 15px;
            font-size: 11px;
            color: #757575;
            text-align: right;
            white-space: nowrap;
        }

        .no-ordered-tests {
            padding: 15px;
            text-align: center;
            color: #666;
            font-style: italic;
            background: #f8faf8;
            white-space: nowrap;
        }
    `);
    // Configuration
    const CONFIG = {
        POLL_INTERVAL: 1000,
        MAX_WAIT_TIME: 30000,
        SCAN_INTERVAL: 3000,
        COLUMN_PATTERNS: {
            TEST_DESC: ['desc', 'testdesc', 'name'],
            STATUS: ['status', 'resultstatus', 'state']
        }
    };

    // Create the tooltip element
    function createTooltip() {
        const tooltip = document.createElement('div');
        tooltip.id = 'ordered-tests-tooltip';
        return tooltip;
    }

    // Find cells by column patterns
    function findCellByPatterns(row, patterns) {
        const cells = row.querySelectorAll('[col-id]');
        for (const cell of cells) {
            const colId = cell.getAttribute('col-id')?.toLowerCase();
            if (patterns.some(pattern => colId?.includes(pattern))) {
                return cell;
            }
        }
        return null;
    }

    // Get all tests with "Ordered" status
    function getOrderedTests() {
        const leftRows = Array.from(document.querySelectorAll('.ag-pinned-left-cols-container .ag-row'));
        const centerRows = Array.from(document.querySelectorAll('.ag-center-cols-container .ag-row'));
        const rightRows = Array.from(document.querySelectorAll('.ag-pinned-right-cols-container .ag-row'));

        const orderedTests = [];

        for (let i = 0; i < leftRows.length; i++) {
            const leftRow = leftRows[i];
            const centerRow = centerRows[i] || {};
            const rightRow = rightRows[i] || {};

            const testDescCell = findCellByPatterns(leftRow, CONFIG.COLUMN_PATTERNS.TEST_DESC);
            const statusCell = findCellByPatterns(rightRow, CONFIG.COLUMN_PATTERNS.STATUS) ||
                             findCellByPatterns(centerRow, CONFIG.COLUMN_PATTERNS.STATUS);

            const testName = testDescCell?.textContent?.trim();
            const status = statusCell?.textContent?.trim().toLowerCase() || '';

            if (testName && status.includes('ordered')) {
                orderedTests.push(testName);
            }
        }

        return orderedTests;
    }

// Update tooltip content with ordered tests (sorted alphabetically)
// Update the tooltip content generation function
function updateTooltipContent(tooltip) {
    const now = new Date();
    const timeString = now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
    const dateString = now.toLocaleDateString([], {month: 'short', day: 'numeric'});

    // Get all tests and categorize them
    const { orderedTests, resultedTests } = getAllTests();

    // Create a Set to remove duplicates, then sort alphabetically (case-insensitive)
    const uniqueOrderedTests = [...new Set(orderedTests)].sort((a, b) =>
        a.localeCompare(b, undefined, {sensitivity: 'base'})
    );

    const uniqueResultedTests = [...new Set(resultedTests)].sort((a, b) =>
        a.localeCompare(b, undefined, {sensitivity: 'base'})
    );

    tooltip.innerHTML = `
        <div class="tooltip-header">
            <span>⏳ Pending Tests (${uniqueOrderedTests.length})</span>
        </div>
        <div class="tooltip-content">
            ${uniqueOrderedTests.length > 0 ?
                uniqueOrderedTests.map(test => `
                    <div class="test-item pending">
                        <span class="test-bullet">•</span>
                        <span>${test}</span>
                    </div>
                `).join('') : `
                <div class="no-tests">No pending tests</div>`
            }
        </div>

        <div class="tooltip-header resulted-header">
            <span>✅ Tests ready for verification (${uniqueResultedTests.length})</span>
        </div>
        <div class="tooltip-content">
            ${uniqueResultedTests.length > 0 ?
                uniqueResultedTests.map(test => `
                    <div class="test-item completed">
                        <span class="test-icon">✓</span>
                        <span>${test}</span>
                    </div>
                `).join('') : `
                <div class="no-tests">No resulted tests</div>`
            }
        </div>

        <div class="tooltip-footer">
            Updated: ${dateString} ${timeString}
        </div>
    `;}

// Function to get all tests and categorize them
function getAllTests() {
    const leftRows = Array.from(document.querySelectorAll('.ag-pinned-left-cols-container .ag-row'));
    const centerRows = Array.from(document.querySelectorAll('.ag-center-cols-container .ag-row'));
    const rightRows = Array.from(document.querySelectorAll('.ag-pinned-right-cols-container .ag-row'));

    const orderedTests = [];
    const resultedTests = [];

    for (let i = 0; i < leftRows.length; i++) {
        const leftRow = leftRows[i];
        const centerRow = centerRows[i] || {};
        const rightRow = rightRows[i] || {};

        const testDescCell = findCellByPatterns(leftRow, CONFIG.COLUMN_PATTERNS.TEST_DESC);
        const statusCell = findCellByPatterns(rightRow, CONFIG.COLUMN_PATTERNS.STATUS) ||
                         findCellByPatterns(centerRow, CONFIG.COLUMN_PATTERNS.STATUS);

        const testName = testDescCell?.textContent?.trim();
        const status = statusCell?.textContent?.trim().toLowerCase() || '';

        if (testName) {
            if (status.includes('ordered')) {
                orderedTests.push(testName);
            } else if (status.includes('resulted')) {
                resultedTests.push(testName);
            }
        }
    }

    return { orderedTests, resultedTests };
}
    // Main function to attach tooltip to VERIFY1 button
    function attachTooltipToVerifyButton() {
        const verifyButton = document.querySelector('.verify1-btn');
        if (!verifyButton) return false;

        if (verifyButton.querySelector('#ordered-tests-tooltip')) return true;

        const tooltip = createTooltip();
        verifyButton.style.position = 'relative';
        verifyButton.appendChild(tooltip);

        verifyButton.addEventListener('mouseenter', () => {
            updateTooltipContent(tooltip);
            tooltip.style.display = 'block';
        });

        verifyButton.addEventListener('mouseleave', () => {
            tooltip.style.display = 'none';
        });

        return true;
    }

    // Wait for button to appear
    function waitForButton() {
        const startTime = Date.now();
        const checkInterval = setInterval(() => {
            if (attachTooltipToVerifyButton() || Date.now() - startTime > CONFIG.MAX_WAIT_TIME) {
                clearInterval(checkInterval);
            }
        }, CONFIG.POLL_INTERVAL);
    }

    // Start the script
    if (document.readyState === 'complete') {
        waitForButton();
    } else {
        window.addEventListener('load', waitForButton);
    }

    // Watch for SPA navigation
    const observer = new MutationObserver(() => {
        if (!document.querySelector('.verify1-btn #ordered-tests-tooltip')) {
            attachTooltipToVerifyButton();
        }
    });
    observer.observe(document.body, { childList: true, subtree: true });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址