您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Combines verification buttons (F7/F8), dynamic alerts, critical result reporting, checkbox automation, toggle back-nav, and inline sample counters.
当前为
// ==UserScript== // @name KAAUH Lab Suite - Verification, Alerts & Enhancements // @namespace Violentmonkey Scripts // @version 7.15.0 // @description Combines verification buttons (F7/F8), dynamic alerts, critical result reporting, checkbox automation, toggle back-nav, and inline sample counters. // @match *://his.kaauh.org/lab/* // @grant none // @author Hamad AlShegifi (modified by Gemini) // @license MIT // ==/UserScript== (function() { 'use strict'; // --- Styling Adjustments (Global) --- 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, ''); } }; simplifySpan('span.to-do'); simplifySpan('span.pending-orders'); } function debounceStyle(func, wait) { // Renamed to avoid conflict if user copies debounce elsewhere let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } const debouncedStyleUpdater = debounceStyle(applySingleLineStyles, 300); const styleObserver = new MutationObserver(debouncedStyleUpdater); styleObserver.observe(document.body, { childList: true, subtree: true }); applySingleLineStyles(); // Initial application })(); // Main Userscript Logic (Alerts, F7/F8 buttons, etc.) (function () { 'use strict'; // Debounce function (copied here for local scope) function debounce(func, wait) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } 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', REPORT_CRITICAL_BTN: '#custom-script-buttons button.report-critical-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"]', ORDERED_STATUS_CELL: 'div[col-id="ResultStatus"]', TOAST: { CONTAINER: '#toast-container', CLOSE_BTN: 'button.toast-close-button', SUCCESS: '.toast-success', }, SAMPLE_RECEIVE_MODAL: 'modal-container.show', ALL_TEST_ROWS_CENTER: '.ag-center-cols-container .ag-row', ALL_TEST_ROWS_PINNED: '.ag-pinned-left-cols-container .ag-row', TEST_RESULT_CELL_GENERAL: 'div[role="gridcell"][col-id="TestResult"] app-result-value-render div', TEST_UOM_CELL_GENERAL: 'div[col-id="UomValue"]', TEST_FLAG_CELL_GENERAL: 'div[role="gridcell"][col-id="LTFlag"] app-ref-high-low div span.critial-alret-indication', }, CHECK_INTERVALS: { UNDEFINED_URL: 200, ORDERED_SCAN: 500, // This is defined but monitorOrderedStatus is not called by setInterval DISABLED_BTN_CHECK: 1000, }, EXCLUDE_WORDS: [ 'culture', "gram's stain", 'stain', 'bacterial', 'fungal', 'pcr', 'meningitis', 'mrsa', 'mid', 'stream', 'cryptococcus' ] }; let verify1Clicked = false; let verify2Clicked = false; let verify1Toggle = localStorage.getItem('verify1Toggle') === 'true'; let verify2Toggle = localStorage.getItem('verify2Toggle') === 'true'; let hasScrolledToOrderedRow = false; let lastDisabledButtonAlertTime = 0; const DISABLED_ALERT_COOLDOWN = 30000; let isReportModalOpen = false; // For the "Report Critical Result" modal (manual one) let isAlertModalOpen = false; // For the automatic alert modal (createCustomAlert) // Centralized logging functions const logDebugMain = msg => console.debug(`[LabScript Main] ${msg}`); const logAlertDebug = msg => console.debug(`[Alerts Scanner DEBUG] ${msg}`); const logAlertError = msg => console.error(`[Alerts Scanner ERROR] ${msg}`); const isCorrectPage = () => window.location.href.startsWith(CONFIG_MAIN.URLS.EDIT_PAGE_PREFIX); function showDisabledButtonAlert(message) { const now = Date.now(); if (now - lastDisabledButtonAlertTime < DISABLED_ALERT_COOLDOWN) return; lastDisabledButtonAlertTime = now; 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' }); const title = document.createElement('h3'); title.textContent = 'Button Disabled'; title.style.color = '#d9534f'; 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(); } function addFontAwesome() { const fontAwesomeLink = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css'; if (!document.querySelector(`link[href="${fontAwesomeLink}"]`)) { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = fontAwesomeLink; const head = document.head || document.body; if (head) head.appendChild(link); } } function createVerifyButton(label, className, onClick, id) { const button = document.createElement('button'); button.type = 'button'; button.className = className; button.classList.add(id); button.innerText = label; const styles = { 'font-family': 'Arial, sans-serif', 'font-size': '14px', 'font-weight': 'normal', 'color': '#ffffff', 'background-color': className.includes('success') ? '#28a745' : '#2594d9', '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; } async function initiateManualCriticalReport() { logDebugMain("Manual critical report initiated. [Phase 1]"); if (isReportModalOpen || document.getElementById('report-selection-modal-overlay')) { logDebugMain("Report selection modal already open. Aborting."); return; } logDebugMain("Collecting testsData... [Phase 2]"); const testsData = collectAllTestsWithResults(); // UOM is now collected as is from the grid if (testsData.length === 0) { showDisabledButtonAlert("No tests found on the page to report."); logDebugMain("No tests found. Aborting modal. [Phase 2.1]"); return; } logDebugMain(`testsData collected: ${testsData.length} items. [Phase 2.2]`); logDebugMain("Introducing a small delay before fetching patient data for manual report... [Phase 2.5]"); await new Promise(resolve => setTimeout(resolve, 300)); try { logDebugMain("Attempting to get patient data... [Phase 3]"); const patientInfoResult = await getPatientDataWithRetry({ name: CONFIG_ALERTS.PATIENT_NAME_SELECTOR, mrn: CONFIG_ALERTS.PATIENT_MRN_SELECTOR, location: CONFIG_ALERTS.PATIENT_LOCATION_SELECTOR, barcode: CONFIG_ALERTS.SAMPLE_BARCODE_SELECTOR }); logDebugMain(`Patient data received. Essential: ${patientInfoResult.allEssentialDataRetrieved}. Data: ${JSON.stringify(patientInfoResult.data)} [Phase 4]`); if (!patientInfoResult.allEssentialDataRetrieved) { logAlertError("Essential patient data could not be retrieved for manual report."); } const userIdElement = document.querySelector(CONFIG_ALERTS.USER_NAME_SELECTOR); const currentUserId = userIdElement ? userIdElement.textContent.trim() : 'N/A'; logDebugMain(`User ID: ${currentUserId} [Phase 5]`); logDebugMain("Calling createReportSelectionModal... [Phase 6]"); createReportSelectionModal(testsData, patientInfoResult.data, currentUserId); logDebugMain("createReportSelectionModal call finished. [Phase 7]"); } catch (error) { logAlertError(`Error in initiateManualCriticalReport: ${error.message}`); console.error("Full error object in initiateManualCriticalReport:", error); alert("An error occurred while preparing the report. Please check the console for details."); } } function createReportCriticalResultButton() { const button = document.createElement('button'); button.type = 'button'; button.className = 'btn btn-danger btn-sm report-critical-btn'; // btn-danger for red button.innerText = 'Report Critical Result'; const styles = { 'font-family': 'Arial, sans-serif', 'font-size': '14px', 'font-weight': 'normal', 'color': '#ffffff', 'background-color': '#dc3545', // Red color 'padding': '8px 16px', 'border': 'none', 'border-radius': '5px', 'text-shadow': 'none', 'cursor': 'pointer', 'margin-right': '10px', 'line-height': '1.5', 'vertical-align': 'middle', }; for (const [prop, value] of Object.entries(styles)) { button.style.setProperty(prop, value, 'important'); } button.onclick = initiateManualCriticalReport; return button; } function createToggleIcon(id, isActive, onClick) { const icon = document.createElement('span'); icon.id = id; icon.innerHTML = `<i class="fas fa-arrow-circle-left" 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'; icon.title = "Toggle: Go back automatically after this verification success toast is clicked"; icon.onclick = onClick; return icon; } function handleVerifyToggle(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; } function addButtons() { if (document.getElementById('custom-script-buttons') || !isCorrectPage()) return; const nextButton = document.querySelector(CONFIG_MAIN.SELECTORS.NEXT_BTN); if (!nextButton || !nextButton.parentNode) { setTimeout(addButtons, 500); return; } 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', () => { verify1Clicked = true; verify2Clicked = false; checkAllVisibleBoxesWithoutDuplicates(); setTimeout(clickCompleteTechnicalVerificationButton, 500); }, 'verify1-btn'); const verify2Button = createVerifyButton('VERIFY2 (F8)', 'btn btn-primary btn-sm', () => { verify2Clicked = true; verify1Clicked = false; checkAllVisibleBoxesWithoutDuplicates(); setTimeout(clickCompleteMedicalVerificationButton, 500); }, 'verify2-btn'); const reportCriticalButton = createReportCriticalResultButton(); 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, reportCriticalButton, credit); nextButton.parentNode.insertBefore(buttonDiv, nextButton.nextSibling); logDebugMain("Custom buttons added."); } function checkAllVisibleBoxesWithoutDuplicates() { const selectedTests = new Set(); const boxes = document.querySelectorAll(CONFIG_MAIN.SELECTORS.UNCHECKED_BOX); boxes.forEach(box => { const row = box.closest(CONFIG_MAIN.SELECTORS.CHECKBOX_ROW); if (row && row.offsetParent !== null) { // Check if row is visible 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(); } } } }); logDebugMain(`Checked ${selectedTests.size} unique, non-excluded, visible boxes.`); } const clickButton = (selector, callback) => { const btn = document.querySelector(selector); if (btn && !btn.disabled) { btn.click(); if (callback) setTimeout(callback, 500); return true; } else if (btn && btn.disabled) { 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.`); } 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 checkForDisabledButtons() { // Proactive checks can be added here if needed } function checkUrlAndTriggerClickForUndefined() { if (window.location.href.endsWith('/undefined')) { const closeBtn = document.querySelector(`${CONFIG_MAIN.SELECTORS.TOAST.CONTAINER} ${CONFIG_MAIN.SELECTORS.TOAST.CLOSE_BTN}`); if (closeBtn) { logDebugMain("URL ends with /undefined, attempting to close toast."); closeBtn.click(); } } } function monitorOrderedStatus() { if (!isCorrectPage()) return; const rows = document.querySelectorAll('div[role="row"]'); let hasOrdered = false; let firstOrderedRow = null; rows.forEach(row => { if (row.offsetParent !== null) { // Check if row is visible 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) { if (!btn.classList.contains('btn-warning')) { btn.className = 'btn btn-warning verify1-btn'; btn.innerText = 'VERIFY1 (F7)'; // Class ensures ID for selector is maintained btn.style.setProperty('background-color', '#fab641', 'important'); btn.style.setProperty('color', '#050505', 'important'); logDebugMain("Ordered status detected. VERIFY1 button changed to warning."); } if (firstOrderedRow && !hasScrolledToOrderedRow) { document.documentElement.style.scrollBehavior = 'smooth'; firstOrderedRow.scrollIntoView({ behavior: 'auto', block: 'center' }); hasScrolledToOrderedRow = true; setTimeout(() => document.documentElement.style.scrollBehavior = 'auto', 1000); logDebugMain("Scrolled to first 'Ordered' row."); } } else { if (btn.classList.contains('btn-warning')) { btn.className = 'btn btn-success btn-sm verify1-btn'; btn.innerText = 'VERIFY1 (F7)'; // Class ensures ID for selector is maintained btn.style.setProperty('background-color', '#28a745', 'important'); btn.style.setProperty('color', '#ffffff', 'important'); logDebugMain("No ordered status. VERIFY1 button reverted to success."); } hasScrolledToOrderedRow = false; } } } // Debounced version of monitorOrderedStatus const debouncedMonitorOrderedStatus = debounce(monitorOrderedStatus, 400); const toastObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { let successToast = node.matches(CONFIG_MAIN.SELECTORS.TOAST.SUCCESS) ? node : node.querySelector(CONFIG_MAIN.SELECTORS.TOAST.SUCCESS); if (successToast) { logDebugMain("Success toast detected."); successToast.addEventListener('click', () => { if ((verify1Clicked && verify1Toggle) || (verify2Clicked && verify2Toggle)) { logDebugMain("Navigating back due to toast click and toggle state."); window.history.back(); } verify1Clicked = false; verify2Clicked = false; }, { once: true }); } } }); }); }); toastObserver.observe(document.body, { childList: true, subtree: true }); document.addEventListener('keydown', e => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') return; if (e.key === 'F7') { e.preventDefault(); document.querySelector(CONFIG_MAIN.SELECTORS.VERIFY1_BTN)?.click(); } else if (e.key === 'F8') { e.preventDefault(); document.querySelector(CONFIG_MAIN.SELECTORS.VERIFY2_BTN)?.click(); } }); // --- Integrated Alerts Scanner Code --- if (typeof XLSX === 'undefined') { console.warn("[Alerts Scanner] SheetJS library (XLSX) not found. Local XLSX saving will not work."); } const CONFIG_ALERTS = { SCAN_INTERVAL: 500, FLASH_COLOR: "pink", FLASH_INTERVAL: 700, RESULT_CELL_SELECTOR: 'div[role="gridcell"][col-id="TestResult"] app-result-value-render div', UOM_CELL_SELECTOR: 'div[col-id="UomValue"]', CRITICAL_FLAG_SELECTOR: 'div[role="gridcell"][col-id="LTFlag"] app-ref-high-low div span.critial-alret-indication', TEST_DESC_PINNED_SELECTOR: '.ag-pinned-left-cols-container .ag-row div[col-id="TestDesc"]', NO_RESULT_MESSAGE: "NO-RESULT DETECTED!", DILUTION_MESSAGE: "DILUTION REQUIRED!", PATIENT_NAME_SELECTOR: 'div.patient-name-full', PATIENT_MRN_SELECTOR: 'div.mid.renal-demography span', PATIENT_LOCATION_SELECTOR: 'div.patient-info span[title]', USER_NAME_SELECTOR: 'div.profile-wrapper span.csi-dropdown-btn-text', SAMPLE_BARCODE_SELECTOR: 'div[style*="font-size: 13px; color: rgb(68, 68, 68);"]', LOCAL_SERVER_SAVE_URL: 'http://localhost:5000/save-alerts', CRITICAL_BACKGROUND_COLORS: [ 'rgb(255, 0, 0)', 'red', '#ff0000', 'rgb(220, 53, 69)', 'rgb(217, 83, 79)' ] }; let isScanningActive = false; let issueScanIntervalId = null; const createModalButton = (text, styleType = 'primary') => { const button = document.createElement('button'); button.textContent = text; Object.assign(button.style, { padding: '12px 24px', borderRadius: '8px', fontWeight: '600', fontSize: '15px', border: 'none', cursor: 'pointer', transition: 'background-color 0.2s ease, box-shadow 0.2s ease, transform 0.1s ease', marginLeft: '8px' }); if (styleType === 'primary') { button.style.backgroundColor = '#007bff'; button.style.color = 'white'; button.onmouseover = () => button.style.backgroundColor = '#0069d9'; button.onmouseout = () => button.style.backgroundColor = '#007bff'; } else if (styleType === 'success') { button.style.backgroundColor = '#28a745'; button.style.color = 'white'; button.onmouseover = () => button.style.backgroundColor = '#218838'; button.onmouseout = () => button.style.backgroundColor = '#28a745'; } else { button.style.backgroundColor = '#6c757d'; button.style.color = 'white'; button.onmouseover = () => button.style.backgroundColor = '#5a6268'; button.onmouseout = () => button.style.backgroundColor = '#6c757d'; } button.onmousedown = () => button.style.transform = 'scale(0.98)'; button.onmouseup = () => button.style.transform = 'scale(1)'; return button; }; const createFormGroup = (label, inputId, placeholder, type = 'text') => { const formGroup = document.createElement('div'); const formLabel = document.createElement('label'); formLabel.textContent = label; formLabel.htmlFor = inputId; Object.assign(formLabel.style, { display: 'block', fontSize: '14px', fontWeight: '500', marginBottom: '6px', color: '#495057' }); formGroup.appendChild(formLabel); const formInput = document.createElement('input'); formInput.type = type; formInput.id = inputId; formInput.placeholder = placeholder; Object.assign(formInput.style, { width: '100%', padding: '12px', border: '1px solid #ced4da', borderRadius: '6px', fontSize: '14px', boxSizing: 'border-box', transition: 'border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out' }); formInput.onfocus = () => { formInput.style.borderColor = '#80bdff'; formInput.style.boxShadow = '0 0 0 0.2rem rgba(0,123,255,.25)'; }; formInput.onblur = () => { formInput.style.borderColor = '#ced4da'; formInput.style.boxShadow = 'none'; }; formGroup.appendChild(formInput); return formGroup; }; function applyFlashingEffect(row) { if (!row || row.dataset.flashing === 'true') return; row.dataset.flashing = 'true'; const originalBg = row.style.backgroundColor || 'transparent'; row.dataset.originalBg = originalBg; row.style.transition = "background-color 0.5s ease"; let isPink = false; const intervalId = setInterval(() => { if (!document.body.contains(row) || row.dataset.flashing === 'false') { clearInterval(intervalId); row.style.transition = ''; row.style.setProperty("background-color", row.dataset.originalBg || 'transparent', "important"); 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.toString(); } function stopFlashingEffect(row) { if (row && row.dataset.flashing === 'true') { row.dataset.flashing = 'false'; const intervalId = parseInt(row.dataset.flashIntervalId, 10); if (!isNaN(intervalId)) { clearInterval(intervalId); } } } function getNotificationSessionKey(type, identifier = 'general') { const safeIdentifier = String(identifier).replace(/[^a-zA-Z0-9_-]/g, ''); return `labAlertNotified_${window.location.pathname}${window.location.hash}_${type}_${safeIdentifier}`; } function hasAlreadyNotified(type, identifier = 'general') { const key = getNotificationSessionKey(type, identifier); return sessionStorage.getItem(key) === 'true'; } function setNotificationFlag(type, identifier = 'general') { const key = getNotificationSessionKey(type, identifier); logAlertDebug(`Setting notification flag for key: "${key}"`); sessionStorage.setItem(key, 'true'); } function getNextEntryID() { const counterKey = 'labAlertEntryCounter'; let currentID = parseInt(localStorage.getItem(counterKey), 10) || 0; currentID++; localStorage.setItem(counterKey, String(currentID)); logAlertDebug(`Next Entry ID: ${currentID}`); return currentID; } async function sendAlertDataToServer(alertData) { logAlertDebug("Attempting to send data to local server."); if (!CONFIG_ALERTS.LOCAL_SERVER_SAVE_URL) { logAlertError("LOCAL_SERVER_SAVE_URL is not configured."); return false; } const isFromAutoAlertModal = document.getElementById('custom-alert-modal-overlay') && !document.getElementById('report-selection-modal-overlay'); const isFromManualReportModal = document.getElementById('report-selection-modal-overlay'); let notifiedPersonName, notifiedPersonTelExt, readBack, reportingUserId; if (isFromAutoAlertModal) { logAlertDebug("sendAlertDataToServer: Data sourced from Auto Alert Modal fields."); const notifiedPersonNameInput = document.getElementById('notifiedPersonNameInput'); const notifiedPersonTelExtInput = document.getElementById('notifiedPersonTelExtInput'); const readBackCheckbox = document.getElementById('readBackCheckbox'); const userIdInput = document.getElementById('userIdInput'); notifiedPersonName = notifiedPersonNameInput ? notifiedPersonNameInput.value : ''; notifiedPersonTelExt = notifiedPersonTelExtInput ? notifiedPersonTelExtInput.value : ''; readBack = readBackCheckbox ? readBackCheckbox.checked : false; reportingUserId = userIdInput ? userIdInput.value : (alertData.userId || 'N/A'); } else if (isFromManualReportModal) { logAlertDebug("sendAlertDataToServer: Data sourced from Manual Report Modal payload fields."); notifiedPersonName = alertData.notifiedPersonName || ''; notifiedPersonTelExt = alertData.notifiedPersonTelExt || ''; readBack = alertData.readBack || false; reportingUserId = alertData.userName || 'N/A'; } else { logAlertError("sendAlertDataToServer: Unknown modal source. Using direct alertData fields if available."); notifiedPersonName = alertData.notifiedPersonName || ''; notifiedPersonTelExt = alertData.notifiedPersonTelExt || ''; readBack = alertData.readBack || false; reportingUserId = alertData.userName || alertData.userId || 'N/A'; } const now = new Date(); const date = `${String(now.getDate()).padStart(2, '0')}/${String(now.getMonth() + 1).padStart(2, '0')}/${now.getFullYear()}`; const hours = now.getHours(); const minutes = String(now.getMinutes()).padStart(2, '0'); const ampm = hours >= 12 ? 'pm' : 'am'; const formattedHours = hours % 12 || 12; const time = `${formattedHours}:${minutes} ${ampm}`; const entryID = alertData.entryID || getNextEntryID(); const dataToSend = { entryID: entryID, date: date, time: time, patientName: alertData.patientName || 'N/A', patientMRN: alertData.patientMRN || alertData.patientId || 'N/A', patientLocation: alertData.patientLocation || 'N/A', sampleBarcode: alertData.sampleBarcode || 'N/A', userName: reportingUserId, notifiedPersonName: notifiedPersonName, notifiedPersonTelExt: notifiedPersonTelExt, readBack: readBack, alerts: alertData.alerts ? alertData.alerts.map(alert => ({ testName: alert.testName, result: alert.result, uom: alert.uom || '', flag: alert.flag, type: alert.type, comment: alert.comment || '' })) : (alertData.alertsToList ? alertData.alertsToList.map(alert => ({ testName: alert.testName, result: alert.result, uom: alert.uom || '', flag: alert.flag, type: alert.type, comment: alert.comment || '' })) : []) }; logAlertDebug("Data prepared for server:", JSON.stringify(dataToSend, null, 2)); try { const response = await fetch(CONFIG_ALERTS.LOCAL_SERVER_SAVE_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataToSend) }); if (response.ok) { logAlertDebug("Data successfully sent to local server."); return true; } else { const errorText = await response.text(); logAlertError(`Failed to send data to local server: ${response.status} ${response.statusText} - ${errorText}`); return false; } } catch (error) { logAlertError("Error sending data to local server:", error); return false; } } function closeAlertModalAction() { logAlertDebug("closeAlertModalAction triggered."); const overlay = document.getElementById('custom-alert-modal-overlay'); if (overlay) { const modalContent = overlay.querySelector('#custom-alert-modal-content'); if (modalContent && modalContent.dataset.alerts) { try { const alertsToMark = JSON.parse(modalContent.dataset.alerts); alertsToMark.forEach(alert => { if (alert.type && alert.rowId) { const parts = alert.rowId.split('_'); const type = parts[0]; const baseIdentifier = parts.slice(1).join('_'); setNotificationFlag(type, baseIdentifier); logAlertDebug(`Marked alert as notified: Type=${type}, Identifier=${baseIdentifier} (from full key ${alert.rowId})`); } }); } catch (e) { console.error("Error parsing alerts data from modal dataset:", e); } } overlay.style.opacity = '0'; if (modalContent) { modalContent.style.opacity = '0'; modalContent.style.transform = 'translateY(-30px) scale(0.95)'; } overlay.addEventListener('transitionend', () => { if (overlay.parentNode) { overlay.parentNode.removeChild(overlay); } isAlertModalOpen = false; document.body.style.overflow = ''; logAlertDebug("Alert modal overlay removed, isAlertModalOpen = false"); }, { once: true }); setTimeout(() => { if (overlay && overlay.parentNode) { overlay.parentNode.removeChild(overlay); } if (isAlertModalOpen) { isAlertModalOpen = false; document.body.style.overflow = ''; logAlertDebug("Alert modal overlay removed by fallback, isAlertModalOpen = false"); } }, 400); } else { isAlertModalOpen = false; document.body.style.overflow = ''; logAlertDebug("closeAlertModalAction called but overlay not found, isAlertModalOpen = false"); } } function createCustomAlert(alertData, alertsDisplayedInModal) { logAlertDebug(`Attempting to create alert modal. isAlertModalOpen: ${isAlertModalOpen}, Existing overlay: ${document.getElementById('custom-alert-modal-overlay')}`); if (document.getElementById('custom-alert-modal-overlay') || isAlertModalOpen) { logAlertDebug("Alert modal already open or overlay element exists. Aborting creation."); return; } isAlertModalOpen = true; logDebugMain("Alert modal creation initiated. isAlertModalOpen set to true."); const overlay = document.createElement('div'); overlay.id = 'custom-alert-modal-overlay'; Object.assign(overlay.style, { position: 'fixed', top: '0', left: '0', width: '100%', height: '100%', backgroundColor: 'rgba(0, 0, 0, 0.65)', zIndex: '10001', display: 'flex', justifyContent: 'center', alignItems: 'center', opacity: '0', transition: 'opacity 0.3s ease-out', padding: '20px' }); document.body.style.overflow = 'hidden'; const modalBox = document.createElement('div'); modalBox.id = 'custom-alert-modal-content'; Object.assign(modalBox.style, { backgroundColor: '#ffffff', borderRadius: '12px', boxShadow: '0 16px 32px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.08)', width: 'calc(100% - 40px)', maxWidth: '1100px', maxHeight: '90vh', overflowY: 'auto', padding: '32px', position: 'relative', opacity: '0', transform: 'translateY(-20px) scale(0.98)', transition: 'opacity 0.3s ease-out, transform 0.3s ease-out', fontFamily: "'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif", color: '#343a40', display: 'flex', flexDirection: 'column', gap: '16px' }); const closeButtonX = document.createElement('button'); closeButtonX.innerHTML = '×'; Object.assign(closeButtonX.style, { position: 'absolute', top: '18px', right: '18px', fontSize: '30px', lineHeight: '1', cursor: 'pointer', color: '#adb5bd', background: 'transparent', border: 'none', padding: '0', width: '30px', height: '30px', display: 'flex', alignItems: 'center', justifyContent: 'center' }); closeButtonX.onmouseover = () => closeButtonX.style.color = '#495057'; closeButtonX.onmouseout = () => closeButtonX.style.color = '#adb5bd'; closeButtonX.onclick = closeAlertModalAction; modalBox.appendChild(closeButtonX); const headerDiv = document.createElement('div'); Object.assign(headerDiv.style, { display: 'flex', alignItems: 'center', gap: '12px', paddingBottom: '18px', borderBottom: '1px solid #dee2e6', marginBottom: '16px' }); const alertIcon = document.createElement('i'); alertIcon.className = 'fas fa-exclamation-triangle'; let iconColor = '#17a2b8'; let alertTitleText = 'Lab Alert Detected'; if (alertData.overallSeverity === 'critical') { alertTitleText = 'Critical Lab Alert!'; iconColor = '#d9534f'; } else if (alertData.overallSeverity === 'noresult') { alertTitleText = 'No Result Detected'; iconColor = '#ffc107'; } else if (alertData.overallSeverity === 'greaterThan') { alertTitleText = 'Dilution Required'; iconColor = '#ffc107'; } alertIcon.style.fontSize = '26px'; alertIcon.style.color = iconColor; headerDiv.appendChild(alertIcon); const title = document.createElement('h2'); title.textContent = alertTitleText; Object.assign(title.style, { fontSize: '26px', fontWeight: '600', color: iconColor, margin: '0' }); headerDiv.appendChild(title); const dateTime = document.createElement('p'); dateTime.id = 'currentDateTime'; dateTime.textContent = new Date().toLocaleString([], { dateStyle: 'medium', timeStyle: 'short' }); Object.assign(dateTime.style, { fontSize: '14px', color: '#6c757d', margin: '0', marginLeft: 'auto' }); headerDiv.appendChild(dateTime); modalBox.appendChild(headerDiv); const createInfoItem = (label, value, iconClass) => { if (!value) value = 'N/A'; const itemDiv = document.createElement('div'); Object.assign(itemDiv.style, { backgroundColor: '#f8f9fa', borderRadius: '8px', padding: '16px', border: '1px solid #e9ecef', boxShadow: '0 3px 6px rgba(0,0,0,0.06)', display: 'flex', flexDirection: 'column', gap: '10px' }); const header = document.createElement('div'); Object.assign(header.style, { display: 'flex', alignItems: 'center', gap: '12px', color: '#495057' }); if (iconClass) { const iconElem = document.createElement('i'); iconElem.className = `fas ${iconClass}`; Object.assign(iconElem.style, { fontSize: '1.4em', color: '#007bff' }); header.appendChild(iconElem); } const labelElem = document.createElement('strong'); labelElem.textContent = `${label}:`; labelElem.style.fontSize = '15px'; labelElem.style.fontWeight = '600'; header.appendChild(labelElem); itemDiv.appendChild(header); const valueElem = document.createElement('span'); valueElem.textContent = value; Object.assign(valueElem.style, { fontSize: '16px', color: '#212529', wordBreak: 'break-word', paddingLeft: iconClass ? '32px' : '0' }); itemDiv.appendChild(valueElem); return itemDiv; }; const allPatientInfoContainer = document.createElement('div'); Object.assign(allPatientInfoContainer.style, { display: 'flex', flexDirection: 'column', gap: '18px', paddingBottom: '18px', borderBottom: '1px solid #dee2e6', marginBottom: '16px' }); const pNameItem = createInfoItem('Patient Name', alertData.patientName, 'fa-user'); if (pNameItem) allPatientInfoContainer.appendChild(pNameItem); const otherPatientInfoRow = document.createElement('div'); Object.assign(otherPatientInfoRow.style, { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))', gap: '18px', }); const pIdElem = createInfoItem('MRN', alertData.patientId, 'fa-id-card'); if (pIdElem) otherPatientInfoRow.appendChild(pIdElem); const pLocElem = createInfoItem('Location', alertData.patientLocation, 'fa-hospital-alt'); if (pLocElem) otherPatientInfoRow.appendChild(pLocElem); const pBarcodeElem = createInfoItem('Sample Barcode', alertData.sampleBarcode, 'fa-barcode'); if (pBarcodeElem) otherPatientInfoRow.appendChild(pBarcodeElem); if (otherPatientInfoRow.hasChildNodes()) allPatientInfoContainer.appendChild(otherPatientInfoRow); if (allPatientInfoContainer.hasChildNodes()) modalBox.appendChild(allPatientInfoContainer); if (alertData.alertsToList && alertData.alertsToList.length > 0) { const alertsListTitle = document.createElement('h3'); alertsListTitle.textContent = 'Alert Details:'; Object.assign(alertsListTitle.style, { fontSize: '19px', fontWeight: '600', color: '#343a40', margin: '0 0 12px 0' }); modalBox.appendChild(alertsListTitle); const alertsContainer = document.createElement('div'); Object.assign(alertsContainer.style, { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: '18px', padding: '5px 0' }); alertData.alertsToList.forEach(alert => { const alertCard = document.createElement('div'); let borderColorCard = '#6c757d', bgColorCard = '#f8f9fa'; if (alert.type === 'critical') { borderColorCard = alert.flag === 'CL' ? '#007bff' : (alert.flag === 'CH' ? '#dc3545' : '#d9534f'); bgColorCard = alert.flag === 'CL' ? '#e7f3ff' : (alert.flag === 'CH' ? '#f8d7da' : '#fdf7f7'); } else if (alert.type === 'greaterThan') { borderColorCard = '#ffc107'; bgColorCard = '#fff9e6'; } else if (alert.type === 'noresult') { borderColorCard = '#6c757d'; bgColorCard = '#f8f9fa'; } Object.assign(alertCard.style, { borderLeft: `6px solid ${borderColorCard}`, padding: '18px', borderRadius: '10px', backgroundColor: bgColorCard, color: '#343a40', boxShadow: '0 5px 10px rgba(0,0,0,0.08)', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', minHeight: '130px' }); const testNameElem = document.createElement('p'); testNameElem.innerHTML = `<strong style="font-size: 17px; color: #212529;">${alert.testName}</strong>`; testNameElem.style.margin = '0 0 10px 0'; alertCard.appendChild(testNameElem); const resultElem = document.createElement('p'); resultElem.textContent = `Result: ${alert.result || 'N/A'}${alert.uom ? ` ${alert.uom}` : ''}`; resultElem.style.margin = '0 0 10px 0'; resultElem.style.fontSize = '15px'; alertCard.appendChild(resultElem); if (alert.flag) { const flagElem = document.createElement('p'); let flagDescription = `Flag: ${alert.flag}`; if (alert.flag === 'CL') flagDescription = 'Flag: CL (Critical Low)'; else if (alert.flag === 'CH') flagDescription = 'Flag: CH (Critical High)'; else if (alert.flag === 'BG_CRITICAL') flagDescription = 'Flag: Critical (Red Background)'; flagElem.textContent = flagDescription; Object.assign(flagElem.style, { margin: '0', fontSize: '15px', fontWeight: '600', color: borderColorCard }); alertCard.appendChild(flagElem); } if (alert.comment) { const commentElem = document.createElement('p'); commentElem.textContent = alert.comment; Object.assign(commentElem.style, { fontSize: '13px', fontStyle: 'italic', color: '#555', marginTop: '8px', paddingTop: '8px', borderTop: '1px dashed #ccc' }); alertCard.appendChild(commentElem); } alertsContainer.appendChild(alertCard); }); modalBox.appendChild(alertsContainer); } else if (alertData.primaryMessage) { const primaryMessageElem = document.createElement('p'); primaryMessageElem.textContent = alertData.primaryMessage; Object.assign(primaryMessageElem.style, { fontSize: '16px', fontWeight: '500', margin: '12px 0', color: '#555' }); modalBox.appendChild(primaryMessageElem); } if (alertData.overallSeverity === 'critical') { const notifiedDetailsDiv = document.createElement('div'); Object.assign(notifiedDetailsDiv.style, { marginTop: '24px', paddingTop: '20px', borderTop: '1px solid #dee2e6' }); const h3 = document.createElement('h3'); h3.textContent = 'Notification Details (Critical)'; Object.assign(h3.style, { fontSize: '17px', fontWeight: '600', marginBottom: '16px', color: '#343a40' }); notifiedDetailsDiv.appendChild(h3); const formGrid = document.createElement('div'); Object.assign(formGrid.style, { display: 'grid', gridTemplateColumns: '1fr', gap: '14px' }); const userIdGroup = createFormGroup('User ID', 'userIdInput', 'Enter your User ID'); formGrid.appendChild(userIdGroup); const userIdInputElem = userIdGroup.querySelector('#userIdInput'); if (userIdInputElem && alertData.userId && alertData.userId !== 'N/A') userIdInputElem.value = alertData.userId; formGrid.appendChild(createFormGroup('Notified Person Name', 'notifiedPersonNameInput', 'Name of person notified')); formGrid.appendChild(createFormGroup('Extension / Contact', 'notifiedPersonTelExtInput', 'Phone or extension')); const readBackGroup = document.createElement('div'); Object.assign(readBackGroup.style, { display: 'flex', alignItems: 'center', marginTop: '10px' }); const readBackCheckbox = document.createElement('input'); readBackCheckbox.type = 'checkbox'; readBackCheckbox.id = 'readBackCheckbox'; Object.assign(readBackCheckbox.style, { marginRight: '10px', height: '18px', width: '18px', cursor: 'pointer', accentColor: '#007bff' }); const readBackLabel = document.createElement('label'); readBackLabel.textContent = 'Read-Back Confirmed'; readBackLabel.htmlFor = 'readBackCheckbox'; Object.assign(readBackLabel.style, { fontSize: '14px', fontWeight: '500', color: '#495057', cursor: 'pointer' }); readBackGroup.appendChild(readBackCheckbox); readBackGroup.appendChild(readBackLabel); formGrid.appendChild(readBackGroup); notifiedDetailsDiv.appendChild(formGrid); modalBox.appendChild(notifiedDetailsDiv); } const buttonContainer = document.createElement('div'); Object.assign(buttonContainer.style, { display: 'flex', justifyContent: 'flex-end', paddingTop: '24px', marginTop: 'auto', gap: '12px', borderTop: '1px solid #dee2e6' }); if (alertData.overallSeverity === 'critical') { const sendErrorMsg = document.createElement('p'); sendErrorMsg.id = 'autoAlertSendErrorMsg'; Object.assign(sendErrorMsg.style, { color: 'red', fontSize: '13px', margin: '0 auto 0 0', flexGrow: 1, alignSelf: 'center' }); buttonContainer.appendChild(sendErrorMsg); const submitReportButton = createModalButton('Submit Report', 'success'); submitReportButton.onclick = async () => { logAlertDebug("Submit Report button clicked for auto-alert modal."); sendErrorMsg.textContent = ''; const success = await sendAlertDataToServer(alertData); if (success) { logAlertDebug("Auto-alert data sent successfully. Closing modal."); closeAlertModalAction(); } else { logAlertDebug("Auto-alert data send failed. Modal remains open."); sendErrorMsg.textContent = 'Failed to save. Check server or try again.'; } }; buttonContainer.appendChild(submitReportButton); const cancelButton = createModalButton('Cancel', 'secondary'); cancelButton.onclick = closeAlertModalAction; buttonContainer.appendChild(cancelButton); } else { const okButton = createModalButton('OK', 'primary'); okButton.onclick = closeAlertModalAction; buttonContainer.appendChild(okButton); } modalBox.appendChild(buttonContainer); modalBox.dataset.alerts = JSON.stringify(alertsDisplayedInModal.map(a => ({ type: a.type, rowId: a.rowId }))); overlay.appendChild(modalBox); document.body.appendChild(overlay); setTimeout(() => { overlay.style.opacity = '1'; modalBox.style.opacity = '1'; modalBox.style.transform = 'translateY(0) scale(1)'; logAlertDebug("Alert modal fade-in and animation triggered."); }, 10); overlay.addEventListener('click', (event) => { if (event.target === overlay) closeAlertModalAction(); }); const escapeKeyListener = (event) => { if (event.key === 'Escape' && document.body.contains(overlay)) { closeAlertModalAction(); document.removeEventListener('keydown', escapeKeyListener); } }; document.addEventListener('keydown', escapeKeyListener); } function scrollToRowNearest(row) { if (!row) return false; let rowToScroll = row; if (row.closest('.ag-center-cols-container')) { const rowIndex = row.getAttribute('row-index'); if (rowIndex) { const pinnedRow = document.querySelector(`.ag-pinned-left-cols-container .ag-row[row-index="${rowIndex}"]`); if (pinnedRow) rowToScroll = pinnedRow; } } document.documentElement.style.scrollBehavior = 'smooth'; rowToScroll.scrollIntoView({ behavior: 'auto', block: 'center' }); setTimeout(() => document.documentElement.style.scrollBehavior = 'auto', 500); return true; } async function getPatientDataWithRetry(selectors, maxWaitTime = 2500, checkInterval = 100) { logAlertDebug(`getPatientDataWithRetry CALLED. Max wait: ${maxWaitTime}ms. Selectors: ${JSON.stringify(selectors)}`); const startTime = performance.now(); let elapsedTime = 0; let patientData = { patientName: 'N/A', patientId: 'N/A', patientLocation: 'N/A', sampleBarcode: 'N/A' }; let attempt = 0; while (elapsedTime < maxWaitTime) { attempt++; const nameEl = document.querySelector(selectors.name); const barcodeEl = document.querySelector(selectors.barcode); logAlertDebug(`[Attempt ${attempt}] Name Element (${selectors.name}): ${nameEl ? 'Found' : 'NOT Found'}`); if (nameEl) { patientData.patientName = nameEl.textContent.trim().replace(/\s+/g, ' ') || 'N/A (empty)'; } else { patientData.patientName = 'N/A (selector failed)'; } let mrnValue = 'N/A (MRN not found)'; let mrnFoundByNewLogic = false; const potentialMrnContainers = document.querySelectorAll('div.mid'); potentialMrnContainers.forEach(container => { if (mrnFoundByNewLogic) return; const h6Elements = container.querySelectorAll('h6'); h6Elements.forEach(h6 => { if (mrnFoundByNewLogic) return; const h6Text = h6.textContent.trim().toUpperCase(); if (h6Text.includes('C.MRN') || h6Text.includes('H.MRN')) { const mrnSpanElement = container.querySelector('span'); if (mrnSpanElement && mrnSpanElement.textContent.trim()) { const currentMrn = mrnSpanElement.textContent.trim(); if (h6Text.includes('C.MRN')) { mrnValue = currentMrn; mrnFoundByNewLogic = true; } else if (h6Text.includes('H.MRN') && mrnValue === 'N/A (MRN not found)') { mrnValue = currentMrn; mrnFoundByNewLogic = true; } } } }); }); if (!mrnFoundByNewLogic) { const mrnElOriginal = document.querySelector(selectors.mrn); if (mrnElOriginal && mrnElOriginal.textContent.trim()) { mrnValue = mrnElOriginal.textContent.trim(); } else { mrnValue = mrnElOriginal ? 'N/A (original empty)' : 'N/A (original selector failed)'; } } patientData.patientId = mrnValue; let locElement = document.querySelector('div.patient-info span[title*="UNIT/"]'); if (!locElement) { locElement = document.querySelector(selectors.location); } if (locElement) { const titleAttr = locElement.getAttribute('title'); if (titleAttr && titleAttr.includes('UNIT/') && titleAttr.trim() !== '') { patientData.patientLocation = titleAttr.trim().replace(/\s+/g, ' '); } else if (titleAttr && titleAttr.trim() !== '') { patientData.patientLocation = titleAttr.trim().replace(/\s+/g, ' '); } else { const clone = locElement.cloneNode(true); const h6InLoc = clone.querySelector('h6'); if (h6InLoc) h6InLoc.remove(); let cleanedText = clone.textContent.trim().replace(/\s+/g, ' '); cleanedText = cleanedText.replace(/^Bed\s*/i, '').trim(); patientData.patientLocation = cleanedText || 'N/A (empty after cleaning)'; } if (patientData.patientLocation === '') patientData.patientLocation = 'N/A (empty string after processing)'; } else { patientData.patientLocation = 'N/A (selector failed)'; } if (barcodeEl) { patientData.sampleBarcode = barcodeEl.textContent.trim() || 'N/A (empty)'; } else { patientData.sampleBarcode = 'N/A (selector failed)'; } if (patientData.sampleBarcode === '') patientData.sampleBarcode = 'N/A (empty string)'; const essentialPopulated = !patientData.patientName.startsWith('N/A') && !patientData.patientId.startsWith('N/A') && !patientData.patientLocation.startsWith('N/A'); if (essentialPopulated) break; await new Promise(resolve => setTimeout(resolve, checkInterval)); elapsedTime += checkInterval; } const allEssentialDataRetrieved = !patientData.patientName.startsWith('N/A') && !patientData.patientId.startsWith('N/A') && !patientData.patientLocation.startsWith('N/A'); if (!allEssentialDataRetrieved) { logAlertError(`Essential patient data content NOT FULLY RETRIEVED after ${maxWaitTime}ms. Final state: ${JSON.stringify(patientData)}`); } const endTime = performance.now(); logAlertDebug(`getPatientDataWithRetry finished in ${(endTime - startTime).toFixed(2)}ms. All essential: ${allEssentialDataRetrieved}. Final Data: ${JSON.stringify(patientData)}`); return { data: patientData, allEssentialDataRetrieved: allEssentialDataRetrieved }; } async function checkForIssues() { const totalCheckStartTime = performance.now(); logAlertDebug("checkForIssues started."); if (!isScanningActive || isAlertModalOpen || isReportModalOpen) { logAlertDebug(`checkForIssues aborted. isScanningActive: ${isScanningActive}, isAlertModalOpen: ${isAlertModalOpen}, isReportModalOpen: ${isReportModalOpen}`); return false; } const patientDataSelectors = { name: CONFIG_ALERTS.PATIENT_NAME_SELECTOR, mrn: CONFIG_ALERTS.PATIENT_MRN_SELECTOR, location: CONFIG_ALERTS.PATIENT_LOCATION_SELECTOR, barcode: CONFIG_ALERTS.SAMPLE_BARCODE_SELECTOR }; const patientDataResult = await getPatientDataWithRetry(patientDataSelectors); const patientDataSource = patientDataResult.data; const allEssentialPatientDataAvailable = patientDataResult.allEssentialDataRetrieved; const patientName = patientDataSource.patientName; const patientId = patientDataSource.patientId; const patientLocation = patientDataSource.patientLocation; const sampleBarcode = patientDataSource.sampleBarcode; const userIdElement = document.querySelector(CONFIG_ALERTS.USER_NAME_SELECTOR); const currentUserId = userIdElement ? userIdElement.textContent.trim() : 'N/A'; const centerRows = document.querySelectorAll('.ag-center-cols-container .ag-row'); const pinnedRows = document.querySelectorAll('.ag-pinned-left-cols-container .ag-row'); const potentialAlertsForAutoModal = []; centerRows.forEach((centerRow, index) => { const pinnedRow = pinnedRows[index]; if (!centerRow || !pinnedRow || centerRow.offsetParent === null) return; const testDescElement = pinnedRow.querySelector('div[col-id="TestDesc"]'); const testDesc = testDescElement?.textContent?.trim() || 'Unknown Test'; const resultDiv = centerRow.querySelector(CONFIG_ALERTS.RESULT_CELL_SELECTOR); const flagSpan = centerRow.querySelector(CONFIG_ALERTS.CRITICAL_FLAG_SELECTOR); const uomCell = centerRow.querySelector(CONFIG_ALERTS.UOM_CELL_SELECTOR); const baseRowIdentifier = centerRow.getAttribute('row-id') || centerRow.getAttribute('row-index') || `scan_row_${index}`; const resultText = resultDiv?.textContent?.trim() || ''; const uomText = uomCell?.textContent?.trim() || ''; const normalizedResultText = resultText.toLowerCase().replace(/[- ]/g, ''); const flagTextByIndicator = flagSpan?.textContent?.trim()?.toUpperCase() || ''; let isCriticalByBackground = false; let effectiveFlagText = flagTextByIndicator; const resultCellElement = resultDiv ? resultDiv.closest('div[role="gridcell"][col-id="TestResult"]') : null; if (resultCellElement) { const bgColor = window.getComputedStyle(resultCellElement).backgroundColor; if (CONFIG_ALERTS.CRITICAL_BACKGROUND_COLORS.includes(bgColor.toLowerCase())) { isCriticalByBackground = true; if (!effectiveFlagText) effectiveFlagText = 'BG_CRITICAL'; } } const isCriticalByFlag = (flagSpan && (flagTextByIndicator === "CL" || flagTextByIndicator === "CH")); const isCritical = isCriticalByFlag || isCriticalByBackground; const isNoResult = ["noresult", "noxresult", "x-noresult"].includes(normalizedResultText); const isDilution = resultText.includes(">"); let alertType = null, alertMessage = "", alertComment = null; if (isCritical) { alertType = "critical"; alertMessage = isCriticalByFlag ? `CRITICAL ${flagTextByIndicator === "CL" ? "LOW" : "HIGH"} RESULT!` : "CRITICAL RESULT (Visual Indicator)!"; } else if (isNoResult) { alertType = "noresult"; alertMessage = CONFIG_ALERTS.NO_RESULT_MESSAGE; } else if (isDilution) { alertType = "greaterThan"; alertMessage = CONFIG_ALERTS.DILUTION_MESSAGE; alertComment = "Sample needs dilution"; } if (alertType) { potentialAlertsForAutoModal.push({ rowElement: centerRow, type: alertType, rowId: `${alertType}_${baseRowIdentifier}`, baseIdentifier: baseRowIdentifier, message: alertMessage, comment: alertComment, testName: testDesc, result: resultText, uom: uomText, flag: effectiveFlagText, alreadyNotified: hasAlreadyNotified(alertType, baseRowIdentifier) }); applyFlashingEffect(centerRow); applyFlashingEffect(pinnedRow); } else { stopFlashingEffect(centerRow); stopFlashingEffect(pinnedRow); } }); const newPotentialAlertsForAutoModalDisplay = potentialAlertsForAutoModal.filter(alert => !alert.alreadyNotified); if (newPotentialAlertsForAutoModalDisplay.length > 0 && !isAlertModalOpen && !document.getElementById('custom-alert-modal-overlay')) { if (!allEssentialPatientDataAvailable) { logAlertDebug("Potential new auto-alerts found, but essential patient data is incomplete. Deferring auto-alert modal display."); } else { const firstSignificantAlert = newPotentialAlertsForAutoModalDisplay.find(a => a.type === 'critical' || a.type === 'noresult' || a.type === 'greaterThan') || newPotentialAlertsForAutoModalDisplay[0]; scrollToRowNearest(firstSignificantAlert.rowElement); const modalData = { alertsToList: newPotentialAlertsForAutoModalDisplay.map(a => ({ testName: a.testName, result: a.result, uom: a.uom, flag: a.flag, type: a.type, rowId: a.rowId, comment: a.comment })), overallSeverity: firstSignificantAlert.type, primaryMessage: firstSignificantAlert.message, patientName, patientId, patientLocation, sampleBarcode, userId: currentUserId }; createCustomAlert(modalData, newPotentialAlertsForAutoModalDisplay); logAlertDebug("Auto-alerts shown for patient. Pausing scanner if configured."); if (window.stopAlertsScanner) window.stopAlertsScanner(); } } else if (potentialAlertsForAutoModal.length === 0) { document.querySelectorAll('.ag-center-cols-container .ag-row[data-flashing="true"]').forEach(stopFlashingEffect); document.querySelectorAll('.ag-pinned-left-cols-container .ag-row[data-flashing="true"]').forEach(stopFlashingEffect); } return (newPotentialAlertsForAutoModalDisplay.length > 0 && !isAlertModalOpen && !document.getElementById('custom-alert-modal-overlay') && allEssentialPatientDataAvailable); } function collectAllTestsWithResults() { logDebugMain("Collecting all tests with results for reporting..."); const collectedTests = []; const centerRows = document.querySelectorAll(CONFIG_MAIN.SELECTORS.ALL_TEST_ROWS_CENTER); const pinnedRows = document.querySelectorAll(CONFIG_MAIN.SELECTORS.ALL_TEST_ROWS_PINNED); centerRows.forEach((centerRow, index) => { const pinnedRow = pinnedRows[index]; if (!centerRow || !pinnedRow || centerRow.offsetParent === null) return; const testDescElement = pinnedRow.querySelector(CONFIG_MAIN.SELECTORS.TEST_DESC_CELL); const testName = testDescElement?.textContent?.trim() || 'Unknown Test'; const resultDiv = centerRow.querySelector(CONFIG_MAIN.SELECTORS.TEST_RESULT_CELL_GENERAL); const result = resultDiv?.textContent?.trim() || 'N/A'; const uomCell = centerRow.querySelector(CONFIG_MAIN.SELECTORS.TEST_UOM_CELL_GENERAL); const uom = uomCell?.textContent?.trim() || ''; const flagSpan = centerRow.querySelector(CONFIG_MAIN.SELECTORS.TEST_FLAG_CELL_GENERAL); const flag = flagSpan?.textContent?.trim()?.toUpperCase() || ''; const rowId = centerRow.getAttribute('row-id') || centerRow.getAttribute('row-index') || `test_report_row_${index}`; collectedTests.push({ id: rowId, testName, result, uom, flag, selected: false }); }); logDebugMain(`Collected ${collectedTests.length} tests for reporting.`); return collectedTests; } function createReportSelectionModal(testsToDisplay, patientData, currentUserId) { logDebugMain(`--- createReportSelectionModal CALLED ---. isReportModalOpen: ${isReportModalOpen}. tests: ${testsToDisplay.length}.`); if (document.getElementById('report-selection-modal-overlay') || isReportModalOpen) { logDebugMain("Report selection modal already open or overlay element exists. Aborting."); return; } isReportModalOpen = true; logDebugMain("Report selection modal is now being created. isReportModalOpen = true."); const overlay = document.createElement('div'); overlay.id = 'report-selection-modal-overlay'; Object.assign(overlay.style, { position: 'fixed', top: '0', left: '0', width: '100%', height: '100%', backgroundColor: 'rgba(0, 0, 0, 0.65)', zIndex: '10002', display: 'flex', justifyContent: 'center', alignItems: 'center', opacity: '0', transition: 'opacity 0.3s ease-out', padding: '20px' }); document.body.style.overflow = 'hidden'; const modalBox = document.createElement('div'); modalBox.id = 'report-selection-modal-content'; Object.assign(modalBox.style, { backgroundColor: '#ffffff', borderRadius: '12px', boxShadow: '0 16px 32px rgba(0,0,0,0.1), 0 8px 16px rgba(0,0,0,0.08)', width: 'calc(100% - 40px)', maxWidth: '1000px', maxHeight: '90vh', overflowY: 'auto', padding: '32px', position: 'relative', opacity: '0', transform: 'translateY(-20px) scale(0.98)', transition: 'opacity 0.3s ease-out, transform 0.3s ease-out', fontFamily: "'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif", color: '#343a40', display: 'flex', flexDirection: 'column', gap: '16px' }); const closeButtonX = document.createElement('button'); closeButtonX.innerHTML = '×'; Object.assign(closeButtonX.style, { position: 'absolute', top: '18px', right: '18px', fontSize: '30px', lineHeight: '1', cursor: 'pointer', color: '#adb5bd', background: 'transparent', border: 'none', padding: '0', width: '30px', height: '30px', display: 'flex', alignItems: 'center', justifyContent: 'center' }); closeButtonX.onmouseover = () => closeButtonX.style.color = '#495057'; closeButtonX.onmouseout = () => closeButtonX.style.color = '#adb5bd'; closeButtonX.onclick = closeReportSelectionModalAction; modalBox.appendChild(closeButtonX); const headerDiv = document.createElement('div'); Object.assign(headerDiv.style, { display: 'flex', alignItems: 'center', gap: '12px', paddingBottom: '18px', borderBottom: '1px solid #dee2e6', marginBottom: '16px' }); const reportIcon = document.createElement('i'); reportIcon.className = 'fas fa-file-signature'; reportIcon.style.fontSize = '26px'; reportIcon.style.color = '#17a2b8'; headerDiv.appendChild(reportIcon); const title = document.createElement('h2'); title.textContent = 'Report Critical Test Results'; Object.assign(title.style, { fontSize: '26px', fontWeight: '600', color: '#17a2b8', margin: '0' }); headerDiv.appendChild(title); modalBox.appendChild(headerDiv); const createInfoItemLocal = (label, value, iconClass) => { if (!value) value = 'N/A'; const itemDiv = document.createElement('div'); Object.assign(itemDiv.style, { backgroundColor: '#f8f9fa', borderRadius: '8px', padding: '12px 16px', border: '1px solid #e9ecef', boxShadow: '0 2px 4px rgba(0,0,0,0.05)', display: 'flex', flexDirection: 'column', gap: '8px' }); const header = document.createElement('div'); Object.assign(header.style, { display: 'flex', alignItems: 'center', gap: '10px', color: '#495057' }); if (iconClass) { const iconElem = document.createElement('i'); iconElem.className = `fas ${iconClass}`; Object.assign(iconElem.style, { fontSize: '1.2em', color: '#007bff', width: '20px', textAlign: 'center' }); header.appendChild(iconElem); } const labelElem = document.createElement('strong'); labelElem.textContent = `${label}:`; labelElem.style.fontSize = '14px'; labelElem.style.fontWeight = '600'; header.appendChild(labelElem); itemDiv.appendChild(header); const valueElem = document.createElement('span'); valueElem.textContent = value; Object.assign(valueElem.style, { fontSize: '15px', color: '#212529', wordBreak: 'break-word', paddingLeft: iconClass ? '30px' : '0' }); itemDiv.appendChild(valueElem); return itemDiv; }; const pDataForModal = patientData || {}; const patientInfoContainer = document.createElement('div'); Object.assign(patientInfoContainer.style, { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '12px', paddingBottom: '18px', borderBottom: '1px solid #dee2e6', marginBottom: '16px' }); patientInfoContainer.appendChild(createInfoItemLocal('Patient Name', pDataForModal.patientName, 'fa-user')); patientInfoContainer.appendChild(createInfoItemLocal('MRN', pDataForModal.patientId, 'fa-id-card')); patientInfoContainer.appendChild(createInfoItemLocal('Location', pDataForModal.patientLocation, 'fa-hospital-alt')); patientInfoContainer.appendChild(createInfoItemLocal('Sample Barcode', pDataForModal.sampleBarcode, 'fa-barcode')); modalBox.appendChild(patientInfoContainer); const testsListTitle = document.createElement('h3'); testsListTitle.textContent = 'Select Tests to Include in Report:'; Object.assign(testsListTitle.style, { fontSize: '19px', fontWeight: '600', color: '#343a40', margin: '0 0 12px 0' }); modalBox.appendChild(testsListTitle); const testsContainer = document.createElement('div'); testsContainer.id = 'report-tests-selection-container'; Object.assign(testsContainer.style, { maxHeight: '300px', overflowY: 'auto', border: '1px solid #dee2e6', borderRadius: '8px', padding: '10px', marginBottom: '16px' }); testsToDisplay.forEach(test => { const testItemDiv = document.createElement('div'); testItemDiv.style.cssText = 'display: flex; align-items: center; padding: 10px 8px; border-bottom: 1px solid #e9ecef; gap: 10px; transition: background-color 0.2s;'; const isCriticalTest = test.flag && (test.flag === 'CH' || test.flag === 'CL' || test.flag.includes('CRITICAL')); testItemDiv.onmouseover = () => testItemDiv.style.backgroundColor = '#f8f9fa'; testItemDiv.onmouseout = () => testItemDiv.style.backgroundColor = isCriticalTest ? 'rgba(255, 0, 0, 0.05)' : 'transparent'; if (isCriticalTest) { testItemDiv.style.backgroundColor = 'rgba(255, 0, 0, 0.05)'; } const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = `report_cb_${test.id}`; checkbox.checked = test.selected; checkbox.onchange = () => { test.selected = checkbox.checked; }; Object.assign(checkbox.style, { marginRight: '5px', transform: 'scale(1.2)', cursor: 'pointer', accentColor: '#007bff' }); const label = document.createElement('label'); label.htmlFor = `report_cb_${test.id}`; label.style.cursor = 'pointer'; label.style.flexGrow = '1'; label.style.fontSize = '14px'; let labelText = `<strong style="color: #333;">${test.testName}</strong>: ${test.result || 'N/A'}`; if (test.uom) labelText += ` ${test.uom}`; if (test.flag) labelText += ` <span style="color: ${test.flag === 'CL' ? '#007bff' : (test.flag === 'CH' ? '#dc3545' : '#d9534f')}; font-weight: bold;">(${test.flag})</span>`; label.innerHTML = labelText; testItemDiv.appendChild(checkbox); testItemDiv.appendChild(label); testsContainer.appendChild(testItemDiv); }); modalBox.appendChild(testsContainer); const reportDetailsDiv = document.createElement('div'); Object.assign(reportDetailsDiv.style, { marginTop: '10px', paddingTop: '16px', borderTop: '1px solid #dee2e6' }); const detailsTitle = document.createElement('h4'); detailsTitle.textContent = 'Reporting Details (If Applicable)'; Object.assign(detailsTitle.style, { fontSize: '16px', fontWeight: '600', marginBottom: '12px', color: '#343a40' }); reportDetailsDiv.appendChild(detailsTitle); const formGrid = document.createElement('div'); Object.assign(formGrid.style, { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '14px' }); const userIdGroup = createFormGroup('Your User ID', 'reportUserIdInput', 'Your system User ID'); const userIdInputElem = userIdGroup.querySelector('#reportUserIdInput'); if (userIdInputElem && currentUserId && currentUserId !== 'N/A') userIdInputElem.value = currentUserId; formGrid.appendChild(userIdGroup); formGrid.appendChild(createFormGroup('Notified Person (Name)', 'reportNotifiedPersonNameInput', 'Person contacted')); formGrid.appendChild(createFormGroup('Extension / Contact Info', 'reportNotifiedPersonTelExtInput', 'Phone or extension')); const readBackGroup = document.createElement('div'); Object.assign(readBackGroup.style, { display: 'flex', alignItems: 'center', marginTop: '10px', gridColumn: '1 / -1' }); const readBackCheckbox = document.createElement('input'); readBackCheckbox.type = 'checkbox'; readBackCheckbox.id = 'reportReadBackCheckbox'; Object.assign(readBackCheckbox.style, { marginRight: '10px', height: '18px', width: '18px', cursor: 'pointer', accentColor: '#007bff'}); const readBackLabel = document.createElement('label'); readBackLabel.textContent = 'Read-Back Confirmed'; readBackLabel.htmlFor = 'reportReadBackCheckbox'; Object.assign(readBackLabel.style, { fontSize: '14px', fontWeight: '500', color: '#495057', cursor: 'pointer' }); readBackGroup.appendChild(readBackCheckbox); readBackGroup.appendChild(readBackLabel); formGrid.appendChild(readBackGroup); reportDetailsDiv.appendChild(formGrid); modalBox.appendChild(reportDetailsDiv); const buttonContainer = document.createElement('div'); Object.assign(buttonContainer.style, { display: 'flex', justifyContent: 'flex-end', paddingTop: '24px', marginTop: 'auto', gap: '12px', borderTop: '1px solid #dee2e6' }); const submitReportButton = createModalButton('Submit Report', 'success'); submitReportButton.onclick = async () => { const selectedTests = testsToDisplay.filter(t => t.selected); if (selectedTests.length === 0) { alert("Please select at least one test to report."); return; } const reportedUserId = document.getElementById('reportUserIdInput')?.value || currentUserId; const notifiedPerson = document.getElementById('reportNotifiedPersonNameInput')?.value || ''; const notifiedContact = document.getElementById('reportNotifiedPersonTelExtInput')?.value || ''; const readBackConfirmed = document.getElementById('reportReadBackCheckbox')?.checked || false; const pDataForPayload = patientData || {}; const reportDataPayload = { entryID: getNextEntryID(), date: new Date().toLocaleDateString('en-GB'), time: new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }), patientName: pDataForPayload.patientName || 'N/A', patientMRN: pDataForPayload.patientId || 'N/A', patientLocation: pDataForPayload.patientLocation || 'N/A', sampleBarcode: pDataForPayload.sampleBarcode || 'N/A', userName: reportedUserId, notifiedPersonName: notifiedPerson, notifiedPersonTelExt: notifiedContact, readBack: readBackConfirmed, alerts: selectedTests.map(st => ({ testName: st.testName, result: st.result, uom: st.uom, flag: st.flag, type: 'user_reported_critical', comment: 'Manually selected by user for critical reporting.' })), }; logDebugMain("Manual Report Payload:", JSON.stringify(reportDataPayload, null, 2)); const serverSuccess = await sendAlertDataToServer(reportDataPayload); if (serverSuccess) { alert("Critical results reported successfully and saved to server."); closeReportSelectionModalAction(); } else { alert("Critical results logged to console. FAILED to save to server. Please ensure the local server is running or check console for errors."); } }; buttonContainer.appendChild(submitReportButton); const cancelButton = createModalButton('Cancel', 'secondary'); cancelButton.onclick = closeReportSelectionModalAction; buttonContainer.appendChild(cancelButton); modalBox.appendChild(buttonContainer); overlay.appendChild(modalBox); document.body.appendChild(overlay); setTimeout(() => { overlay.style.opacity = '1'; modalBox.style.opacity = '1'; modalBox.style.transform = 'translateY(0) scale(1)'; }, 10); overlay.addEventListener('click', (event) => { if (event.target === overlay) closeReportSelectionModalAction(); }); const escapeKeyListenerReport = (event) => { if (event.key === 'Escape' && document.body.contains(overlay)) { closeReportSelectionModalAction(); document.removeEventListener('keydown', escapeKeyListenerReport); } }; document.addEventListener('keydown', escapeKeyListenerReport); } function closeReportSelectionModalAction() { logDebugMain("closeReportSelectionModalAction triggered."); const overlay = document.getElementById('report-selection-modal-overlay'); if (overlay) { overlay.style.opacity = '0'; const modalContent = overlay.querySelector('#report-selection-modal-content'); if (modalContent) { modalContent.style.opacity = '0'; modalContent.style.transform = 'translateY(-30px) scale(0.95)'; } overlay.addEventListener('transitionend', () => { if (overlay.parentNode) { overlay.parentNode.removeChild(overlay); } isReportModalOpen = false; document.body.style.overflow = ''; logDebugMain("Report selection modal overlay removed."); }, { once: true }); setTimeout(() => { if (overlay && overlay.parentNode) { overlay.parentNode.removeChild(overlay); } if(isReportModalOpen) { isReportModalOpen = false; document.body.style.overflow = ''; } }, 400); } else { isReportModalOpen = false; document.body.style.overflow = ''; } } function startAlertsScannerInternal() { logAlertDebug("startAlertsScannerInternal called."); if (isScanningActive && issueScanIntervalId === null) { logAlertDebug("Alert Scanner starting/resuming..."); checkForIssues(); issueScanIntervalId = setInterval(checkForIssues, CONFIG_ALERTS.SCAN_INTERVAL); } else if (!isScanningActive && issueScanIntervalId === null) { logAlertDebug("Alert Scanner starting fresh (isScanningActive was false)..."); isScanningActive = true; checkForIssues(); issueScanIntervalId = setInterval(checkForIssues, CONFIG_ALERTS.SCAN_INTERVAL); } } function stopAlertsScannerInternal() { logAlertDebug("stopAlertsScannerInternal called (pause behavior)."); if (issueScanIntervalId !== null) { clearInterval(issueScanIntervalId); issueScanIntervalId = null; } } window.startAlertsScanner = startAlertsScannerInternal; window.stopAlertsScanner = stopAlertsScannerInternal; let previousObservedUrl = window.location.href; const pageUrlObserver = new MutationObserver(() => { const newObservedUrl = window.location.href; if (newObservedUrl !== previousObservedUrl) { logDebugMain(`URL changed from: ${previousObservedUrl} to: ${newObservedUrl}.`); const wasOnEditPage = previousObservedUrl.startsWith(CONFIG_MAIN.URLS.EDIT_PAGE_PREFIX); const nowOnEditPage = newObservedUrl.startsWith(CONFIG_MAIN.URLS.EDIT_PAGE_PREFIX); if (wasOnEditPage && !nowOnEditPage) { logDebugMain("Navigated AWAY from edit page. Stopping scanner, clearing flags, removing UI."); isScanningActive = false; if (window.stopAlertsScanner) window.stopAlertsScanner(); try { const oldUrlObject = new URL(previousObservedUrl, window.location.origin); const oldPageKeyPrefix = `labAlertNotified_${oldUrlObject.pathname}${oldUrlObject.hash}_`; Object.keys(sessionStorage) .filter(key => key.startsWith(oldPageKeyPrefix)) .forEach(key => { logAlertDebug(`Removing sessionStorage key: ${key}`); sessionStorage.removeItem(key); }); } catch (e) { logAlertError(`Error processing old URL for flag clearing: ${e}`);} const customButtons = document.getElementById('custom-script-buttons'); if (customButtons) customButtons.remove(); const alertOverlay = document.getElementById('custom-alert-modal-overlay'); if (alertOverlay) alertOverlay.remove(); isAlertModalOpen = false; const reportOverlay = document.getElementById('report-selection-modal-overlay'); if (reportOverlay) reportOverlay.remove(); isReportModalOpen = false; document.body.style.overflow = ''; } if (nowOnEditPage) { logDebugMain("Navigated TO an edit page. Resetting states, (re)starting scanner, adding UI."); hasScrolledToOrderedRow = false; isAlertModalOpen = false; isReportModalOpen = false; const existingAlertOverlay = document.getElementById('custom-alert-modal-overlay'); if (existingAlertOverlay) existingAlertOverlay.remove(); const existingReportOverlay = document.getElementById('report-selection-modal-overlay'); if (existingReportOverlay) existingReportOverlay.remove(); document.body.style.overflow = ''; if (!isScanningActive) { isScanningActive = true; } setTimeout(() => { if (window.startAlertsScanner) window.startAlertsScanner(); addButtons(); addFontAwesome(); monitorOrderedStatus(); // Initial check on new page }, 700); } else { const customButtons = document.getElementById('custom-script-buttons'); if (customButtons) customButtons.remove(); if (isScanningActive) { isScanningActive = false; if (window.stopAlertsScanner) window.stopAlertsScanner(); } } previousObservedUrl = newObservedUrl; } else { // URL hasn't changed, but mutations occurred. const stillOnEditPage = newObservedUrl.startsWith(CONFIG_MAIN.URLS.EDIT_PAGE_PREFIX); if (stillOnEditPage) { if (!document.getElementById('custom-script-buttons')) { addButtons(); // Re-add buttons if missing } addFontAwesome(); // Ensure FontAwesome is still there // Call debounced monitorOrderedStatus to update button based on current grid state debouncedMonitorOrderedStatus(); // MODIFIED: Call debounced version if (isScanningActive && issueScanIntervalId === null && window.startAlertsScanner) { window.startAlertsScanner(); } else if (!isScanningActive && window.startAlertsScanner) { isScanningActive = true; window.startAlertsScanner(); } } } }); pageUrlObserver.observe(document.body, { childList: true, subtree: true }); if (isCorrectPage()) { isScanningActive = true; setTimeout(() => { if (window.startAlertsScanner) window.startAlertsScanner(); addFontAwesome(); addButtons(); monitorOrderedStatus(); // Initial check }, 700); } else { isScanningActive = false; } setInterval(checkUrlAndTriggerClickForUndefined, CONFIG_MAIN.CHECK_INTERVALS.UNDEFINED_URL); setInterval(checkForDisabledButtons, CONFIG_MAIN.CHECK_INTERVALS.DISABLED_BTN_CHECK); })(); // User's SAMPLES COUNT Logic (IIFE) (function () { 'use strict'; const SCRIPT_PREFIX_SAMPLES = "[SAMPLES COUNT 통합]"; const logSampleCountDebug = msg => console.debug(`${SCRIPT_PREFIX_SAMPLES} ${msg}`); function createCounterElementSamples(id) { const wrapper = document.createElement('span'); wrapper.id = id; Object.assign(wrapper.style, { display: 'inline-flex', alignItems: 'center', padding: '6px 12px', backgroundColor: '#e9ecef', borderRadius: '20px', boxShadow: '0 2px 5px rgba(0,0,0,0.1)', marginRight: 'auto', marginLeft: '10px', fontSize: '14px', fontWeight: '500', color: '#495057', verticalAlign: 'middle' }); const label = document.createElement('span'); label.textContent = 'SAMPLES COUNT: '; label.style.marginRight = '8px'; const badge = document.createElement('span'); badge.className = 'sample-count-badge'; badge.textContent = '0'; Object.assign(badge.style, { backgroundColor: '#6c757d', color: '#ffffff', padding: '4px 10px', borderRadius: '12px', fontSize: '14px', fontWeight: 'bold', minWidth: '26px', textAlign: 'center', lineHeight: '1' }); wrapper.appendChild(label); wrapper.appendChild(badge); return wrapper; } function updateSpecificCounterSamples(modalElementForInputs, counterElement, inputSelector) { const badge = counterElement.querySelector('.sample-count-badge'); if (!badge || !modalElementForInputs || !document.body.contains(modalElementForInputs) || !counterElement.isConnected) return; const inputs = modalElementForInputs.querySelectorAll(inputSelector); const count = inputs.length; badge.textContent = count; badge.style.backgroundColor = count > 0 ? '#28a745' : '#6c757d'; } function setupModalCounterSamples(modalConfig) { const { modalKeyElement, targetFooter, counterId, inputSelector, activeIntervalsMap, buttonIdForLog } = modalConfig; if (targetFooter.querySelector('#' + counterId)) { return false; } const counter = createCounterElementSamples(counterId); targetFooter.insertBefore(counter, targetFooter.firstChild); const modalElementForInputs = modalKeyElement.querySelector('.modal-body') || modalKeyElement.querySelector('.modal-content') || modalKeyElement; const interval = setInterval(() => { const modalStillTracked = document.body.contains(modalKeyElement); const counterStillPresent = counter.isConnected; let modalVisible = false; if (modalStillTracked && modalKeyElement instanceof Element) { modalVisible = modalKeyElement.classList.contains('show') || modalKeyElement.classList.contains('modal-open') || (modalKeyElement.style && modalKeyElement.style.display && modalKeyElement.style.display !== 'none') || (modalKeyElement.style && !modalKeyElement.style.display && modalKeyElement.offsetParent !== null); } if (!modalStillTracked || !counterStillPresent || !modalVisible) { clearInterval(interval); activeIntervalsMap.delete(modalKeyElement); if (counter.isConnected) counter.remove(); return; } updateSpecificCounterSamples(modalElementForInputs, counter, inputSelector); }, 500); activeIntervalsMap.set(modalKeyElement, { interval, counterId }); return true; } function observeModalsSamples() { const activeModalIntervals = new Map(); const getModalKeyDetails = (el) => { if (!el || !(el instanceof Element)) return "non-element"; const tagName = el.tagName || "UNKNOWN_TAG"; let className = ""; if (el.className && typeof el.className === 'string') { className = el.className.trim().replace(/\s+/g, '.'); } else if (el.className && typeof el.className.baseVal === 'string') { className = el.className.baseVal.trim().replace(/\s+/g, '.'); } return `${tagName}${className ? '.' + className : ''}`; }; const observer = new MutationObserver(() => { const modalConfigs = [ { buttonSelector: 'button#btnclose-smplcollection', logId: 'btnclose-smplcollection', counterIdSuffix: 'smplcollection', inputSelector: 'tbody[formarrayname="TubeTypeList"] input[formcontrolname="PatientID"]' }, { buttonSelector: 'button#closebtn-smplrecieve', logId: 'closebtn-smplrecieve', counterIdSuffix: 'smplrecieve', inputSelector: 'td input[formcontrolname="PatientID"]' } ]; modalConfigs.forEach(config => { const button = document.querySelector(config.buttonSelector); if (button) { const footer = button.closest('.modal-footer'); if (footer) { const modalKeyElement = footer.closest('.modal.show, .modal-dialog, modal-container.show'); if (modalKeyElement && !activeModalIntervals.has(modalKeyElement)) { setupModalCounterSamples({ ...config, modalKeyElement: modalKeyElement, targetFooter: footer, activeIntervalsMap: activeModalIntervals }); } } } }); activeModalIntervals.forEach((data, modalElemKey) => { if (!(modalElemKey instanceof Element)) { clearInterval(data.interval); const counterElem = document.getElementById(data.counterId); if (counterElem && counterElem.isConnected) counterElem.remove(); activeModalIntervals.delete(modalElemKey); return; } const modalStillPresent = document.body.contains(modalElemKey); const counterElem = document.getElementById(data.counterId); let modalStillVisible = false; if (modalStillPresent) { modalStillVisible = modalElemKey.classList.contains('show') || modalElemKey.classList.contains('modal-open') || (modalElemKey.style.display && modalElemKey.style.display !== 'none') || (!modalElemKey.style.display && modalElemKey.offsetParent !== null); } if (!modalStillPresent || !modalStillVisible) { clearInterval(data.interval); if (counterElem && counterElem.isConnected) counterElem.remove(); activeModalIntervals.delete(modalElemKey); } else if (counterElem && !counterElem.isConnected) { clearInterval(data.interval); activeModalIntervals.delete(modalElemKey); } }); }); observer.observe(document.body, { childList: true, subtree: true }); } window.addEventListener('load', () => { try { observeModalsSamples(); } catch (e) { console.error(`${SCRIPT_PREFIX_SAMPLES} Error initializing modal observer: ${e.message} \n${e.stack}`); } }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址