KAAUH Lab Enhancement Suite 1 (Buttons, Alerts, Auto-Actions) - Modified Alert Handling

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.

目前為 2025-04-05 提交的版本,檢視 最新版本

// ==UserScript==
// @name         KAAUH Lab Enhancement Suite 1 (Buttons, Alerts, Auto-Actions) - Modified Alert Handling
// @namespace    Violentmonkey Scripts
// @version      5.6.4
// @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';

  // --- Configuration ---
  const CONFIG = {
    // General & Debugging
    DEBUG_MODE: true,
    PERSISTENT_MODALS: true, // Modals require manual dismissal
    NO_RESULT_MESSAGE: "NO-RESULT DETECTED!!", // Standardized message

    // Script 1: Buttons & Verification
    TARGET_EDIT_PAGE_URL_PREFIX: 'https://his.kaauh.org/lab/#/lab-orders/edit-lab-order/',
    EXCLUDE_WORDS: ['culture', "gram's stain", 'stain', 'bacterial', 'fungal', 'culture', 'pcr', 'Meningitis', 'MRSA', 'Mid', 'stream', 'Cryptococcus'],
    VERIFY1_BUTTON_SELECTOR: '#custom-script-buttons button.btn-success',
    VERIFY2_BUTTON_SELECTOR: '#custom-script-buttons button.btn-primary',
    COMPLETE_TECH_VERIFY_SELECTOR: 'button.dropdown-item[translateid="test-results.CompleteTechnicalVerification"]',
    COMPLETE_MED_VERIFY_SELECTOR: 'button.dropdown-item[translateid="test-results.CompleteMedicalVerification"]',
    FINAL_VERIFY_BUTTON_SELECTOR: 'button.btn-success.btn-sm.min-width[translateid="test-verification.Verify"]',
    NEXT_BUTTON_SELECTOR: 'button#btnNext',
    TEST_DESC_SELECTOR: 'div[col-id="TestDesc"]',
    UNCHECKED_BOX_SELECTOR: 'span.ag-icon-checkbox-unchecked[unselectable="on"]',
    CHECKBOX_PARENT_ROW_SELECTOR: '.ag-row',

    // Script 2: Alerts & Scanning
    SCAN_INTERVAL: 150,
    FLASH_COLOR: "pink",
    FLASH_INTERVAL: 500,
    // MODAL_TIMEOUT: 10000, // Removed as PERSISTENT_MODALS=true makes it irrelevant
    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',

    // Script 3 & General Toast Handling
    UNDEFINED_URL_CHECK_INTERVAL: 200,
    TOAST_CONTAINER_SELECTOR: '#toast-container',
    TOAST_CLOSE_BUTTON_SELECTOR: 'button.toast-close-button',
    SUCCESS_TOAST_SELECTOR: '.toast-success',
  };

  // --- State Variables ---
  let verify1Toggle = localStorage.getItem('verify1Toggle') === 'true';
  let verify2Toggle = localStorage.getItem('verify2Toggle') === 'true';
  let verify1Clicked = false;
  let verify2Clicked = false;
  let issueScanIntervalId = null;
  let isScanningActive = false;
  const activeModals = new Set();

  // --- Utility Functions ---
  function logDebug(message) {
    // Added check for console availability
    if (CONFIG.DEBUG_MODE && typeof console !== 'undefined' && console.log) {
        console.log(`[Lab Suite v5.6.4] ${message}`);
    }
  }

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

  function isVisible(element) {
    return !!(element && (element.offsetWidth || element.offsetHeight || element.getClientRects().length));
  }

  // --- Modal Functions ---
  function showModal(message) {
    // Remove duplicate modals with same message - prevents spam if scan is too fast
    document.querySelectorAll('.lab-suite-modal').forEach(modal => {
      const contentElement = modal.querySelector('p'); // Target the content paragraph
      if (contentElement && contentElement.textContent === message) {
        const overlay = document.querySelector('.lab-suite-modal-overlay[data-modal-ref="' + modal.id + '"]');
        logDebug(`Removing duplicate modal for message: ${message}`);
        if (overlay) overlay.remove();
        modal.remove();
        activeModals.delete(modal); // Ensure removal from active set
      }
    });

    logDebug(`Showing modal: ${message}`);
    const modalId = `lab-suite-modal-${Date.now()}-${Math.random().toString(16).slice(2)}`; // Unique ID

    const overlay = document.createElement("div");
    overlay.className = 'lab-suite-modal-overlay';
    overlay.style.position = "fixed";
    overlay.style.top = "0";
    overlay.style.left = "0";
    overlay.style.width = "100%";
    overlay.style.height = "100%";
    overlay.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
    overlay.style.zIndex = "2000";
    overlay.style.opacity = "0";
    overlay.style.transition = "opacity 0.3s ease";
    overlay.setAttribute('data-modal-ref', modalId); // Link overlay to modal
    document.body.appendChild(overlay);

    const modal = document.createElement("div");
    modal.id = modalId; // Assign unique ID
    modal.className = 'lab-suite-modal';
    modal.style.position = "fixed";
    modal.style.top = "50%";
    modal.style.left = "50%";
    modal.style.transform = "translate(-50%, -50%) scale(0.5)";
    modal.style.backgroundColor = "#f4f4f9";
    modal.style.padding = "30px";
    modal.style.boxShadow = "0px 10px 30px rgba(0, 0, 0, 0.15)";
    modal.style.zIndex = "2001";
    modal.style.borderRadius = "15px";
    modal.style.textAlign = "center";
    modal.style.transition = "transform 0.3s ease, opacity 0.3s ease";
    modal.style.opacity = "0";
    modal.style.maxWidth = "450px";
    modal.setAttribute('role', 'alertdialog'); // More specific role
    modal.setAttribute('aria-labelledby', `${modalId}-heading`);
    modal.setAttribute('aria-describedby', `${modalId}-content`);
    modal.setAttribute('aria-modal', 'true');

    const heading = document.createElement("h2");
    heading.id = `${modalId}-heading`; // ID for aria-labelledby
    heading.textContent = "Attention!";
    heading.style.fontFamily = "'Arial', sans-serif";
    heading.style.color = "#333";
    heading.style.marginBottom = "10px";
    heading.style.fontSize = "24px";
    modal.appendChild(heading);

    const content = document.createElement("p");
    content.id = `${modalId}-content`; // ID for aria-describedby
    // Standardize NO RESULT messages
    let displayMessage = message;
    if (message.includes("NO RESULT") || message.includes("X-NORESULT")) {
      displayMessage = CONFIG.NO_RESULT_MESSAGE;
    }
    content.textContent = displayMessage;
    content.style.fontFamily = "'Arial', sans-serif";
    content.style.color = "#555";
    content.style.marginBottom = "20px";
    content.style.fontSize = "16px";
    content.style.lineHeight = "1.5";
    modal.appendChild(content);

    const okButton = createModalButton("OK", "#ff4081", () => {
      logDebug(`Modal [${modalId}] manually dismissed.`);
      closeModal(modal, overlay);
    });
    modal.appendChild(okButton);

    document.body.appendChild(modal);
    okButton.focus(); // Focus the OK button for accessibility

    // Animate appearance
    setTimeout(() => {
      overlay.style.opacity = "1";
      modal.style.transform = "translate(-50%, -50%) scale(1)";
      modal.style.opacity = "1";
    }, 10);

    activeModals.add(modal);

    // Auto-close functionality (if PERSISTENT_MODALS is false) - currently disabled by config
    // if (!CONFIG.PERSISTENT_MODALS && CONFIG.MODAL_TIMEOUT > 0) {
    //   setTimeout(() => {
    //     logDebug(`Modal [${modalId}] automatically closing after timeout.`);
    //     closeModal(modal, overlay);
    //   }, CONFIG.MODAL_TIMEOUT);
    // }
  }

  function createModalButton(text, backgroundColor, onClick) {
    const button = document.createElement("button");
    button.textContent = text;
    button.style.padding = "10px 20px";
    button.style.border = "none";
    button.style.backgroundColor = backgroundColor;
    button.style.color = "white";
    button.style.borderRadius = "30px";
    button.style.cursor = "pointer";
    button.style.fontSize = "16px";
    button.style.transition = "background-color 0.3s ease, transform 0.2s ease";
    button.style.minWidth = '80px';
    button.style.margin = '0 5px';

    button.addEventListener("mouseenter", () => {
      button.style.backgroundColor = darkenColor(backgroundColor, 20);
      button.style.transform = "scale(1.05)";
    });
    button.addEventListener("mouseleave", () => {
      button.style.backgroundColor = backgroundColor;
      button.style.transform = "scale(1)";
    });
    button.addEventListener("click", onClick);
    return button;
  }

  function closeModal(modal, overlay) {
    if (!modal || !overlay || !document.body.contains(modal)) {
        logDebug("Attempted to close a modal that doesn't exist or is already removed.");
        return;
    }
    logDebug(`Closing modal [${modal.id}].`);
    modal.style.transform = "translate(-50%, -50%) scale(0.5)";
    modal.style.opacity = "0";
    overlay.style.opacity = "0";

    // Remove after transition
    setTimeout(() => {
      if (document.body.contains(modal)) {
          document.body.removeChild(modal);
          activeModals.delete(modal); // Ensure removal from active set
          logDebug(`Modal [${modal.id}] removed from DOM.`);
      } else {
          logDebug(`Modal [${modal.id}] was already removed from DOM before timeout.`);
      }
      if (document.body.contains(overlay)) {
           document.body.removeChild(overlay);
           logDebug(`Overlay for modal [${modal.id}] removed from DOM.`);
      } else {
          logDebug(`Overlay for modal [${modal.id}] was already removed from DOM before timeout.`);
      }
    }, 300); // Corresponds to transition duration
  }

  function darkenColor(color, percent) {
    try {
      // Ensure color starts with #
      if (!color.startsWith('#')) {
          logDebug(`Invalid color format for darkenColor: ${color}`);
          return color; // Return original if format is wrong
      }
      let num = parseInt(color.slice(1), 16);
      let amt = Math.round(2.55 * percent);
      let R = (num >> 16) - amt;
      let G = ((num >> 8) & 0x00ff) - amt;
      let B = (num & 0x0000ff) - amt;
      R = R < 0 ? 0 : R;
      G = G < 0 ? 0 : G;
      B = B < 0 ? 0 : B;
      return `#${(0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1)}`;
    } catch (e) {
      logDebug(`Error darkening color ${color}: ${e}`);
      return color; // Return original on error
    }
  }

  // --- Script 1 Functions ---
  function isCorrectPage() {
    return window.location.href.startsWith(CONFIG.TARGET_EDIT_PAGE_URL_PREFIX);
  }

  function createVerifyButton(label, className, onClick) {
    let button = document.createElement('button');
    button.type = 'button';
    button.innerHTML = label;
    button.className = className;

    let buttonColors = {
      'btn btn-success btn-sm': '#28a745', // Green for VERIFY1
      'btn btn-primary btn-sm': '#2594d9'  // Blue for VERIFY2
    };

    // Basic styles - !important might be needed depending on site CSS specificity
    button.style.cssText = `
        font-family: 'Arial', sans-serif !important;
        font-size: 14px !important;
        font-weight: normal !important;
        color: #ffffff !important;
        background-color: ${buttonColors[className] || '#6c757d'} !important; /* Default grey */
        padding: 8px 16px !important;
        border: none !important;
        border-radius: 5px !important;
        text-shadow: none !important;
        cursor: pointer !important;
        margin-right: 5px !important;
        line-height: 1.5 !important;
        vertical-align: middle; /* Align with icons */
      `;
    button.onclick = onClick;
    return button;
  }

 function createToggleIcon(id, isActive, onClick) {
    let icon = document.createElement('span');
    icon.id = id;
    // Using Font Awesome classes directly in innerHTML
    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'; // Space after icon
    icon.style.marginLeft = '-1px';  // Adjust spacing relative to button
    icon.onclick = onClick;
    icon.title = "Toggle: Go back automatically after this verification?"; // More descriptive title
    return icon;
 }

  function handleVerify1IconToggle() {
    verify1Toggle = !verify1Toggle;
    localStorage.setItem('verify1Toggle', verify1Toggle);
    const iconElement = document.querySelector('#verify1Icon i'); // Target the <i> tag
    if (iconElement) iconElement.style.color = verify1Toggle ? '#008000' : '#d1cfcf'; // Green/Grey
    logDebug(`Verify1 Auto-Back Toggle set to: ${verify1Toggle}`);
  }

  function handleVerify2IconToggle() {
    verify2Toggle = !verify2Toggle;
    localStorage.setItem('verify2Toggle', verify2Toggle);
    const iconElement = document.querySelector('#verify2Icon i'); // Target the <i> tag
    if (iconElement) iconElement.style.color = verify2Toggle ? '#008000' : '#d1cfcf'; // Green/Grey
    logDebug(`Verify2 Auto-Back Toggle set to: ${verify2Toggle}`);
  }

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

    const nextButton = document.querySelector(CONFIG.NEXT_BUTTON_SELECTOR);
    if (nextButton && nextButton.parentNode) {
      logDebug("Adding custom VERIFY buttons.");
      let buttonDiv = document.createElement('div');
      buttonDiv.id = 'custom-script-buttons';
      buttonDiv.style.display = 'inline-block'; // Keep elements inline
      buttonDiv.style.marginLeft = '10px'; // Space from Next button
      buttonDiv.style.verticalAlign = 'middle'; // Align container vertically

      // VERIFY1 Button & Toggle
      let verify1Button = createVerifyButton('VERIFY1 (F7)', 'btn btn-success btn-sm', () => {
        logDebug("VERIFY1 button clicked.");
        verify1Clicked = true;
        verify2Clicked = false;
        checkAllVisibleBoxesWithoutDuplicates();
        // Add slight delay before clicking dropdown item
        setTimeout(() => { clickCompleteTechnicalVerificationButton(); }, 500);
      });
      let verify1Icon = createToggleIcon('verify1Icon', verify1Toggle, handleVerify1IconToggle);

      // VERIFY2 Button & Toggle
      let verify2Button = createVerifyButton('VERIFY2 (F8)', 'btn btn-primary btn-sm', () => {
        logDebug("VERIFY2 button clicked.");
        verify2Clicked = true;
        verify1Clicked = false;
        checkAllVisibleBoxesWithoutDuplicates();
        // Add slight delay before clicking dropdown item
        setTimeout(() => { clickCompleteMedicalVerificationButton(); }, 500);
      });
      let verify2Icon = createToggleIcon('verify2Icon', verify2Toggle, handleVerify2IconToggle);

      // Author Credit (Styled)
      let modedByText = document.createElement('span');
      modedByText.textContent = "Modded by: Hamad AlShegifi";
      modedByText.style.fontSize = '11px'; // Slightly smaller
      modedByText.style.fontWeight = 'bold';
      modedByText.style.color = '#e60000'; // Darker red
      modedByText.style.marginLeft = '15px'; // Space before credit
      modedByText.style.border = '1px solid #e60000';
      modedByText.style.borderRadius = '5px'; // Matched button radius
      modedByText.style.padding = '3px 6px';
      modedByText.style.backgroundColor = '#fff0f0'; // Very light pink background
      modedByText.style.verticalAlign = 'middle'; // Align with buttons/icons

      // Append elements in order
      buttonDiv.appendChild(verify1Button);
      buttonDiv.appendChild(verify1Icon);
      buttonDiv.appendChild(verify2Button);
      buttonDiv.appendChild(verify2Icon);
      buttonDiv.appendChild(modedByText); // Add credit last

      // Insert the div after the Next button
      nextButton.parentNode.insertBefore(buttonDiv, nextButton.nextSibling);
    } else {
      logDebug("Could not find Next button ('" + CONFIG.NEXT_BUTTON_SELECTOR + "') to anchor custom buttons.");
      // Fallback: Append to body or a known container if Next button is missing?
      // For now, it just logs the failure.
    }
  }

  function checkAllVisibleBoxesWithoutDuplicates() {
    logDebug("Checking checkboxes...");
    const testDivs = document.querySelectorAll(CONFIG.TEST_DESC_SELECTOR);
    let seenTests = new Set();
    let boxesChecked = 0;
    // Ensure exclude words are lowercase for comparison
    const excludeWordsLower = CONFIG.EXCLUDE_WORDS.map(word => word.toLowerCase());

    testDivs.forEach(testDiv => {
      const testName = testDiv.textContent?.trim().toLowerCase() || '';
      if (!testName) return; // Skip empty descriptions

      // Check if test name contains any excluded word
      if (excludeWordsLower.some(word => testName.includes(word))) {
        logDebug(`Excluding checkbox for test containing excluded word: ${testName}`);
        return; // Skip this test
      }

      // Check for duplicates (only check the first instance of a test name)
      if (seenTests.has(testName)) {
        logDebug(`Skipping duplicate test: ${testName}`);
        return; // Skip this duplicate test
      }

      seenTests.add(testName); // Add unique test name to set

      // Find the parent row and the unchecked checkbox within it
      const parentRow = testDiv.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR);
      if (parentRow) {
        const checkbox = parentRow.querySelector(CONFIG.UNCHECKED_BOX_SELECTOR);
        // Ensure checkbox exists and is visible before clicking
        if (checkbox && isVisible(checkbox)) {
          logDebug(`Clicking checkbox for unique, non-excluded test: ${testName}`);
          // Simulate a user click more reliably
          const event = new MouseEvent('click', { bubbles: true, cancelable: true, view: window });
          checkbox.dispatchEvent(event);
          boxesChecked++;
        } else if (checkbox && !isVisible(checkbox)) {
            logDebug(`Checkbox found but not visible for test: ${testName}`);
        }
      } else {
          logDebug(`Could not find parent row for test: ${testName}`);
      }
    });
    logDebug(`${boxesChecked} unique, non-excluded, visible checkboxes were checked.`);
  }

  function clickCompleteTechnicalVerificationButton() {
    const button = document.querySelector(CONFIG.COMPLETE_TECH_VERIFY_SELECTOR);
    if (button) {
      button.click();
      logDebug("Complete Technical Verification button clicked!");
      // Wait slightly longer for the final verify button to appear/enable
      setTimeout(() => { clickFinalVerifyButton(); }, 700);
    } else {
      logDebug("Complete Technical Verification button not found!");
      showModal("Error: Unable to find the 'Complete Technical Verification' button.");
    }
  }

  function clickCompleteMedicalVerificationButton() {
    const button = document.querySelector(CONFIG.COMPLETE_MED_VERIFY_SELECTOR);
    if (button) {
      button.click();
      logDebug("Complete Medical Verification button clicked!");
      // Wait slightly longer for the final verify button to appear/enable
      setTimeout(() => { clickFinalVerifyButton(); }, 700);
    } else {
      logDebug("Complete Medical Verification button not found!");
      showModal("Error: Unable to find the 'Complete Medical Verification' button.");
    }
  }

  function clickFinalVerifyButton() {
    const verifyButton = document.querySelector(CONFIG.FINAL_VERIFY_BUTTON_SELECTOR);
    if (verifyButton && !verifyButton.disabled) { // Check if button exists and is not disabled
      verifyButton.click();
      logDebug("Final Verify button clicked!");
      // Reset click flags AFTER successful final verify click
      // verify1Clicked = false; // Moved reset logic to toast observer/click
      // verify2Clicked = false;
    } else if (verifyButton && verifyButton.disabled) {
        logDebug("Final Verify button found, but it is disabled.");
        showModal("Error: The final 'Verify' button is disabled. Cannot proceed.");
    } else {
      logDebug("Final Verify button not found!");
      showModal("Error: Unable to find the final 'Verify' button.");
    }
  }

 // --- Script 2 Functions ---
 function applyFlashingEffect(rows) {
   rows.forEach(row => {
     // Prevent multiple intervals on the same row
     if (row.dataset.flashing === 'true') {
        // logDebug("Flashing already active on this row.");
        return;
     }
     row.dataset.flashing = 'true'; // Mark as flashing
     logDebug(`Applying flashing effect to row ID: ${row.getAttribute('row-id') || 'N/A'}`);

     // Store original background for restoring later if needed, default to transparent
     const originalBg = row.style.backgroundColor || 'transparent';
     row.dataset.originalBg = originalBg; // Store it

     row.style.transition = "background-color 0.5s ease";
     let isPink = false;

     const intervalId = setInterval(() => {
       // Check if row still exists in DOM
       if (!document.body.contains(row)) {
         clearInterval(intervalId);
         // No need to reset background if element is gone
         logDebug(`Row ${row.getAttribute('row-id') || 'N/A'} removed, stopping its flash interval.`);
         return;
       }
       // Check if flashing should stop (e.g., modal dismissed or issue resolved - needs external trigger)
       if (row.dataset.flashing === 'false') {
           clearInterval(intervalId);
           row.style.backgroundColor = row.dataset.originalBg || 'transparent'; // Restore original
           logDebug(`Flashing stopped externally for row ID: ${row.getAttribute('row-id') || 'N/A'}. Restored background.`);
           delete row.dataset.flashing; // Clean up attribute
           delete row.dataset.originalBg;
           delete row.dataset.flashIntervalId;
           return;
       }

       isPink = !isPink;
       row.style.backgroundColor = isPink ? CONFIG.FLASH_COLOR : originalBg;
     }, CONFIG.FLASH_INTERVAL);

     // Store interval ID on the row itself for potential external clearing
     row.dataset.flashIntervalId = intervalId;
   });
 }

 function stopFlashingEffect(row) {
     if (row && row.dataset.flashing === 'true') {
         logDebug(`Requesting to stop flashing for row ID: ${row.getAttribute('row-id') || 'N/A'}`);
         row.dataset.flashing = 'false'; // Signal interval to stop and restore color
         // The interval itself handles cleanup
     }
 }


 function getNotificationSessionKey(type, identifier = 'general') {
    // Include pathname to make keys specific to the current view/order
    return `labSuiteNotified_${window.location.pathname}_${type}_${identifier}`;
 }

 function hasAlreadyNotified(type, identifier = 'general') {
    const key = getNotificationSessionKey(type, identifier);
    const notified = sessionStorage.getItem(key) === 'true';
    // if (notified) logDebug(`Notification flag FOUND for key: ${key}`);
    return notified;
 }

 function setNotificationFlag(type, identifier = 'general') {
    const key = getNotificationSessionKey(type, identifier);
    logDebug(`Setting notification flag for key: ${key}`);
    sessionStorage.setItem(key, 'true');
 }

// ==================================================
// == MODIFIED checkForIssues Function Starts Here ==
// ==================================================
 function checkForIssues() {
   const resultDivs = document.querySelectorAll(CONFIG.RESULT_CELL_SELECTOR);
   const criticalDivs = document.querySelectorAll(CONFIG.CRITICAL_FLAG_SELECTOR);
   const potentialAlerts = []; // Store potential alerts found in this scan

   // Helper function to scroll with 'nearest' block alignment
   function scrollToRowNearest(element) {
     const row = element.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR);
     if (row) {
       const rowId = row.getAttribute('row-id');
       logDebug(`Scrolling to nearest position for row ID: ${rowId || 'ID not found'}`);
       // Use 'nearest' to minimize scrolling distance and avoid centering
       try {
           row.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
       } catch (e) {
            logDebug(`Error during scrollIntoView: ${e}. Falling back to basic scroll.`);
            row.scrollIntoView(); // Fallback
       }
       return true;
     }
     logDebug('Could not find parent row to scroll to.');
     return false;
   }

   // 1. Check for Critical Flags (CL/CH)
   criticalDivs.forEach(div => {
     const text = div.textContent?.trim() || '';
     if (text === "CL" || text === "CH") {
       const rowElement = div.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR);
       if (rowElement) {
          const specificRowKey = rowElement.getAttribute('row-id') || text + "_" + Array.from(criticalDivs).indexOf(div); // Use row-id or text+index as fallback
           if (!hasAlreadyNotified('critical', specificRowKey)) {
               const message = text === "CL" ? "CRITICAL LOW RESULT DETECTED !!" : "CRITICAL HIGH RESULT DETECTED !!";
               logDebug(`Found potential critical alert (${text}) for row ${specificRowKey}`);
               potentialAlerts.push({
                   element: div, // Store the element that triggered the alert
                   rowElement: rowElement, // Store the row itself for easier scrolling later
                   type: 'critical',
                   message: message,
                   rowId: specificRowKey
               });
           } else {
                // logDebug(`Critical alert (${text}) for row ${specificRowKey} already notified this session.`);
           }
       }
     }
   });

   // 2. Check for "NO RESULT" / "X-NORESULT"
   resultDivs.forEach(div => {
     const text = div.textContent?.trim().toLowerCase() || '';
     const isNoResultType = (text === "no result" || text === "no-xresult" || text === "x-noresult");

     if (isNoResultType) {
        const rowElement = div.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR);
        if (rowElement) {
            const specificRowKey = rowElement.getAttribute('row-id') || text.replace(/[\s-]+/g, '_') + "_" + Array.from(resultDivs).indexOf(div); // Use row-id or text+index
            if (!hasAlreadyNotified('noresult', specificRowKey)) {
                logDebug(`Found potential noresult alert (${text}) for row ${specificRowKey}`);
                potentialAlerts.push({
                    element: div,
                    rowElement: rowElement,
                    type: 'noresult',
                    message: CONFIG.NO_RESULT_MESSAGE, // Use standardized message
                    rowId: specificRowKey
                });
            } else {
                // logDebug(`Noresult alert (${text}) for row ${specificRowKey} already notified this session.`);
            }
        }
     }
   });

   // 3. Check for ">" (Dilution Required)
   resultDivs.forEach(div => {
     const text = div.textContent?.trim() || ''; // Don't lowercase here, '>' is specific
     if (text.includes(">")) {
       const rowElement = div.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR);
       if (rowElement) {
           const rowId = rowElement.getAttribute('row-id') || ">_" + Array.from(resultDivs).indexOf(div); // Use row-id or symbol+index
           if (!hasAlreadyNotified('greaterThan', rowId)) {
                logDebug(`Found potential greaterThan alert (>) for row ${rowId}`);
                // Apply flashing immediately for any '>' found, regardless of whether it's the final alert
                applyFlashingEffect([rowElement]); // Pass row element in array
                potentialAlerts.push({
                   element: div,
                   rowElement: rowElement,
                   type: 'greaterThan',
                   message: "Dilution is required for this sample (> detected)!",
                   rowId: rowId
               });
           } else {
                // Apply flashing even if notified, as the condition persists
                applyFlashingEffect([rowElement]);
                // logDebug(`GreaterThan alert (>) for row ${rowId} already notified this session, but applying flash.`);
           }
       }
     }
   });

   // --- Process the collected alerts ---
   if (potentialAlerts.length > 0) {
     // Get the LAST alert found during the scan (assumes DOM order is stable)
     const lastAlert = potentialAlerts[potentialAlerts.length - 1];
     logDebug(`Potential alerts found: ${potentialAlerts.length}. Selecting the last one: Type=${lastAlert.type}, RowID=${lastAlert.rowId}`);

     // Check if *this specific last alert* has already been notified and dismissed in this session
     if (!hasAlreadyNotified(lastAlert.type, lastAlert.rowId)) {
         // Show the modal for the last alert found
         showModal(lastAlert.message);

         // Set the notification flag for this specific alert row/type
         // This prevents this exact alert from showing again until the session ends or changes path
         setNotificationFlag(lastAlert.type, lastAlert.rowId);

         // Scroll to the row of the last alert using 'nearest'
         scrollToRowNearest(lastAlert.element);

         return true; // Indicate that an alert was processed in this cycle
     } else {
         logDebug(`Last potential alert (Type=${lastAlert.type}, RowID=${lastAlert.rowId}) was already notified in this session. Modal suppressed.`);
         // Ensure flashing is still applied if it's a '>' type, even if notified
         if (lastAlert.type === 'greaterThan' && lastAlert.rowElement) {
             applyFlashingEffect([lastAlert.rowElement]);
         }
     }
   } else {
       // logDebug("No new alert conditions found in this scan cycle.");
   }

   return false; // No new, un-notified alert was processed in this cycle
 }
// ================================================
// == MODIFIED checkForIssues Function Ends Here ==
// ================================================


 function startContinuousScanning() {
   if (isScanningActive) {
       // logDebug("Scanning already active.");
       return;
   }
   logDebug("Starting continuous issue scanning...");
   isScanningActive = true;

   // Clear any previous interval just in case
   if (issueScanIntervalId) clearInterval(issueScanIntervalId);

   issueScanIntervalId = setInterval(() => {
     // Check if the grid elements we monitor still exist
     const resultsGridExists = document.querySelector(CONFIG.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG.CRITICAL_FLAG_SELECTOR);
     if (!resultsGridExists && isScanningActive) { // Add isScanningActive check to prevent race condition log
       logDebug("Monitored result/critical elements disappeared, stopping issue scan.");
       stopContinuousScanning(); // Stop if grid is gone
       return;
     }
     // Run the check
     checkForIssues();
   }, CONFIG.SCAN_INTERVAL);
 }

 function stopContinuousScanning() {
   if (issueScanIntervalId) {
     clearInterval(issueScanIntervalId);
     issueScanIntervalId = null;
     logDebug("Stopped continuous issue scanning.");
   }
   // Reset flashing state for all rows when scanning stops (e.g., page navigation)
   document.querySelectorAll(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR + '[data-flashing="true"]').forEach(row => {
       stopFlashingEffect(row);
   });
   isScanningActive = false;
 }

 // --- Script 3 Function ---
 function checkUrlAndTriggerClickForUndefined() {
   // Avoid running if modals are open, as it might dismiss them unexpectedly
    if (activeModals.size > 0) {
        return;
    }

   const currentUrl = window.location.href;
   // Check specifically for ending with /undefined
   if (currentUrl.endsWith('/undefined')) {
     logDebug('URL ends with /undefined. Checking for toast...');
     const toastContainer = document.querySelector(CONFIG.TOAST_CONTAINER_SELECTOR);
     if (toastContainer) {
       const closeButton = toastContainer.querySelector(CONFIG.TOAST_CLOSE_BUTTON_SELECTOR);
       // Ensure the button is visible before clicking
       if (closeButton && isVisible(closeButton)) {
         logDebug('Found visible toast close button on /undefined page. Clicking...');
         closeButton.click();
         // Optional: Navigate back or to a default page?
         // window.history.back(); // Or window.location.href = '/lab/some/default/page';
       } else {
           logDebug('Toast container found, but close button not found or not visible.');
       }
     } else {
         logDebug('Toast container not found on /undefined page.');
     }
   }
 }

 // --- Event Listeners & Observers ---

 // Keyboard Shortcuts (F7/F8)
 document.addEventListener('keydown', function (event) {
    // Ignore keypresses if a modal is open or if typing in an input/textarea
    if (activeModals.size > 0 || ['INPUT', 'TEXTAREA', 'SELECT'].includes(event.target.tagName)) {
        return;
    }

   if (event.key === 'F7') {
     event.preventDefault(); // Prevent default F7 browser behavior
     logDebug("F7 pressed: Triggering VERIFY1 button click");
     const verify1Button = document.querySelector(CONFIG.VERIFY1_BUTTON_SELECTOR);
     if (verify1Button) verify1Button.click();
     else logDebug("VERIFY1 button not found for F7 shortcut.");
   } else if (event.key === 'F8') {
     event.preventDefault(); // Prevent default F8 browser behavior
     logDebug("F8 pressed: Triggering VERIFY2 button click");
     const verify2Button = document.querySelector(CONFIG.VERIFY2_BUTTON_SELECTOR);
     if (verify2Button) verify2Button.click();
     else logDebug("VERIFY2 button not found for F8 shortcut.");
   }
 });

 // Observer for Success Toasts (for Auto-Back Navigation)
 const toastObserver = new MutationObserver((mutations) => {
   mutations.forEach(mutation => {
     mutation.addedNodes.forEach(node => {
       // Check if the added node is an element and matches the success toast selector
       if (node.nodeType === Node.ELEMENT_NODE && node.matches && node.matches(CONFIG.SUCCESS_TOAST_SELECTOR)) {
         logDebug('Success toast detected. Adding click listener for potential back navigation.');

         // Use a named function for potential removal later if needed
         const handleToastClick = () => {
           logDebug('Success toast clicked.');
           if (verify1Clicked && verify1Toggle) {
             logDebug('Verify1 was active and toggle is ON. Navigating back.');
             window.history.back();
           } else if (verify2Clicked && verify2Toggle) {
             logDebug('Verify2 was active and toggle is ON. Navigating back.');
             window.history.back();
           } else {
               logDebug('Toast clicked, but no active verify toggle matched or toggle was OFF.');
           }
           // Reset flags regardless of navigation
           verify1Clicked = false;
           verify2Clicked = false;
           // Remove listener after click to prevent multiple back navigations if toast persists
           node.removeEventListener('click', handleToastClick);
         };

         node.addEventListener('click', handleToastClick);
       }
     });
   });
 });

 // Main Observer for Page Changes (Adding/Removing Buttons, Starting/Stopping Scan)
 const mainObserver = new MutationObserver((mutations) => {
    // Optimization: Check if relevant nodes were added/removed, e.g., the grid or buttons container
    let potentiallyRelevantChange = false;
    for (const mutation of mutations) {
        if (mutation.type === 'childList') {
            // Basic check: If any nodes were added/removed, re-evaluate state
            if (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0) {
                 potentiallyRelevantChange = true;
                 break;
            }
            // More specific checks could be added here if performance is an issue
        }
    }

    if (!potentiallyRelevantChange) return; // Skip if no relevant DOM changes detected

   // --- Handle Custom Buttons ---
   if (isCorrectPage()) {
       // If on the correct page, ensure buttons are present
       if (!document.getElementById('custom-script-buttons')) {
            addButtons(); // Add buttons if they are missing
       }
   } else {
       // If not on the correct page, ensure buttons are removed
       const buttonDiv = document.getElementById('custom-script-buttons');
       if (buttonDiv) {
           logDebug("Navigated away from edit page, removing custom buttons.");
           buttonDiv.remove();
       }
   }

   // --- Handle Continuous Scanning ---
   // Check if the elements we scan for exist
   const resultsGridExists = document.querySelector(CONFIG.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG.CRITICAL_FLAG_SELECTOR);

   if (resultsGridExists) {
       // If grid exists, ensure scanning is active
       if (!isScanningActive) {
           startContinuousScanning();
       }
   } else {
       // If grid does not exist, ensure scanning is stopped
       if (isScanningActive) {
           stopContinuousScanning();
       }
   }
 });

 // --- Initialization ---
 try {
   logDebug("KAAUH Lab Enhancement Suite Initializing (v5.6.4)...");
   loadFontAwesome(); // Load icons

   // Start checking for the '/undefined' URL issue periodically
   setInterval(checkUrlAndTriggerClickForUndefined, CONFIG.UNDEFINED_URL_CHECK_INTERVAL);
   logDebug(`Started URL check interval (${CONFIG.UNDEFINED_URL_CHECK_INTERVAL}ms) for /undefined toasts.`);

   // Start observing the main body for changes relevant to buttons and scanning
   mainObserver.observe(document.body, { childList: true, subtree: true });
   logDebug("Started main MutationObserver.");

   // Start observing for success toasts
   // Observe the body, assuming toasts are appended there or within a container in the body
   const toastTargetNode = document.body; // Or a more specific container if known e.g., document.getElementById('toast-container-parent')
   toastObserver.observe(toastTargetNode, { childList: true, subtree: true }); // subtree: true if toasts can appear deep
   logDebug("Started toast MutationObserver for back-navigation.");

   // Initial setup checks on window load (covers cases where script loads after initial DOM ready)
   window.addEventListener('load', () => {
     logDebug("Page fully loaded (window.load event). Performing initial checks.");
     // Ensure buttons are added if on the correct page initially
     if (isCorrectPage()) {
         if (!document.getElementById('custom-script-buttons')) {
            addButtons();
         }
     }
     // Start scanning if results grid exists on load
     const resultsGridExists = document.querySelector(CONFIG.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG.CRITICAL_FLAG_SELECTOR);
     if (resultsGridExists && !isScanningActive) {
       startContinuousScanning();
     }
   });

    // Also run initial checks once DOM is ready (might be slightly earlier than window.load)
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            logDebug("DOM fully loaded and parsed (DOMContentLoaded event). Performing initial checks.");
             if (isCorrectPage()) {
                 if (!document.getElementById('custom-script-buttons')) {
                    addButtons();
                 }
             }
             const resultsGridExists = document.querySelector(CONFIG.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG.CRITICAL_FLAG_SELECTOR);
             if (resultsGridExists && !isScanningActive) {
               startContinuousScanning();
             }
        });
    } else {
        // DOM already ready, run checks immediately
        logDebug("DOM already ready. Performing initial checks.");
        if (isCorrectPage()) {
             if (!document.getElementById('custom-script-buttons')) {
                addButtons();
             }
         }
         const resultsGridExists = document.querySelector(CONFIG.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG.CRITICAL_FLAG_SELECTOR);
         if (resultsGridExists && !isScanningActive) {
           startContinuousScanning();
         }
    }


   logDebug("Initialization complete.");

 } catch (error) {
   console.error("[Lab Suite] Critical error during initialization:", error);
   // Attempt to show a modal even if other parts failed
   try {
        showModal("A critical error occurred in the Lab Enhancement Suite. Please check the browser console (F12) for details.");
   } catch (modalError) {
       console.error("[Lab Suite] Could not even display the error modal:", modalError);
       alert("A critical error occurred in the Lab Enhancement Suite script. Check the console (F12)."); // Fallback alert
   }
 }

})(); // End of Userscript IIFE

QingJ © 2025

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