// ==UserScript==
// @name KAAUH Lab Suite - Verification, Alerts & Enhancements
// @namespace Violentmonkey Scripts
// @version 6.7.1
// @description Combines verification buttons (F7/F8), dynamic alerts (>, NO RESULT, X-NORESULT, CL/CH) showing only the *last* alert per scan, checkbox automation, toggle back-nav, and improved scrolling.
// @match *://his.kaauh.org/lab/*
// @grant none
// @author Hamad AlShegifi
// @license MIT
// ==/UserScript==
(function() {
'use strict';
function applySingleLineStyles() {
const anchor = document.querySelector('li > a[href="#/lab-orders/doctor-request"]');
if (anchor) {
anchor.style.setProperty('white-space', 'nowrap', 'important');
anchor.style.setProperty('overflow', 'visible', 'important');
anchor.style.setProperty('text-overflow', 'unset', 'important');
const spans = anchor.querySelectorAll('span');
spans.forEach(span => {
span.style.setProperty('display', 'inline', 'important');
span.style.setProperty('font-size', '13px', 'important');
span.style.setProperty('white-space', 'nowrap', 'important');
});
}
const simplifySpan = (selector) => {
const span = document.querySelector(selector);
if (span) {
span.style.setProperty('display', 'inline', 'important');
span.style.setProperty('font-size', '20px', 'important');
span.style.setProperty('white-space', 'nowrap', 'important');
span.style.setProperty('overflow', 'visible', 'important');
span.style.setProperty('text-overflow', 'unset', 'important');
span.textContent = span.textContent.replace(/\s+/g, ''); // optional
}
};
simplifySpan('span.to-do');
simplifySpan('span.pending-orders');
}
// Debounce function to limit how often applySingleLineStyles is called
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
const debouncedStyleUpdater = debounce(applySingleLineStyles, 300);
const observer = new MutationObserver(debouncedStyleUpdater);
observer.observe(document.body, { childList: true, subtree: true });
// Initial run
applySingleLineStyles();
})();
(function () {
'use strict';
/*** CONFIGURATION (Main Script) ***/
const CONFIG_MAIN = {
URLS: {
EDIT_PAGE_PREFIX: 'https://his.kaauh.org/lab/#/lab-orders/edit-lab-order/',
},
SELECTORS: {
VERIFY1_BTN: '#custom-script-buttons button.verify1-btn',
VERIFY2_BTN: '#custom-script-buttons button.verify2-btn',
COMPLETE_TECH: 'button.dropdown-item[translateid="test-results.CompleteTechnicalVerification"]',
COMPLETE_MED: 'button.dropdown-item[translateid="test-results.CompleteMedicalVerification"]',
FINAL_VERIFY: 'button.btn-success.btn-sm.min-width[translateid="test-verification.Verify"]',
NEXT_BTN: 'button#btnNext',
UNCHECKED_BOX: 'span.ag-icon-checkbox-unchecked[unselectable="on"]',
CHECKBOX_ROW: '.ag-row',
TEST_DESC_CELL: '[col-id="TestDesc"]',
ORDERED_STATUS_CELL: 'div[col-id="ResultStatus"]',
TOAST: {
CONTAINER: '#toast-container',
CLOSE_BTN: 'button.toast-close-button',
SUCCESS: '.toast-success',
}
},
CHECK_INTERVALS: {
UNDEFINED_URL: 200,
ORDERED_SCAN: 500,
DISABLED_BTN_CHECK: 1000,
},
EXCLUDE_WORDS: [
'culture', "gram's stain", 'stain', 'bacterial', 'fungal',
'pcr', 'meningitis', 'mrsa', 'mid', 'stream', 'cryptococcus'
]
};
/*** STATE FLAGS ***/
let verify1Clicked = false;
let verify2Clicked = false;
let verify1Toggle = localStorage.getItem('verify1Toggle') === 'true';
let verify2Toggle = localStorage.getItem('verify2Toggle') === 'true';
let hasScrolledToOrderedRow = false;
let lastDisabledButtonAlertTime = 0;
const DISABLED_ALERT_COOLDOWN = 30000;
/*** UTILITY FUNCTIONS ***/
const logDebugMain = msg => console.debug(`[LabScript Main] ${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();
}
const addFontAwesome = () => {
if (!document.querySelector('link[href*="font-awesome"]')) {
logDebugMain("Injecting Font Awesome.");
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css';
document.head.appendChild(link);
}
};
/*** BUTTON + ICON CREATORS ***/
function createVerifyButton(label, className, onClick, id) {
const button = document.createElement('button');
button.type = 'button';
button.className = className;
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;
}
function createToggleIcon(id, isActive, onClick) {
const icon = document.createElement('span');
icon.id = id;
icon.innerHTML = `<i class="fas fa-arrow-circle-left" aria-hidden="true" style="color: ${isActive ? '#008000' : '#d1cfcf'}; font-size: 1.3em; vertical-align: middle;"></i>`;
icon.style.cursor = 'pointer';
icon.style.marginRight = '10px';
icon.style.marginLeft = '-1px';
icon.title = "Toggle: Go back automatically after this verification success toast is clicked";
icon.onclick = onClick;
return icon;
}
function handleVerifyToggle(type) {
logDebugMain(`Toggling auto-back for ${type}`);
const toggle = type === 'verify1' ? !verify1Toggle : !verify2Toggle;
localStorage.setItem(type + 'Toggle', toggle);
const icon = document.querySelector(`#${type}Icon i`);
if (icon) icon.style.setProperty('color', toggle ? '#008000' : '#d1cfcf', 'important');
if (type === 'verify1') verify1Toggle = toggle;
else verify2Toggle = toggle;
}
/*** BUTTONS INJECTION ***/
function addButtons() {
if (document.getElementById('custom-script-buttons') || !isCorrectPage()) return;
const nextButton = document.querySelector(CONFIG_MAIN.SELECTORS.NEXT_BTN);
if (!nextButton || !nextButton.parentNode) {
setTimeout(addButtons, 500);
return;
}
logDebugMain("Adding custom buttons to page.");
const buttonDiv = document.createElement('div');
buttonDiv.id = 'custom-script-buttons';
buttonDiv.style.setProperty('display', 'inline-block', 'important');
buttonDiv.style.setProperty('margin-left', '10px', 'important');
buttonDiv.style.setProperty('vertical-align', 'middle', 'important');
const verify1Button = createVerifyButton('VERIFY1 (F7)', 'btn btn-success btn-sm', () => {
logDebugMain('VERIFY1 (F7) clicked.');
verify1Clicked = true;
verify2Clicked = false;
checkAllVisibleBoxesWithoutDuplicates();
setTimeout(clickCompleteTechnicalVerificationButton, 500);
}, 'verify1-btn');
const verify2Button = createVerifyButton('VERIFY2 (F8)', 'btn btn-primary btn-sm', () => {
logDebugMain('VERIFY2 (F8) clicked.');
verify2Clicked = true;
verify1Clicked = false;
checkAllVisibleBoxesWithoutDuplicates();
setTimeout(clickCompleteMedicalVerificationButton, 500);
}, 'verify2-btn');
const verify1Icon = createToggleIcon('verify1Icon', verify1Toggle, () => handleVerifyToggle('verify1'));
const verify2Icon = createToggleIcon('verify2Icon', verify2Toggle, () => handleVerifyToggle('verify2'));
const credit = document.createElement('span');
credit.textContent = "Modded by: Hamad AlShegifi";
credit.style.cssText = `
font-size: 11px !important;
font-weight: bold !important;
color: #e60000 !important;
margin-left: 15px !important;
border: 1px solid #e60000 !important;
border-radius: 5px !important;
padding: 3px 6px !important;
background-color: #ffffff !important;
vertical-align: middle !important;
`;
buttonDiv.append(verify1Button, verify1Icon, verify2Button, verify2Icon, credit);
nextButton.parentNode.insertBefore(buttonDiv, nextButton.nextSibling);
logDebugMain("Custom buttons added.");
}
/*** CORE ACTIONS ***/
function checkAllVisibleBoxesWithoutDuplicates() {
logDebugMain("Checking all visible boxes...");
const selectedTests = new Set();
const boxes = document.querySelectorAll(CONFIG_MAIN.SELECTORS.UNCHECKED_BOX);
let count = 0;
boxes.forEach(box => {
const row = box.closest(CONFIG_MAIN.SELECTORS.CHECKBOX_ROW);
if (row && row.offsetParent !== null) {
const testDescElement = row.querySelector(CONFIG_MAIN.SELECTORS.TEST_DESC_CELL);
const descText = testDescElement?.textContent.trim().toLowerCase();
if (descText) {
const isExcluded = CONFIG_MAIN.EXCLUDE_WORDS.some(word => descText.includes(word));
if (!selectedTests.has(descText) && !isExcluded) {
selectedTests.add(descText);
box.click();
count++;
} else if (selectedTests.has(descText)) {
} else if (isExcluded) {
}
} else {
logDebugMain("Found checkbox in row without valid test description.");
}
}
});
logDebugMain(`Clicked ${count} checkboxes.`);
}
const clickButton = (selector, callback) => {
const btn = document.querySelector(selector);
if (btn && !btn.disabled) {
logDebugMain(`Clicking button: ${selector}`);
btn.click();
if (callback) {
setTimeout(callback, 500);
}
return true;
} else if (btn && btn.disabled) {
logDebugMain(`Button found but disabled: ${selector}`);
const buttonName = selector.includes('CompleteTechnicalVerification') ? 'Complete Technical Verification' :
selector.includes('CompleteMedicalVerification') ? 'Complete Medical Verification' :
selector.includes('Verify') ? 'Final Verify' : 'Button';
showDisabledButtonAlert(`${buttonName} button is disabled. Please check if you have selected all required tests or if verification is already done.`);
} else {
logDebugMain(`Button not found: ${selector}`);
}
return false;
};
const clickCompleteTechnicalVerificationButton = () => {
clickButton(CONFIG_MAIN.SELECTORS.COMPLETE_TECH, clickFinalVerifyButton);
};
const clickCompleteMedicalVerificationButton = () => {
clickButton(CONFIG_MAIN.SELECTORS.COMPLETE_MED, clickFinalVerifyButton);
};
const clickFinalVerifyButton = () => {
clickButton(CONFIG_MAIN.SELECTORS.FINAL_VERIFY);
};
function checkForDisabledButtons() {
if (!isCorrectPage()) return;
const verify1Btn = document.querySelector(CONFIG_MAIN.SELECTORS.VERIFY1_BTN);
const verify2Btn = document.querySelector(CONFIG_MAIN.SELECTORS.VERIFY2_BTN);
if (verify1Btn && verify1Btn.disabled) {
logDebugMain("VERIFY1 button is disabled");
showDisabledButtonAlert("Verification is disabled. Make sure tests not already verified.");
}
if (verify2Btn && verify2Btn.disabled) {
logDebugMain("VERIFY2 button is disabled");
showDisabledButtonAlert("Verification is disabled. Make sure tests not already verified.");
}
}
function checkUrlAndTriggerClickForUndefined() {
if (window.location.href.endsWith('/undefined')) {
logDebugMain("Detected '/undefined' URL. Attempting to close toast.");
const closeBtn = document.querySelector(`${CONFIG_MAIN.SELECTORS.TOAST.CONTAINER} ${CONFIG_MAIN.SELECTORS.TOAST.CLOSE_BTN}`);
if (closeBtn) {
closeBtn.click();
logDebugMain("Clicked toast close button.");
}
}
}
function monitorOrderedStatus() {
const rows = document.querySelectorAll('div[role="row"]');
let hasOrdered = false;
let firstOrderedRow = null;
rows.forEach(row => {
if (row.offsetParent !== null) {
const cell = row.querySelector(CONFIG_MAIN.SELECTORS.ORDERED_STATUS_CELL);
if (cell?.textContent.includes('Ordered')) {
if (!firstOrderedRow) firstOrderedRow = row;
hasOrdered = true;
}
}
});
const btn = document.querySelector(CONFIG_MAIN.SELECTORS.VERIFY1_BTN);
if (btn) {
if (hasOrdered) {
if (!btn.classList.contains('btn-warning')) {
logDebugMain("Found 'Ordered' status, updating VERIFY1 button.");
btn.className = 'btn btn-warning verify1-btn';
btn.innerText = 'VERIFY1 (F7)';
btn.style.setProperty('background-color', '#fab641', 'important');
btn.style.setProperty('color', '#050505', 'important');
}
if (firstOrderedRow && !hasScrolledToOrderedRow) {
logDebugMain("Scrolling to first 'Ordered' row.");
document.documentElement.style.scrollBehavior = 'smooth';
firstOrderedRow.scrollIntoView({ behavior: 'auto', block: 'center' });
hasScrolledToOrderedRow = true;
setTimeout(() => {
document.documentElement.style.scrollBehavior = 'auto';
logDebugMain("Scroll complete, scroll behavior reset.");
}, 1000);
}
} else {
if (btn.classList.contains('btn-warning')) {
logDebugMain("No 'Ordered' status found, resetting VERIFY1 button.");
btn.className = 'btn btn-success btn-sm verify1-btn';
btn.innerText = 'VERIFY1 (F7)';
btn.style.setProperty('background-color', '#28a745', 'important');
btn.style.setProperty('color', '#ffffff', 'important');
}
hasScrolledToOrderedRow = false;
}
}
}
setInterval(monitorOrderedStatus, CONFIG_MAIN.CHECK_INTERVALS.ORDERED_SCAN);
/*** TOAST OBSERVER (for Auto-Back) ***/
const toastObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
let successToast = null;
if (node.matches(CONFIG_MAIN.SELECTORS.TOAST.SUCCESS)) {
successToast = node;
} else if (node.querySelector) {
successToast = node.querySelector(CONFIG_MAIN.SELECTORS.TOAST.SUCCESS);
}
if (successToast) {
logDebugMain("Success toast detected.");
successToast.addEventListener('click', () => {
logDebugMain("Success toast clicked.");
if ((verify1Clicked && verify1Toggle) || (verify2Clicked && verify2Toggle)) {
logDebugMain("Auto-back triggered.");
window.history.back();
}
verify1Clicked = false;
verify2Clicked = false;
}, { once: true });
}
}
});
});
});
toastObserver.observe(document.body, { childList: true, subtree: true });
/*** HOTKEYS ***/
document.addEventListener('keydown', e => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') {
return;
}
if (e.key === 'F7') {
e.preventDefault();
logDebugMain("F7 key pressed.");
document.querySelector(CONFIG_MAIN.SELECTORS.VERIFY1_BTN)?.click();
} else if (e.key === 'F8') {
e.preventDefault();
logDebugMain("F8 key pressed.");
document.querySelector(CONFIG_MAIN.SELECTORS.VERIFY2_BTN)?.click();
}
});
/*** MAIN PAGE WATCHER (for SPA Navigation) ***/
const pageObserver = new MutationObserver(() => {
if (isCorrectPage()) {
addFontAwesome();
if (!document.getElementById('custom-script-buttons')) {
addButtons();
hasScrolledToOrderedRow = false;
}
} else {
const customButtons = document.getElementById('custom-script-buttons');
if (customButtons) {
logDebugMain("Navigated away from edit page, removing custom buttons.");
customButtons.remove();
}
}
});
pageObserver.observe(document.body, { childList: true, subtree: true });
/*** INITIAL BOOTSTRAP ***/
logDebugMain("KAAUH Lab Suite script initializing...");
if (isCorrectPage()) {
addFontAwesome();
addButtons();
}
setInterval(checkUrlAndTriggerClickForUndefined, CONFIG_MAIN.CHECK_INTERVALS.UNDEFINED_URL);
setInterval(checkForDisabledButtons, CONFIG_MAIN.CHECK_INTERVALS.DISABLED_BTN_CHECK);
})();
// --- Start of Integrated Alerts Scanner Code ---
(function () {
'use strict';
const CONFIG_ALERTS = {
SCAN_INTERVAL: 150,
FLASH_COLOR: "pink",
FLASH_INTERVAL: 500,
RESULT_CELL_SELECTOR: 'div[role="gridcell"][col-id="TestResult"] app-result-value-render div',
CRITICAL_FLAG_SELECTOR: 'div[role="gridcell"][col-id="LTFlag"] app-ref-high-low div span.critial-alret-indication',
CHECKBOX_PARENT_ROW_SELECTOR: '.ag-row',
NO_RESULT_MESSAGE: "NO-RESULT DETECTED!!",
SOUND_URL: 'https://notificationsounds.com/storage/sounds/download/48088/file-sounds-916-light.mp3',
};
let isScanningActive = false;
let issueScanIntervalId = null;
let alertAudio = null;
function logDebugAlerts(message) {
console.log(`[Alerts Scanner] ${message}`);
}
function playAlertSound() {
if (!alertAudio) {
if (!CONFIG_ALERTS.SOUND_URL) {
console.error("[Alerts Scanner] CONFIG_ALERTS.SOUND_URL is not defined!");
return;
}
alertAudio = new Audio(CONFIG_ALERTS.SOUND_URL);
alertAudio.loop = true;
}
alertAudio.pause();
alertAudio.currentTime = 0;
alertAudio.play().catch(e => console.warn("[Alerts Scanner] Sound playback blocked by browser:", e));
setTimeout(() => {
if (alertAudio) {
alertAudio.pause();
alertAudio.currentTime = 0;
}
}, 3000);
}
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;
}
function stopFlashingEffect(row) {
if (row && row.dataset.flashing === 'true') {
row.dataset.flashing = 'false';
}
}
function getNotificationSessionKey(type, identifier = 'general') {
return `labAlertNotified_${window.location.pathname}_${type}_${identifier}`;
}
function hasAlreadyNotified(type, identifier = 'general') {
return sessionStorage.getItem(getNotificationSessionKey(type, identifier)) === 'true';
}
function setNotificationFlag(type, identifier = 'general') {
sessionStorage.setItem(getNotificationSessionKey(type, identifier), 'true');
}
function createCustomAlert(message, callback) {
if (document.getElementById('custom-alert-modal-overlay')) return;
const overlay = document.createElement('div');
overlay.id = 'custom-alert-modal-overlay';
Object.assign(overlay.style, {
position: 'fixed', top: 0, left: 0, width: '100vw', height: '100vh',
backgroundColor: 'rgba(0, 0, 0, 0.5)', display: 'flex',
justifyContent: 'center', alignItems: 'center', zIndex: 9999
});
const modal = document.createElement('div');
Object.assign(modal.style, {
backgroundColor: '#fff', borderRadius: '12px', padding: '24px 28px',
boxShadow: '0 8px 20px rgba(0, 0, 0, 0.15)', width: 'auto', minWidth: '300px',
maxWidth: '90%', textAlign: 'left', position: 'relative',
fontFamily: 'Segoe UI, sans-serif', animation: 'fadeIn 0.2s ease-out', overflow: 'hidden'
});
const closeBtn = document.createElement('span');
closeBtn.innerHTML = '×';
Object.assign(closeBtn.style, {
position: 'absolute', top: '12px', right: '16px', fontSize: '20px',
color: '#999', cursor: 'pointer'
});
closeBtn.onclick = () => {
document.body.removeChild(overlay);
if (callback) callback();
};
const title = document.createElement('h2');
title.textContent = 'Lab Alert';
Object.assign(title.style, {
margin: '0 0 12px', fontSize: '20px', color: '#d9534f'
});
const msg = document.createElement('div');
msg.innerHTML = message.split('\n').map(testLine => {
const parts = testLine.split(' ');
if (parts.length > 1) {
const trigger = parts.pop();
const restOfLine = parts.join(' ');
if (trigger.length <= 3 && trigger.match(/^[A-Z]{1,2}$/)) {
return `<span style="display: inline-block; margin-right: 10px; font-weight: bold;">${restOfLine} <span style="color: red;">${trigger}</span></span>`;
} else {
return `<span style="display: inline-block; margin-right: 10px; font-weight: bold;">${testLine}</span>`;
}
} else {
return `<span style="display: inline-block; margin-right: 10px; font-weight: bold;">${testLine}</span>`;
}
}).join('<br>');
Object.assign(msg.style, {
fontSize: '16px', color: '#333', marginBottom: '20px', whiteSpace: 'normal',
wordBreak: 'break-word', maxWidth: '100%', textAlign: 'left', overflowX: 'auto'
});
const okBtn = document.createElement('button');
okBtn.textContent = 'OK';
Object.assign(okBtn.style, {
padding: '10px 20px', fontSize: '16px', backgroundColor: '#5cb85c',
color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer', width: '100%'
});
okBtn.onclick = () => {
document.body.removeChild(overlay);
if (callback) callback();
};
modal.append(closeBtn, title, msg, okBtn);
overlay.appendChild(modal);
document.body.appendChild(overlay);
okBtn.focus();
}
function scrollToRowNearest(row) {
if (!row) return false;
document.documentElement.style.scrollBehavior = 'smooth';
row.scrollIntoView({ behavior: 'auto', block: 'center' });
setTimeout(() => document.documentElement.style.scrollBehavior = 'auto', 500);
return true;
}
function checkForIssues() {
const centerRows = document.querySelectorAll('.ag-center-cols-container .ag-row');
const pinnedRows = document.querySelectorAll('.ag-pinned-left-cols-container .ag-row');
const potentialAlerts = [];
centerRows.forEach((centerRow, index) => {
const pinnedRow = pinnedRows[index];
if (!centerRow || !pinnedRow) return;
const resultDiv = centerRow.querySelector(CONFIG_ALERTS.RESULT_CELL_SELECTOR);
const flagSpan = centerRow.querySelector(CONFIG_ALERTS.CRITICAL_FLAG_SELECTOR);
const testDesc = pinnedRow.querySelector('div[col-id="TestDesc"]')?.textContent.trim() || 'Unknown Test';
const rowId = centerRow.getAttribute('row-id') || `${index}`;
const resultText = resultDiv?.textContent.trim().toLowerCase() || '';
const flagText = flagSpan?.textContent.trim().toUpperCase() || '';
const isNoResult = ["no result", "no-xresult", "x-noresult"].includes(resultText);
const isDilution = resultText.includes(">");
const isCritical = (flagText === "CL" || flagText === "CH");
if (!isCritical && centerRow.dataset.flashing === 'true') {
stopFlashingEffect(centerRow);
}
if (isCritical) {
const key = `critical_${rowId}`;
if (!hasAlreadyNotified("critical", key)) {
potentialAlerts.push({
rowElement: centerRow, type: "critical", rowId: key,
message: `CRITICAL ${flagText === "CL" ? "LOW" : "HIGH"} RESULT DETECTED !!`,
testName: testDesc, result: resultText, flag: flagText
});
setNotificationFlag("critical", key);
}
applyFlashingEffect(centerRow);
}
if (isNoResult && !isCritical) {
const key = `noresult_${rowId}`;
if (!hasAlreadyNotified("noresult", key)) {
potentialAlerts.push({
rowElement: centerRow, type: "noresult", rowId: key,
message: CONFIG_ALERTS.NO_RESULT_MESSAGE,
testName: testDesc, result: resultText, flag: ''
});
setNotificationFlag("noresult", key);
}
applyFlashingEffect(centerRow);
}
if (isDilution && !isCritical) {
const key = `dilution_${rowId}`;
if (!hasAlreadyNotified("greaterThan", key)) {
potentialAlerts.push({
rowElement: centerRow, type: "greaterThan", rowId: key,
message: "Dilution is required for this test (> detected)!",
testName: testDesc, result: resultText, flag: ''
});
setNotificationFlag("greaterThan", key);
}
applyFlashingEffect(centerRow);
}
});
if (potentialAlerts.length > 0) {
const firstAlert = potentialAlerts.find(alert => alert.type === 'critical') || potentialAlerts[0];
playAlertSound();
scrollToRowNearest(firstAlert.rowElement);
let messageText;
if (firstAlert.type === 'critical') {
messageText = `${firstAlert.message}\n\n🔴 ${firstAlert.testName} = ${firstAlert.result} ${firstAlert.flag}`;
} else {
messageText = `${firstAlert.message}\n\n🔴 ${firstAlert.testName} =\n${firstAlert.result}`;
}
createCustomAlert(messageText);
potentialAlerts.forEach(alert => applyFlashingEffect(alert.rowElement));
return true;
} else {
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 false;
}
function startAlertsScannerInternal() {
if (!isScanningActive) {
logDebugAlerts("Starting scan for alerts...");
isScanningActive = true;
checkForIssues();
issueScanIntervalId = setInterval(checkForIssues, CONFIG_ALERTS.SCAN_INTERVAL);
} else {
logDebugAlerts("Scanner is already active (internal call).");
}
}
function stopAlertsScannerInternal() {
if (isScanningActive) {
logDebugAlerts("Stopping scan for alerts (internal call).");
clearInterval(issueScanIntervalId);
issueScanIntervalId = null;
isScanningActive = false;
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);
if (alertAudio && !alertAudio.paused) {
alertAudio.pause();
alertAudio.currentTime = 0;
}
} else {
logDebugAlerts("Scanner is not active (internal call).");
}
}
window.startAlertsScanner = function() { // Exposed to window for external calls if needed
startAlertsScannerInternal();
};
window.stopAlertsScanner = function() { // Exposed to window for external calls if needed
stopAlertsScannerInternal();
};
// Observer to automatically start/stop scanning when results grid appears/disappears
const gridObserver = new MutationObserver(() => {
const resultsGridExists = document.querySelector(CONFIG_ALERTS.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG_ALERTS.CRITICAL_FLAG_SELECTOR);
if (resultsGridExists) {
if (!isScanningActive) {
setTimeout(startAlertsScannerInternal, 300); // Use internal function
}
} else {
if (isScanningActive) {
stopAlertsScannerInternal(); // Use internal function
}
}
});
gridObserver.observe(document.body, { childList: true, subtree: true });
// Initial check in case the grid is already present on script load
if (document.querySelector(CONFIG_ALERTS.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG_ALERTS.CRITICAL_FLAG_SELECTOR)) {
startAlertsScannerInternal(); // Use internal function
}
})();
// --- End of Integrated Alerts Scanner Code ---
(function () {
'use strict';
const counterId = 'inline-patientid-counter';
function createInlineCounter(container) {
if (document.getElementById(counterId)) return;
const wrapper = document.createElement('span');
wrapper.id = counterId;
wrapper.style.marginLeft = '12px';
wrapper.style.fontSize = '18px';
wrapper.style.fontWeight = 'bold';
wrapper.style.display = 'inline-flex';
wrapper.style.alignItems = 'center';
const label = document.createElement('span');
label.textContent = 'SAMPLES COUNT: ';
label.style.marginRight = '6px';
label.style.color = '#333';
const badge = document.createElement('span');
badge.className = 'sample-count-badge';
badge.textContent = '0';
badge.style.backgroundColor = '#6c757d';
badge.style.color = '#fff';
badge.style.padding = '2px 8px';
badge.style.borderRadius = '12px';
badge.style.fontSize = '18px';
badge.style.minWidth = '24px';
badge.style.textAlign = 'center';
wrapper.appendChild(label);
wrapper.appendChild(badge);
container.appendChild(wrapper);
}
function updateCounter() {
const badge = document.querySelector('#' + counterId + ' .sample-count-badge');
if (!badge) return;
const inputs = document.querySelectorAll(
'modal-container.show td input[formcontrolname="PatientID"]'
);
const count = inputs.length;
badge.textContent = count;
badge.style.backgroundColor = count > 0 ? '#28a745' : '#6c757d';
}
function observeModal() {
const observer = new MutationObserver(() => {
const closeBtn = document.getElementById('closebtn-smplrecieve');
const modal = document.querySelector('modal-container.show');
if (closeBtn && modal && !document.getElementById(counterId)) {
createInlineCounter(closeBtn.parentElement);
updateCounter();
const interval = setInterval(() => {
if (!document.body.contains(modal)) {
clearInterval(interval);
const existing = document.getElementById(counterId);
if (existing) existing.remove();
} else {
updateCounter();
}
}, 800);
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
window.addEventListener('load', observeModal);
})();