KAAUH Lab Suite - Verification, Alerts & Enhancements

Combines verification buttons (F7/F8), dynamic alerts (>, NO RESULT, X-NORESULT, CL/CH) showing only the *last* alert per scan, checkbox automation, toggle back-nav, and improved scrolling.

// ==UserScript==
// @name         KAAUH Lab Suite - Verification, Alerts & Enhancements
// @namespace    Violentmonkey Scripts
// @version      6.3
// @description  Combines verification buttons (F7/F8), dynamic alerts (>, NO RESULT, X-NORESULT, CL/CH) showing only the *last* alert per scan, checkbox automation, toggle back-nav, and improved scrolling.
// @match        *://his.kaauh.org/lab/*
// @grant        none
// @author       Hamad AlShegifi
// @license      MIT
// ==/UserScript==


(function() {
    'use strict';

    function applySingleLineStyles() {
        const anchor = document.querySelector('li > a[href="#/lab-orders/doctor-request"]');
        if (anchor) {
            anchor.style.setProperty('white-space', 'nowrap', 'important');

            anchor.style.setProperty('overflow', 'visible', 'important');
            anchor.style.setProperty('text-overflow', 'unset', 'important');

            const spans = anchor.querySelectorAll('span');
            spans.forEach(span => {
                span.style.setProperty('display', 'inline', 'important');
                span.style.setProperty('font-size', '13px', 'important');
                span.style.setProperty('white-space', 'nowrap', 'important');
            });
        }

        const simplifySpan = (selector) => {
            const span = document.querySelector(selector);
            if (span) {
                span.style.setProperty('display', 'inline', 'important');
                span.style.setProperty('font-size', '20px', 'important');
                span.style.setProperty('white-space', 'nowrap', 'important');
                span.style.setProperty('overflow', 'visible', 'important');
                span.style.setProperty('text-overflow', 'unset', 'important');
                span.textContent = span.textContent.replace(/\s+/g, ''); // optional
            }
        };

        simplifySpan('span.to-do');
        simplifySpan('span.pending-orders');
    }

    // Debounce function to limit how often applySingleLineStyles is called
    function debounce(func, wait) {
        let timeout;
        return function(...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), wait);
        };
    }

    const debouncedStyleUpdater = debounce(applySingleLineStyles, 300);

    const observer = new MutationObserver(debouncedStyleUpdater);
    observer.observe(document.body, { childList: true, subtree: true });

    // Initial run
    applySingleLineStyles();
})();

(function () {
  'use strict';

  /*** CONFIGURATION (Main Script) ***/
  const CONFIG_MAIN = {
    URLS: {
      EDIT_PAGE_PREFIX: 'https://his.kaauh.org/lab/#/lab-orders/edit-lab-order/',
    },
    SELECTORS: {
      VERIFY1_BTN: '#custom-script-buttons button.verify1-btn',
      VERIFY2_BTN: '#custom-script-buttons button.verify2-btn',
      COMPLETE_TECH: 'button.dropdown-item[translateid="test-results.CompleteTechnicalVerification"]',
      COMPLETE_MED: 'button.dropdown-item[translateid="test-results.CompleteMedicalVerification"]',
      FINAL_VERIFY: 'button.btn-success.btn-sm.min-width[translateid="test-verification.Verify"]',
      NEXT_BTN: 'button#btnNext',
      UNCHECKED_BOX: 'span.ag-icon-checkbox-unchecked[unselectable="on"]',
      CHECKBOX_ROW: '.ag-row',
      TEST_DESC_CELL: '[col-id="TestDesc"]', // Added for clarity
      ORDERED_STATUS_CELL: 'div[col-id="ResultStatus"]', // Added for clarity
      TOAST: {
        CONTAINER: '#toast-container',
        CLOSE_BTN: 'button.toast-close-button',
        SUCCESS: '.toast-success',
      }
    },
    CHECK_INTERVALS: {
      UNDEFINED_URL: 200,
      ORDERED_SCAN: 500,
      DISABLED_BTN_CHECK: 1000, // Check for disabled buttons every second
    },
    EXCLUDE_WORDS: [ // Words to exclude from automatic checkbox selection
      'culture', "gram's stain", 'stain', 'bacterial', 'fungal',
      'pcr', 'meningitis', 'mrsa', 'mid', 'stream', 'cryptococcus'
    ]
  };

  /*** STATE FLAGS ***/
  let verify1Clicked = false;
  let verify2Clicked = false;
  let verify1Toggle = localStorage.getItem('verify1Toggle') === 'true';
  let verify2Toggle = localStorage.getItem('verify2Toggle') === 'true';
  let hasScrolledToOrderedRow = false; // Track if we've scrolled to the "Ordered" row
  let lastDisabledButtonAlertTime = 0; // Track when we last alerted about disabled buttons
  const DISABLED_ALERT_COOLDOWN = 30000; // 30 seconds cooldown between alerts

  /*** UTILITY FUNCTIONS ***/
  const logDebugMain = msg => console.debug(`[LabScript Main] ${msg}`);
  const isCorrectPage = () => window.location.href.startsWith(CONFIG_MAIN.URLS.EDIT_PAGE_PREFIX);

  // Function to show alert for disabled buttons
  function showDisabledButtonAlert(message) {
    const now = Date.now();
    if (now - lastDisabledButtonAlertTime < DISABLED_ALERT_COOLDOWN) {
      return; // Still in cooldown period
    }
    lastDisabledButtonAlertTime = now;

    // Create a custom modal alert
    const modalOverlay = document.createElement('div');
    modalOverlay.id = 'disabled-button-alert-overlay';
    Object.assign(modalOverlay.style, {
      position: 'fixed', top: '0', left: '0', width: '100vw', height: '100vh',
      backgroundColor: 'rgba(0, 0, 0, 0.7)', zIndex: '10000', display: 'flex',
      justifyContent: 'center', alignItems: 'center'
    });

    const modalBox = document.createElement('div');
    Object.assign(modalBox.style, {
      backgroundColor: '#fff', padding: '25px', borderRadius: '8px',
      boxShadow: '0 5px 15px rgba(0,0,0,0.3)', width: 'auto', maxWidth: '80%',
      textAlign: 'center', borderTop: '5px solid #f0ad4e' // Warning color
    });

    const title = document.createElement('h3');
    title.textContent = 'Button Disabled';
    title.style.color = '#d9534f'; // Red color for title
    title.style.marginTop = '0';
    modalBox.appendChild(title);

    const messageElem = document.createElement('p');
    messageElem.textContent = message;
    messageElem.style.fontSize = '16px';
    messageElem.style.marginBottom = '20px';
    modalBox.appendChild(messageElem);

    const okButton = document.createElement('button');
    okButton.textContent = 'OK';
    Object.assign(okButton.style, {
      padding: '8px 20px', borderRadius: '5px', backgroundColor: '#5cb85c',
      color: '#fff', border: 'none', cursor: 'pointer', fontSize: '16px'
    });
    okButton.onclick = () => {
      document.body.removeChild(modalOverlay);
    };
    modalBox.appendChild(okButton);

    modalOverlay.appendChild(modalBox);
    document.body.appendChild(modalOverlay);
    okButton.focus();
  }

  const addFontAwesome = () => {
    if (!document.querySelector('link[href*="font-awesome"]')) {
      logDebugMain("Injecting Font Awesome.");
      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css';
      document.head.appendChild(link);
    }
  };

  /*** BUTTON + ICON CREATORS ***/
  function createVerifyButton(label, className, onClick, id) {
    const button = document.createElement('button');
    button.type = 'button';
    button.className = className; // Base classes like btn btn-sm
    button.classList.add(id); // Specific ID class like verify1-btn
    button.innerText = label;

    // Apply consistent styling, overriding potential theme issues
    const styles = {
      'font-family': 'Arial, sans-serif',
      'font-size': '14px',
      'font-weight': 'normal',
      'color': '#ffffff',
      'background-color': className.includes('success') ? '#28a745' : '#2594d9', // Initial colors
      'padding': '8px 16px',
      'border': 'none',
      'border-radius': '5px',
      'text-shadow': 'none',
      'cursor': 'pointer',
      'margin-right': '5px',
      'line-height': '1.5',
      'vertical-align': 'middle',
    };

    for (const [prop, value] of Object.entries(styles)) {
      button.style.setProperty(prop, value, 'important');
    }

    button.onclick = onClick;
    return button;
  }

  function createToggleIcon(id, isActive, onClick) {
    const icon = document.createElement('span');
    icon.id = id;
    icon.innerHTML = `<i class="fas fa-arrow-circle-left" aria-hidden="true" style="color: ${isActive ? '#008000' : '#d1cfcf'}; font-size: 1.3em; vertical-align: middle;"></i>`;
    icon.style.cursor = 'pointer';
    icon.style.marginRight = '10px';
    icon.style.marginLeft = '-1px'; // Fine-tuning position
    icon.title = "Toggle: Go back automatically after this verification success toast is clicked";
    icon.onclick = onClick;
    return icon;
  }

  function handleVerifyToggle(type) {
    logDebugMain(`Toggling auto-back for ${type}`);
    const toggle = type === 'verify1' ? !verify1Toggle : !verify2Toggle;
    localStorage.setItem(type + 'Toggle', toggle);
    const icon = document.querySelector(`#${type}Icon i`);
    if (icon) icon.style.setProperty('color', toggle ? '#008000' : '#d1cfcf', 'important');

    if (type === 'verify1') verify1Toggle = toggle;
    else verify2Toggle = toggle;
  }

  /*** BUTTONS INJECTION ***/
  function addButtons() {
    if (document.getElementById('custom-script-buttons') || !isCorrectPage()) return;

    const nextButton = document.querySelector(CONFIG_MAIN.SELECTORS.NEXT_BTN);
    if (!nextButton || !nextButton.parentNode) {
        // Wait a bit and retry if the next button isn't rendered yet
        setTimeout(addButtons, 500);
        return;
    }

    logDebugMain("Adding custom buttons to page.");
    const buttonDiv = document.createElement('div');
    buttonDiv.id = 'custom-script-buttons';
    buttonDiv.style.setProperty('display', 'inline-block', 'important');
    buttonDiv.style.setProperty('margin-left', '10px', 'important');
    buttonDiv.style.setProperty('vertical-align', 'middle', 'important');

    const verify1Button = createVerifyButton('VERIFY1 (F7)', 'btn btn-success btn-sm', () => {
      logDebugMain('VERIFY1 (F7) clicked.');
      verify1Clicked = true;
      verify2Clicked = false;
      checkAllVisibleBoxesWithoutDuplicates();
      setTimeout(clickCompleteTechnicalVerificationButton, 500); // Delay for checkbox processing
    }, 'verify1-btn');

    const verify2Button = createVerifyButton('VERIFY2 (F8)', 'btn btn-primary btn-sm', () => {
      logDebugMain('VERIFY2 (F8) clicked.');
      verify2Clicked = true;
      verify1Clicked = false;
      checkAllVisibleBoxesWithoutDuplicates();
      setTimeout(clickCompleteMedicalVerificationButton, 500); // Delay for checkbox processing
    }, 'verify2-btn');

    const verify1Icon = createToggleIcon('verify1Icon', verify1Toggle, () => handleVerifyToggle('verify1'));
    const verify2Icon = createToggleIcon('verify2Icon', verify2Toggle, () => handleVerifyToggle('verify2'));

    const credit = document.createElement('span');
    credit.textContent = "Modded by: Hamad AlShegifi";
    credit.style.cssText = `
      font-size: 11px !important;
      font-weight: bold !important;
      color: #e60000 !important;
      margin-left: 15px !important;
      border: 1px solid #e60000 !important;
      border-radius: 5px !important;
      padding: 3px 6px !important;
      background-color: #ffffff !important;
      vertical-align: middle !important;
    `;

    buttonDiv.append(verify1Button, verify1Icon, verify2Button, verify2Icon, credit);
    // Insert after the next button's parent container if needed, adjust as per actual DOM
    nextButton.parentNode.insertBefore(buttonDiv, nextButton.nextSibling);
    logDebugMain("Custom buttons added.");
  }

  /*** CORE ACTIONS ***/
  function checkAllVisibleBoxesWithoutDuplicates() {
    logDebugMain("Checking all visible boxes...");
    const selectedTests = new Set();
    const boxes = document.querySelectorAll(CONFIG_MAIN.SELECTORS.UNCHECKED_BOX);
    let count = 0;
    boxes.forEach(box => {
      const row = box.closest(CONFIG_MAIN.SELECTORS.CHECKBOX_ROW);
      // Ensure the row is actually visible (offsetParent check)
      if (row && row.offsetParent !== null) {
        const testDescElement = row.querySelector(CONFIG_MAIN.SELECTORS.TEST_DESC_CELL);
        const descText = testDescElement?.textContent.trim().toLowerCase();

        if (descText) {
            const isExcluded = CONFIG_MAIN.EXCLUDE_WORDS.some(word => descText.includes(word));
            if (!selectedTests.has(descText) && !isExcluded) {
                selectedTests.add(descText);
                box.click();
                count++;
                // logDebugMain(`Clicked box for test: ${descText}`);
             } else if (selectedTests.has(descText)) {
                // logDebugMain(`Skipping duplicate test: ${descText}`);
             } else if (isExcluded) {
                // logDebugMain(`Skipping excluded test: ${descText}`);
             }
        } else {
            logDebugMain("Found checkbox in row without valid test description.");
        }
      }
    });
    logDebugMain(`Clicked ${count} checkboxes.`);
  }

  const clickButton = (selector, callback) => {
      const btn = document.querySelector(selector);
      if (btn && !btn.disabled) {
          logDebugMain(`Clicking button: ${selector}`);
          btn.click();
          if (callback) {
              // Add delay before executing next step (e.g., final verify)
              setTimeout(callback, 500);
          }
          return true;
      } else if (btn && btn.disabled) {
          logDebugMain(`Button found but disabled: ${selector}`);
          // Show alert for disabled button
          const buttonName = selector.includes('CompleteTechnicalVerification') ? 'Complete Technical Verification' :
                           selector.includes('CompleteMedicalVerification') ? 'Complete Medical Verification' :
                           selector.includes('Verify') ? 'Final Verify' : 'Button';
          showDisabledButtonAlert(`${buttonName} button is disabled. Please check if you have selected all required tests or if verification is already done.`);
      } else {
          logDebugMain(`Button not found: ${selector}`);
      }
      return false;
  };

  const clickCompleteTechnicalVerificationButton = () => {
      clickButton(CONFIG_MAIN.SELECTORS.COMPLETE_TECH, clickFinalVerifyButton);
  };

  const clickCompleteMedicalVerificationButton = () => {
      clickButton(CONFIG_MAIN.SELECTORS.COMPLETE_MED, clickFinalVerifyButton);
  };

  const clickFinalVerifyButton = () => {
      clickButton(CONFIG_MAIN.SELECTORS.FINAL_VERIFY);
  };

  // Function to periodically check for disabled buttons
  function checkForDisabledButtons() {
    if (!isCorrectPage()) return;

    // Check if any verification buttons are disabled when they shouldn't be
    const verify1Btn = document.querySelector(CONFIG_MAIN.SELECTORS.VERIFY1_BTN);
    const verify2Btn = document.querySelector(CONFIG_MAIN.SELECTORS.VERIFY2_BTN);

    if (verify1Btn && verify1Btn.disabled) {
      logDebugMain("VERIFY1 button is disabled");
      showDisabledButtonAlert("varification is disabled. Make sure tests not already verified.");
    }

    if (verify2Btn && verify2Btn.disabled) {
      logDebugMain("VERIFY2 button is disabled");
      showDisabledButtonAlert("varification is disabled. Make sure tests not already verified.");
    }
  }

  // Handles the '/undefined' URL issue by closing toasts
  function checkUrlAndTriggerClickForUndefined() {
    if (window.location.href.endsWith('/undefined')) {
        logDebugMain("Detected '/undefined' URL. Attempting to close toast.");
        const closeBtn = document.querySelector(`${CONFIG_MAIN.SELECTORS.TOAST.CONTAINER} ${CONFIG_MAIN.SELECTORS.TOAST.CLOSE_BTN}`);
        if (closeBtn) {
            closeBtn.click();
            logDebugMain("Clicked toast close button.");
            // Optionally, navigate back or to a known safe page
            // window.history.back();
        }
    }
  }

  // Monitors for 'Ordered' status and updates VERIFY1 button/scrolls
  function monitorOrderedStatus() {
    const rows = document.querySelectorAll('div[role="row"]');
    let hasOrdered = false;
    let firstOrderedRow = null;

    rows.forEach(row => {
        // Ensure row is potentially visible before querying deeper
        if (row.offsetParent !== null) {
            const cell = row.querySelector(CONFIG_MAIN.SELECTORS.ORDERED_STATUS_CELL);
            if (cell?.textContent.includes('Ordered')) {
                if (!firstOrderedRow) firstOrderedRow = row;
                hasOrdered = true;
            }
        }
    });

    const btn = document.querySelector(CONFIG_MAIN.SELECTORS.VERIFY1_BTN);
    if (btn) {
      if (hasOrdered) {
        // Update button style only if it's not already in warning state
        if (!btn.classList.contains('btn-warning')) {
            logDebugMain("Found 'Ordered' status, updating VERIFY1 button.");
            btn.className = 'btn btn-warning verify1-btn'; // Ensure base btn class remains
            btn.innerText = 'VERIFY1 (F7)🚩incomplete!!';
            btn.style.setProperty('background-color', '#fab641', 'important'); // Orange/Yellow
            btn.style.setProperty('color', '#050505', 'important'); // Dark text for contrast
        }

        // Scroll the first ordered row into view (centered) only if we haven't scrolled yet for this page view
        if (firstOrderedRow && !hasScrolledToOrderedRow) {
          logDebugMain("Scrolling to first 'Ordered' row.");
          // Temporarily set smooth scroll for the action
          document.documentElement.style.scrollBehavior = 'smooth';
          firstOrderedRow.scrollIntoView({ behavior: 'auto', block: 'center' }); // 'auto' behavior uses thedocumentElement style

          hasScrolledToOrderedRow = true; // Mark as scrolled to prevent re-scrolling
          // Reset scroll behavior after a delay to allow scroll to finish and permit manual scrolling
          setTimeout(() => {
            document.documentElement.style.scrollBehavior = 'auto';
            logDebugMain("Scroll complete, scroll behavior reset.");
          }, 1000); // 1 second delay
        }
      } else {
        // Reset button style only if it's currently in warning state
         if (btn.classList.contains('btn-warning')) {
            logDebugMain("No 'Ordered' status found, resetting VERIFY1 button.");
            btn.className = 'btn btn-success btn-sm verify1-btn'; // Ensure base classes remain
            btn.innerText = 'VERIFY1 (F7)';
            btn.style.setProperty('background-color', '#28a745', 'important'); // Default green
            btn.style.setProperty('color', '#ffffff', 'important'); // Default white text
         }
         // Reset scroll flag if page no longer has ordered items
         hasScrolledToOrderedRow = false;
      }
    }
  }

  // Use setInterval for periodic checks
  setInterval(monitorOrderedStatus, CONFIG_MAIN.CHECK_INTERVALS.ORDERED_SCAN);


  /*** TOAST OBSERVER (for Auto-Back) ***/
  const toastObserver = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
      mutation.addedNodes.forEach(node => {
        // Check if the added node itself is a success toast or contains one
        if (node.nodeType === Node.ELEMENT_NODE) {
            let successToast = null;
            if (node.matches(CONFIG_MAIN.SELECTORS.TOAST.SUCCESS)) {
                successToast = node;
            } else if (node.querySelector) {
                 successToast = node.querySelector(CONFIG_MAIN.SELECTORS.TOAST.SUCCESS);
            }

            if (successToast) {
                logDebugMain("Success toast detected.");
                // Add listener to the specific toast that appeared
                successToast.addEventListener('click', () => {
                    logDebugMain("Success toast clicked.");
                    if ((verify1Clicked && verify1Toggle) || (verify2Clicked && verify2Toggle)) {
                        logDebugMain("Auto-back triggered.");
                        window.history.back();
                    }
                    // Reset flags regardless of whether back navigation occurred
                    verify1Clicked = false;
                    verify2Clicked = false;
                 }, { once: true }); // Use { once: true } so the listener removes itself after firing
            }
        }
      });
    });
  });

  // Observe the body for toasts appearing (might need a more specific container if available)
  toastObserver.observe(document.body, { childList: true, subtree: true });


  // --- Start of Integrated Alerts Scanner Code ---
  (function () {
    'use strict'; // Inner scope strict mode

    const CONFIG_ALERTS = {
        SCAN_INTERVAL: 150, // Faster scan for critical alerts
        FLASH_COLOR: "pink",
        FLASH_INTERVAL: 500,
        RESULT_CELL_SELECTOR: 'div[role="gridcell"][col-id="TestResult"] app-result-value-render div',
        CRITICAL_FLAG_SELECTOR: 'div[role="gridcell"][col-id="LTFlag"] app-ref-high-low div span.critial-alret-indication', // Note: Original script had typo "critial-alret" - keeping it if that's the actual class name
        CHECKBOX_PARENT_ROW_SELECTOR: '.ag-row', // Re-using main config selector concept
        NO_RESULT_MESSAGE: "NO-RESULT DETECTED!!",

    };

    let isScanningActive = false;
    let issueScanIntervalId = null;
    let alertAudio = null; // Reuse audio object

    function logDebugAlerts(message) {
        console.log(`[Alerts Scanner] ${message}`);
    }

    function playAlertSound() {
        if (!alertAudio) {
            alertAudio = new Audio(CONFIG_ALERTS.SOUND_URL);
            alertAudio.loop = true; // Loop the sound
        }
        alertAudio.play().catch(e => console.warn("[Alerts Scanner] Sound playback blocked by browser:", e));
        // Stop the sound after a few seconds
        setTimeout(() => {
            if (alertAudio) {
                alertAudio.pause();
                alertAudio.currentTime = 0; // Reset for next play
            }
        }, 3000); // Play for 3 seconds
    }

    // function isVisible(element) { // Already checked by offsetParent in main script, might not be needed here if checks use row context
    //     return !!(element && (element.offsetWidth || element.offsetHeight || element.getClientRects().length));
    // }

    function applyFlashingEffect(row) {
        if (!row || row.dataset.flashing === 'true') return; // Already flashing or invalid row

        row.dataset.flashing = 'true';
        const originalBg = row.style.backgroundColor || 'transparent'; // Get current or default
        row.dataset.originalBg = originalBg;
        row.style.transition = "background-color 0.5s ease"; // Smooth transition
        let isPink = false;

        // Store interval ID on the element itself to manage it
        const intervalId = setInterval(() => {
            // Check if the row still exists and is still meant to be flashing
            if (!document.body.contains(row) || row.dataset.flashing === 'false') {
                clearInterval(intervalId);
                row.style.transition = ''; // Remove transition before resetting color
                row.style.setProperty("background-color", row.dataset.originalBg || 'transparent', "important");
                // Clean up data attributes
                delete row.dataset.flashing;
                delete row.dataset.originalBg;
                delete row.dataset.flashIntervalId;
                return;
            }
            isPink = !isPink;
            row.style.setProperty("background-color", isPink ? CONFIG_ALERTS.FLASH_COLOR : originalBg, "important");
        }, CONFIG_ALERTS.FLASH_INTERVAL);

        row.dataset.flashIntervalId = intervalId; // Store reference for stopping
    }

    function stopFlashingEffect(row) {
        if (row && row.dataset.flashing === 'true') {
            row.dataset.flashing = 'false'; // Signal the interval to stop and clean up
            // The interval itself handles clearing and resetting style
        }
    }

    // Use sessionStorage to track notifications *per browser session* for specific issues
    function getNotificationSessionKey(type, identifier = 'general') {
        // Include pathname to make keys specific to the current view/page if URL changes within the SPA
        return `labAlertNotified_${window.location.pathname}_${type}_${identifier}`;
    }

    function hasAlreadyNotified(type, identifier = 'general') {
        return sessionStorage.getItem(getNotificationSessionKey(type, identifier)) === 'true';
    }

    function setNotificationFlag(type, identifier = 'general') {
        sessionStorage.setItem(getNotificationSessionKey(type, identifier), 'true');
    }

    // Creates a custom modal alert dialog
    function createCustomAlert(message, callback) {
        // Prevent duplicate alerts if one is already showing
        if (document.getElementById('custom-alert-modal-overlay')) return;

        logDebugAlerts(`Displaying custom alert: ${message}`);
        const modalOverlay = document.createElement('div');
        modalOverlay.id = 'custom-alert-modal-overlay'; // ID to prevent duplicates
        // Basic styling for overlay
        Object.assign(modalOverlay.style, {
            position: 'fixed', top: '0', left: '0', width: '100vw', height: '100vh',
            backgroundColor: 'rgba(0, 0, 0, 0.6)', zIndex: '10000', display: 'flex',
            justifyContent: 'center', alignItems: 'center'
        });

        const modalBox = document.createElement('div');
         // Basic styling for modal box
        Object.assign(modalBox.style, {
            backgroundColor: '#fff', padding: '30px', borderRadius: '8px',
            boxShadow: '0 5px 15px rgba(0,0,0,0.2)', width: 'auto', minWidth:'300px', maxWidth: '90%',
            textAlign: 'center', borderTop: '5px solid #f0ad4e' // Warning color top border
        });

        const messageElem = document.createElement('p');
        messageElem.textContent = message;
        messageElem.style.fontSize = '16px';
        messageElem.style.marginBottom = '20px';
        modalBox.appendChild(messageElem);

        const okButton = document.createElement('button');
        okButton.textContent = 'OK';
         // Basic styling for button
         Object.assign(okButton.style, {
            padding: '10px 25px', borderRadius: '5px', backgroundColor: '#5cb85c', // Green color
            color: '#fff', border: 'none', cursor: 'pointer', fontSize: '16px'
        });
        okButton.onclick = () => {
            logDebugAlerts("User acknowledged alert.");
            document.body.removeChild(modalOverlay);
            if (callback) callback();
        };
        modalBox.appendChild(okButton);
        modalOverlay.appendChild(modalBox);

        document.body.appendChild(modalOverlay);
        okButton.focus(); // Focus the OK button
    }

    function checkForIssues() {
        const resultDivs = document.querySelectorAll(CONFIG_ALERTS.RESULT_CELL_SELECTOR);
        const criticalDivs = document.querySelectorAll(CONFIG_ALERTS.CRITICAL_FLAG_SELECTOR);
        const potentialAlerts = []; // Collect potential new alerts for this scan cycle

        function scrollToRowNearest(element) {
            const row = element.closest(CONFIG_ALERTS.CHECKBOX_PARENT_ROW_SELECTOR);
            if (row) {
                logDebugAlerts("Scrolling to issue row.");
                document.documentElement.style.scrollBehavior = 'smooth'; // Ensure smooth scroll for alerts too
                try {
                    // Center is often better for drawing attention than 'nearest'
                    row.scrollIntoView({ behavior: 'auto', block: 'center' });
                } catch (e) { // Fallback
                     row.scrollIntoView();
                }
                 // Reset scroll behavior after a short delay
                setTimeout(() => { document.documentElement.style.scrollBehavior = 'auto'; }, 500);
                return true;
            }
            return false;
        }

        // --- Check for Critical Flags ---
        criticalDivs.forEach((div, index) => {
            const rowElement = div.closest(CONFIG_ALERTS.CHECKBOX_PARENT_ROW_SELECTOR);
            if (rowElement && rowElement.offsetParent !== null) { // Check visibility
                 const text = div.textContent?.trim() || '';
                 if (text === "CL" || text === "CH") {
                    const specificRowKey = rowElement.getAttribute('row-id') || `${text}_${index}`; // Unique identifier
                    if (!hasAlreadyNotified('critical', specificRowKey)) {
                        const message = text === "CL" ? "CRITICAL LOW RESULT DETECTED !!" : "CRITICAL HIGH RESULT DETECTED !!";
                        logDebugAlerts(`Found potential critical alert: ${message} (Key: ${specificRowKey})`);
                        potentialAlerts.push({
                            element: div, rowElement: rowElement, type: 'critical',
                            message: message, rowId: specificRowKey
                        });
                    } else {
                         // Apply flashing even if notified, as it might be a persistent issue
                         applyFlashingEffect(rowElement);
                    }
                 }
            }
        });

        // --- Check for No Results and Dilution ('>') ---
        resultDivs.forEach((div, index) => {
            const rowElement = div.closest(CONFIG_ALERTS.CHECKBOX_PARENT_ROW_SELECTOR);
             if (rowElement && rowElement.offsetParent !== null) { // Check visibility
                const text = div.textContent?.trim().toLowerCase() || '';
                const isNoResultType = (text === "no result" || text === "no-xresult" || text === "x-noresult"); // Add other variations if needed
                const needsDilution = text.includes(">");

                // Handle No Result
                if (isNoResultType) {
                    const specificRowKey = rowElement.getAttribute('row-id') || `noresult_${index}`;
                     if (!hasAlreadyNotified('noresult', specificRowKey)) {
                        logDebugAlerts(`Found potential no-result alert. (Key: ${specificRowKey})`);
                        potentialAlerts.push({
                             element: div, rowElement: rowElement, type: 'noresult',
                             message: CONFIG_ALERTS.NO_RESULT_MESSAGE, rowId: specificRowKey
                         });
                    } else {
                        applyFlashingEffect(rowElement);
                    }
                }

                 // Handle Dilution (>)
                 if (needsDilution) {
                     const specificRowKey = rowElement.getAttribute('row-id') || `dilution_${index}`;
                     if (!hasAlreadyNotified('greaterThan', specificRowKey)) {
                        logDebugAlerts(`Found potential dilution alert. (Key: ${specificRowKey})`);
                         potentialAlerts.push({
                             element: div, rowElement: rowElement, type: 'greaterThan',
                             message: "Dilution is required for this sample (> detected)!", rowId: specificRowKey
                         });
                     } else {
                         // Always flash if dilution is needed and already notified
                         applyFlashingEffect(rowElement);
                     }
                 }
            }
        });

        // --- Process Alerts ---
        if (potentialAlerts.length > 0) {
            // Find the *first* new alert in the list to display and scroll to
            const alertToShow = potentialAlerts.find(alert => !hasAlreadyNotified(alert.type, alert.rowId));

            if (alertToShow) {
                logDebugAlerts(`Triggering alert for type: ${alertToShow.type}, rowId: ${alertToShow.rowId}`);
                playAlertSound();
                setNotificationFlag(alertToShow.type, alertToShow.rowId); // Mark as notified *before* showing alert
                applyFlashingEffect(alertToShow.rowElement); // Apply flashing effect
                scrollToRowNearest(alertToShow.element); // Scroll to the problematic element/row
                createCustomAlert(alertToShow.message); // Show the custom modal
            }

             // Apply flashing to all potential alerts found in this cycle, even if not the primary one shown
             potentialAlerts.forEach(alert => {
                 applyFlashingEffect(alert.rowElement);
             });

            return true; // Indicates an issue was found/processed
        }
        return false; // No new issues found this cycle
    }


    function startContinuousScanning() {
        if (isScanningActive) return;
        logDebugAlerts("Starting continuous issue scanning...");
        isScanningActive = true;
        // Clear any previous interval just in case
        if (issueScanIntervalId) clearInterval(issueScanIntervalId);

        // Initial check
        checkForIssues();

        // Start interval
        issueScanIntervalId = setInterval(checkForIssues, CONFIG_ALERTS.SCAN_INTERVAL);
    }

    function stopContinuousScanning() {
        if (!isScanningActive) return; // Only stop if active
        logDebugAlerts("Stopping continuous issue scanning.");
        if (issueScanIntervalId) {
            clearInterval(issueScanIntervalId);
            issueScanIntervalId = null;
        }
         // Stop flashing on all rows that might still be flashing
        document.querySelectorAll(CONFIG_ALERTS.CHECKBOX_PARENT_ROW_SELECTOR + '[data-flashing="true"]').forEach(stopFlashingEffect);
        isScanningActive = false;

        // Stop and reset audio if it's playing
         if (alertAudio && !alertAudio.paused) {
             alertAudio.pause();
             alertAudio.currentTime = 0;
         }
    }

    // Use MutationObserver to automatically start/stop scanning when results grid appears/disappears
    const observer = new MutationObserver((mutationsList, observer) => {
        // Check if the relevant grid elements exist now
        const resultsGridExists = document.querySelector(CONFIG_ALERTS.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG_ALERTS.CRITICAL_FLAG_SELECTOR);

        if (resultsGridExists) {
            if (!isScanningActive) {
                 // Delay slightly before starting scan after elements appear, allowing grid to fully render
                setTimeout(startContinuousScanning, 300);
            }
        } else {
            if (isScanningActive) {
                stopContinuousScanning();
            }
        }
    });

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

    // Initial check in case the grid is already present on script load
    if (document.querySelector(CONFIG_ALERTS.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG_ALERTS.CRITICAL_FLAG_SELECTOR)) {
        startContinuousScanning();
    }

  })(); // End of Integrated Alerts Scanner IIFE
  // --- End of Integrated Alerts Scanner Code ---


  /*** HOTKEYS ***/
  document.addEventListener('keydown', e => {
    // Ignore keydowns if focus is inside an input/textarea/select
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') {
      return;
    }

    if (e.key === 'F7') {
      e.preventDefault();
      logDebugMain("F7 key pressed.");
      document.querySelector(CONFIG_MAIN.SELECTORS.VERIFY1_BTN)?.click();
    } else if (e.key === 'F8') {
      e.preventDefault();
      logDebugMain("F8 key pressed.");
      document.querySelector(CONFIG_MAIN.SELECTORS.VERIFY2_BTN)?.click();
    }
  });

  /*** MAIN PAGE WATCHER (for SPA Navigation) ***/
  const pageObserver = new MutationObserver(() => {
    if (isCorrectPage()) {
      // If on the correct page, ensure setup is complete
      addFontAwesome(); // Ensure FontAwesome is loaded
      if (!document.getElementById('custom-script-buttons')) {
           // Buttons not present, try adding them (handles SPA navigation where elements might be removed/re-added)
           addButtons();
           // Reset scroll flag on navigation to the correct page
           hasScrolledToOrderedRow = false;
      }
    } else {
      // If not on the correct page, remove custom elements
      const customButtons = document.getElementById('custom-script-buttons');
      if (customButtons) {
        logDebugMain("Navigated away from edit page, removing custom buttons.");
        customButtons.remove();
      }
      // Stop alert scanner if navigating away (though the alert scanner's own observer should also handle this)
      // Consider calling stopContinuousScanning() here if needed, but it might be redundant.
    }
  });

   // Observe the body for major changes that might indicate page navigation in an SPA
  pageObserver.observe(document.body, { childList: true, subtree: true });

  /*** INITIAL BOOTSTRAP ***/
  logDebugMain("KAAUH Lab Suite script initializing...");
  if (isCorrectPage()) {
    addFontAwesome();
    addButtons(); // Initial attempt to add buttons
  }
  // Start the interval check for the '/undefined' URL issue immediately
  setInterval(checkUrlAndTriggerClickForUndefined, CONFIG_MAIN.CHECK_INTERVALS.UNDEFINED_URL);
  // The 'monitorOrderedStatus' interval is started within its own definition block
  // The 'Alerts Scanner' starts based on its MutationObserver detecting the grid
  // Start disabled button checker
  setInterval(checkForDisabledButtons, CONFIG_MAIN.CHECK_INTERVALS.DISABLED_BTN_CHECK);

})(); // End of Main UserScript IIFE

QingJ © 2025

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