// ==UserScript==
// @name KAAUH Lab Enhancement Suite 1 (Buttons, Alerts, Auto-Actions)
// @namespace Violentmonkey Scripts
// @version 5.6.3
// @description Combines verification buttons (F7/F8), dynamic alerts (>, NO RESULT, X-NORESULT, CL/CH) with modals that persist until dismissed, checkbox automation, toggle back-nav.
// @match *://his.kaauh.org/lab/*
// @grant none
// @author Hamad AlShegifi
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// --- Configuration ---
const CONFIG = {
// General & Debugging
DEBUG_MODE: true,
PERSISTENT_MODALS: true, // Modals require manual dismissal
NO_RESULT_MESSAGE: "NO-RESULT DETECTED!!", // Standardized message
// Script 1: Buttons & Verification
TARGET_EDIT_PAGE_URL_PREFIX: 'https://his.kaauh.org/lab/#/lab-orders/edit-lab-order/',
EXCLUDE_WORDS: ['culture', "gram's stain", 'stain', 'bacterial', 'fungal', 'culture', 'pcr', 'Meningitis', 'MRSA', 'Mid', 'stream', 'Cryptococcus'],
VERIFY1_BUTTON_SELECTOR: '#custom-script-buttons button.btn-success',
VERIFY2_BUTTON_SELECTOR: '#custom-script-buttons button.btn-primary',
COMPLETE_TECH_VERIFY_SELECTOR: 'button.dropdown-item[translateid="test-results.CompleteTechnicalVerification"]',
COMPLETE_MED_VERIFY_SELECTOR: 'button.dropdown-item[translateid="test-results.CompleteMedicalVerification"]',
FINAL_VERIFY_BUTTON_SELECTOR: 'button.btn-success.btn-sm.min-width[translateid="test-verification.Verify"]',
NEXT_BUTTON_SELECTOR: 'button#btnNext',
TEST_DESC_SELECTOR: 'div[col-id="TestDesc"]',
UNCHECKED_BOX_SELECTOR: 'span.ag-icon-checkbox-unchecked[unselectable="on"]',
CHECKBOX_PARENT_ROW_SELECTOR: '.ag-row',
// Script 2: Alerts & Scanning
SCAN_INTERVAL: 150,
FLASH_COLOR: "pink",
FLASH_INTERVAL: 500,
MODAL_TIMEOUT: 10000,
RESULT_CELL_SELECTOR: 'div[role="gridcell"][col-id="TestResult"] app-result-value-render div',
CRITICAL_FLAG_SELECTOR: 'div[role="gridcell"][col-id="LTFlag"] app-ref-high-low div span.critial-alret-indication',
// Script 3 & General Toast Handling
UNDEFINED_URL_CHECK_INTERVAL: 200,
TOAST_CONTAINER_SELECTOR: '#toast-container',
TOAST_CLOSE_BUTTON_SELECTOR: 'button.toast-close-button',
SUCCESS_TOAST_SELECTOR: '.toast-success',
};
// --- State Variables ---
let verify1Toggle = localStorage.getItem('verify1Toggle') === 'true';
let verify2Toggle = localStorage.getItem('verify2Toggle') === 'true';
let verify1Clicked = false;
let verify2Clicked = false;
let issueScanIntervalId = null;
let isScanningActive = false;
const activeModals = new Set();
// --- Utility Functions ---
function logDebug(message) {
if (CONFIG.DEBUG_MODE) console.log(`[Lab Suite v5.6.3] ${message}`);
}
function loadFontAwesome() {
const existingLink = document.querySelector('link[href*="font-awesome"]');
if (!existingLink) {
const link = document.createElement('link');
link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css';
link.rel = 'stylesheet';
document.head.appendChild(link);
logDebug('Font Awesome loaded');
}
}
function isVisible(element) {
return !!(element && (element.offsetWidth || element.offsetHeight || element.getClientRects().length));
}
// --- Modal Functions ---
function showModal(message) {
// Remove duplicate modals with same message
document.querySelectorAll('.lab-suite-modal').forEach(modal => {
if (modal.textContent.includes(message)) {
const overlay = document.querySelector('.lab-suite-modal-overlay');
if (overlay) overlay.remove();
modal.remove();
}
});
logDebug(`Showing modal: ${message}`);
const overlay = document.createElement("div");
overlay.className = 'lab-suite-modal-overlay';
overlay.style.position = "fixed";
overlay.style.top = "0";
overlay.style.left = "0";
overlay.style.width = "100%";
overlay.style.height = "100%";
overlay.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
overlay.style.zIndex = "2000";
overlay.style.opacity = "0";
overlay.style.transition = "opacity 0.3s ease";
document.body.appendChild(overlay);
const modal = document.createElement("div");
modal.className = 'lab-suite-modal';
modal.style.position = "fixed";
modal.style.top = "50%";
modal.style.left = "50%";
modal.style.transform = "translate(-50%, -50%) scale(0.5)";
modal.style.backgroundColor = "#f4f4f9";
modal.style.padding = "30px";
modal.style.boxShadow = "0px 10px 30px rgba(0, 0, 0, 0.15)";
modal.style.zIndex = "2001";
modal.style.borderRadius = "15px";
modal.style.textAlign = "center";
modal.style.transition = "transform 0.3s ease, opacity 0.3s ease";
modal.style.opacity = "0";
modal.style.maxWidth = "450px";
modal.setAttribute('role', 'alert');
modal.setAttribute('aria-live', 'assertive');
const heading = document.createElement("h2");
heading.textContent = "Attention!";
heading.style.fontFamily = "'Arial', sans-serif";
heading.style.color = "#333";
heading.style.marginBottom = "10px";
heading.style.fontSize = "24px";
modal.appendChild(heading);
const content = document.createElement("p");
// Standardize NO RESULT messages
if (message.includes("NO RESULT") || message.includes("X-NORESULT")) {
message = CONFIG.NO_RESULT_MESSAGE;
}
content.textContent = message;
content.style.fontFamily = "'Arial', sans-serif";
content.style.color = "#555";
content.style.marginBottom = "20px";
content.style.fontSize = "16px";
content.style.lineHeight = "1.5";
modal.appendChild(content);
const okButton = createModalButton("OK", "#ff4081", () => {
logDebug("Modal manually dismissed.");
closeModal(modal, overlay);
});
modal.appendChild(okButton);
document.body.appendChild(modal);
// Animate appearance
setTimeout(() => {
overlay.style.opacity = "1";
modal.style.transform = "translate(-50%, -50%) scale(1)";
modal.style.opacity = "1";
}, 10);
activeModals.add(modal);
}
function createModalButton(text, backgroundColor, onClick) {
const button = document.createElement("button");
button.textContent = text;
button.style.padding = "10px 20px";
button.style.border = "none";
button.style.backgroundColor = backgroundColor;
button.style.color = "white";
button.style.borderRadius = "30px";
button.style.cursor = "pointer";
button.style.fontSize = "16px";
button.style.transition = "background-color 0.3s ease, transform 0.2s ease";
button.style.minWidth = '80px';
button.style.margin = '0 5px';
button.addEventListener("mouseenter", () => {
button.style.backgroundColor = darkenColor(backgroundColor, 20);
button.style.transform = "scale(1.05)";
});
button.addEventListener("mouseleave", () => {
button.style.backgroundColor = backgroundColor;
button.style.transform = "scale(1)";
});
button.addEventListener("click", onClick);
return button;
}
function closeModal(modal, overlay) {
if (!modal || !overlay || !document.body.contains(modal)) return;
logDebug("Closing modal.");
modal.style.transform = "translate(-50%, -50%) scale(0.5)";
modal.style.opacity = "0";
overlay.style.opacity = "0";
setTimeout(() => {
if (document.body.contains(modal)) document.body.removeChild(modal);
if (document.body.contains(overlay)) document.body.removeChild(overlay);
activeModals.delete(modal);
}, 300);
}
function darkenColor(color, percent) {
try {
let num = parseInt(color.slice(1), 16);
let amt = Math.round(2.55 * percent);
let R = (num >> 16) - amt;
let G = ((num >> 8) & 0x00ff) - amt;
let B = (num & 0x0000ff) - amt;
R = R < 0 ? 0 : R;
G = G < 0 ? 0 : G;
B = B < 0 ? 0 : B;
return `#${(0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1)}`;
} catch (e) {
logDebug(`Error darkening color ${color}: ${e}`);
return color;
}
}
// --- Script 1 Functions ---
function isCorrectPage() {
return window.location.href.startsWith(CONFIG.TARGET_EDIT_PAGE_URL_PREFIX);
}
function createVerifyButton(label, className, onClick) {
let button = document.createElement('button');
button.type = 'button';
button.innerHTML = label;
button.className = className;
let buttonColors = {
'btn btn-success btn-sm': '#28a745',
'btn btn-primary btn-sm': '#2594d9'
};
button.style.cssText = `
font-family: 'Arial', sans-serif !important;
font-size: 14px !important;
font-weight: normal !important;
color: #ffffff !important;
background-color: ${buttonColors[className] || '#6c757d'} !important;
padding: 8px 16px !important;
border: none !important;
border-radius: 5px !important;
text-shadow: none !important;
cursor: pointer !important;
margin-right: 5px !important;
line-height: 1.5 !important;
`;
button.onclick = onClick;
return button;
}
function createToggleIcon(id, isActive, onClick) {
let icon = document.createElement('span');
icon.id = id;
icon.innerHTML = `<i class="fa 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.onclick = onClick;
icon.title = "Go back after this verification";
return icon;
}
function handleVerify1IconToggle() {
verify1Toggle = !verify1Toggle;
localStorage.setItem('verify1Toggle', verify1Toggle);
const iconElement = document.querySelector('#verify1Icon i');
if (iconElement) iconElement.style.color = verify1Toggle ? '#008000' : '#d1cfcf';
logDebug(`Verify1 Toggle set to: ${verify1Toggle}`);
}
function handleVerify2IconToggle() {
verify2Toggle = !verify2Toggle;
localStorage.setItem('verify2Toggle', verify2Toggle);
const iconElement = document.querySelector('#verify2Icon i');
if (iconElement) iconElement.style.color = verify2Toggle ? '#008000' : '#d1cfcf';
logDebug(`Verify2 Toggle set to: ${verify2Toggle}`);
}
function addButtons() {
if (document.getElementById('custom-script-buttons') || !isCorrectPage()) return;
const nextButton = document.querySelector(CONFIG.NEXT_BUTTON_SELECTOR);
if (nextButton && nextButton.parentNode) {
logDebug("Adding custom VERIFY buttons.");
let buttonDiv = document.createElement('div');
buttonDiv.id = 'custom-script-buttons';
buttonDiv.style.display = 'inline-block';
buttonDiv.style.marginLeft = '10px';
buttonDiv.style.verticalAlign = 'middle';
// VERIFY1 Button & Toggle
let verify1Button = createVerifyButton('VERIFY1 (F7)', 'btn btn-success btn-sm', () => {
logDebug("VERIFY1 button clicked.");
verify1Clicked = true;
verify2Clicked = false;
checkAllVisibleBoxesWithoutDuplicates();
setTimeout(() => { clickCompleteTechnicalVerificationButton(); }, 500);
});
let verify1Icon = createToggleIcon('verify1Icon', verify1Toggle, handleVerify1IconToggle);
// VERIFY2 Button & Toggle
let verify2Button = createVerifyButton('VERIFY2 (F8)', 'btn btn-primary btn-sm', () => {
logDebug("VERIFY2 button clicked.");
verify2Clicked = true;
verify1Clicked = false;
checkAllVisibleBoxesWithoutDuplicates();
setTimeout(() => { clickCompleteMedicalVerificationButton(); }, 500);
});
let verify2Icon = createToggleIcon('verify2Icon', verify2Toggle, handleVerify2IconToggle);
// Author Credit
let modedByText = document.createElement('span');
modedByText.textContent = "Moded by: Hamad AlShegifi";
modedByText.style.fontSize = '12px';
modedByText.style.fontWeight = 'bold';
modedByText.style.color = '#ff0000';
modedByText.style.position = 'relative';
modedByText.style.left = '0px';
modedByText.style.top = '0px';
modedByText.style.border = '1.5px solid #ff0000';
modedByText.style.borderRadius = '8px';
modedByText.style.padding = '4px';
modedByText.style.backgroundColor = 'white';
buttonDiv.appendChild(verify1Button);
buttonDiv.appendChild(verify1Icon);
buttonDiv.appendChild(verify2Button);
buttonDiv.appendChild(verify2Icon);
buttonDiv.appendChild(modedByText);
nextButton.parentNode.insertBefore(buttonDiv, nextButton.nextSibling);
} else {
logDebug("Could not find Next button to anchor custom buttons.");
}
}
function checkAllVisibleBoxesWithoutDuplicates() {
logDebug("Checking checkboxes...");
const testDivs = document.querySelectorAll(CONFIG.TEST_DESC_SELECTOR);
let seenTests = new Set();
let boxesChecked = 0;
const excludeWordsLower = CONFIG.EXCLUDE_WORDS.map(word => word.toLowerCase());
testDivs.forEach(testDiv => {
const testName = testDiv.textContent?.trim().toLowerCase() || '';
if (!testName) return;
if (excludeWordsLower.some(word => testName.includes(word))) {
return;
}
if (seenTests.has(testName)) {
return;
}
seenTests.add(testName);
const parentRow = testDiv.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR);
if (parentRow) {
const checkbox = parentRow.querySelector(CONFIG.UNCHECKED_BOX_SELECTOR);
if (checkbox && isVisible(checkbox)) {
logDebug(`Clicking checkbox for test: ${testName}`);
const event = new MouseEvent('click', { bubbles: true, cancelable: true, view: window });
checkbox.dispatchEvent(event);
boxesChecked++;
}
}
});
logDebug(`${boxesChecked} unique, non-excluded checkboxes were checked.`);
}
function clickCompleteTechnicalVerificationButton() {
const button = document.querySelector(CONFIG.COMPLETE_TECH_VERIFY_SELECTOR);
if (button) {
button.click();
logDebug("Complete Technical Verification button clicked!");
setTimeout(() => { clickFinalVerifyButton(); }, 700);
} else {
logDebug("Complete Technical Verification button not found!");
showModal("Error: Unable to find the 'Complete Technical Verification' button.");
}
}
function clickCompleteMedicalVerificationButton() {
const button = document.querySelector(CONFIG.COMPLETE_MED_VERIFY_SELECTOR);
if (button) {
button.click();
logDebug("Complete Medical Verification button clicked!");
setTimeout(() => { clickFinalVerifyButton(); }, 700);
} else {
logDebug("Complete Medical Verification button not found!");
showModal("Error: Unable to find the 'Complete Medical Verification' button.");
}
}
function clickFinalVerifyButton() {
const verifyButton = document.querySelector(CONFIG.FINAL_VERIFY_BUTTON_SELECTOR);
if (verifyButton) {
verifyButton.click();
logDebug("Final Verify button clicked!");
} else {
logDebug("Final Verify button not found!");
showModal("Error: Unable to find the final 'Verify' button.");
}
}
// --- Script 2 Functions ---
function applyFlashingEffect(rows) {
rows.forEach(row => {
if (row.dataset.flashing) return;
row.dataset.flashing = 'true';
logDebug("Applying flashing effect to a row.");
row.style.transition = "background-color 0.5s ease";
let isPink = false;
const originalBg = row.style.backgroundColor || 'transparent';
const intervalId = setInterval(() => {
if (!document.contains(row)) {
clearInterval(intervalId);
delete row.dataset.flashing;
return;
}
isPink = !isPink;
row.style.backgroundColor = isPink ? CONFIG.FLASH_COLOR : originalBg;
}, CONFIG.FLASH_INTERVAL);
row.dataset.flashIntervalId = intervalId;
});
}
function getNotificationSessionKey(type, identifier = 'general') {
return `labSuiteNotified_${window.location.pathname}_${type}_${identifier}`;
}
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);
logDebug(`Setting notification flag for key: ${key}`);
sessionStorage.setItem(key, 'true');
}
function checkForIssues() {
const resultDivs = document.querySelectorAll(CONFIG.RESULT_CELL_SELECTOR);
const criticalDivs = document.querySelectorAll(CONFIG.CRITICAL_FLAG_SELECTOR);
function scrollToRow(element) {
const row = element.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR);
if (row) {
const rowId = row.getAttribute('row-id');
logDebug(`Attempting to scroll to row with ID: ${rowId || 'ID not found'}`);
row.scrollIntoView({ behavior: 'smooth', block: 'start' });
return { scrolled: true, rowId: rowId };
}
return { scrolled: false };
}
let alertShownThisCycle = false;
// 1. Check for Critical Flags (CL/CH)
if (!alertShownThisCycle && !hasAlreadyNotified('critical')) {
criticalDivs.forEach(div => {
if (alertShownThisCycle) return;
const text = div.textContent?.trim() || '';
if (text === "CL" || text === "CH") {
const scrollResult = scrollToRow(div);
if (scrollResult.scrolled) {
const specificRowKey = scrollResult.rowId || text;
if (!hasAlreadyNotified('critical', specificRowKey)) {
logDebug(`Found ${text} for row ${specificRowKey}`);
const message = text === "CL" ? "CRITICAL LOW RESULT DETECTED !!" : "CRITICAL HIGH RESULT DETECTED !!";
showModal(message);
setNotificationFlag('critical', specificRowKey);
setNotificationFlag('critical');
alertShownThisCycle = true;
}
}
}
});
}
// 2. Check for "NO RESULT" / "X-NORESULT"
if (!alertShownThisCycle && !hasAlreadyNotified('noresult')) {
resultDivs.forEach(div => {
if (alertShownThisCycle) return;
const text = div.textContent?.trim().toLowerCase() || '';
const isNoResultType = (text === "no result" || text === "no-xresult" || text === "x-noresult");
if (isNoResultType) {
const scrollResult = scrollToRow(div);
if (scrollResult.scrolled) {
const specificRowKey = scrollResult.rowId || text.replace(/[\s-]+/g, '_');
if (!hasAlreadyNotified('noresult', specificRowKey)) {
logDebug(`Found ${text} for row ${specificRowKey}`);
showModal(CONFIG.NO_RESULT_MESSAGE);
setNotificationFlag('noresult', specificRowKey);
setNotificationFlag('noresult');
alertShownThisCycle = true;
}
}
}
});
}
// 3. Check for ">" (Dilution Required)
if (!alertShownThisCycle) {
resultDivs.forEach(div => {
const text = div.textContent?.trim().toLowerCase() || '';
if (text.includes(">")) {
const rowElement = div.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR);
const rowId = rowElement?.getAttribute('row-id') || text;
const eventKey = `greaterThan_${rowId}`;
if (!hasAlreadyNotified('greaterThan', rowId)) {
if (rowElement) {
logDebug(`Found '>' for row ${rowId}`);
applyFlashingEffect([rowElement]);
showModal("Dilution is required for this sample (> detected)!");
setNotificationFlag('greaterThan', rowId);
scrollToRow(div);
}
}
}
});
}
return alertShownThisCycle;
}
function startContinuousScanning() {
if (isScanningActive) return;
logDebug("Starting continuous issue scanning...");
isScanningActive = true;
if (issueScanIntervalId) clearInterval(issueScanIntervalId);
issueScanIntervalId = setInterval(() => {
const resultsGridExists = document.querySelector(CONFIG.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG.CRITICAL_FLAG_SELECTOR);
if (!resultsGridExists) {
logDebug("Monitored elements disappeared, stopping issue scan.");
stopContinuousScanning();
return;
}
checkForIssues();
}, CONFIG.SCAN_INTERVAL);
}
function stopContinuousScanning() {
if (issueScanIntervalId) {
clearInterval(issueScanIntervalId);
issueScanIntervalId = null;
logDebug("Stopped continuous issue scanning.");
}
isScanningActive = false;
}
// --- Script 3 Function ---
function checkUrlAndTriggerClickForUndefined() {
const currentUrl = window.location.href;
if (currentUrl.endsWith('/undefined')) {
const toastContainer = document.querySelector(CONFIG.TOAST_CONTAINER_SELECTOR);
if (toastContainer) {
const closeButton = toastContainer.querySelector(CONFIG.TOAST_CLOSE_BUTTON_SELECTOR);
if (closeButton && isVisible(closeButton)) {
logDebug('URL ends with /undefined. Found visible toast close button. Clicking...');
closeButton.click();
}
}
}
}
// --- Event Listeners & Observers ---
document.addEventListener('keydown', function (event) {
if (event.key === 'F7') {
event.preventDefault();
logDebug("F7 pressed: Triggering VERIFY1 button click");
const verify1Button = document.querySelector(CONFIG.VERIFY1_BUTTON_SELECTOR);
if (verify1Button) verify1Button.click();
else logDebug("VERIFY1 button not found for F7.");
} else if (event.key === 'F8') {
event.preventDefault();
logDebug("F8 pressed: Triggering VERIFY2 button click");
const verify2Button = document.querySelector(CONFIG.VERIFY2_BUTTON_SELECTOR);
if (verify2Button) verify2Button.click();
else logDebug("VERIFY2 button not found for F8.");
}
});
const toastObserver = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE && node.matches && node.matches(CONFIG.SUCCESS_TOAST_SELECTOR)) {
logDebug('Success toast added. Adding click listener for back navigation.');
node.addEventListener('click', () => {
logDebug('Success toast clicked.');
if (verify1Clicked && verify1Toggle) {
logDebug('Verify1 was clicked and toggle is on. Going back.');
window.history.back();
} else if (verify2Clicked && verify2Toggle) {
logDebug('Verify2 was clicked and toggle is on. Going back.');
window.history.back();
}
verify1Clicked = false;
verify2Clicked = false;
});
}
});
});
});
const mainObserver = new MutationObserver(() => {
if (isCorrectPage()) {
addButtons();
} else {
const buttonDiv = document.getElementById('custom-script-buttons');
if (buttonDiv) {
logDebug("Leaving edit page, removing custom buttons.");
buttonDiv.remove();
}
}
const resultsGridExists = document.querySelector(CONFIG.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG.CRITICAL_FLAG_SELECTOR);
if (resultsGridExists) {
if (!isScanningActive) {
startContinuousScanning();
}
} else {
if (isScanningActive) {
stopContinuousScanning();
}
}
});
// --- Initialization ---
try {
logDebug("KAAUH Lab Enhancement Suite Initializing (v5.6.3)...");
loadFontAwesome();
setInterval(checkUrlAndTriggerClickForUndefined, CONFIG.UNDEFINED_URL_CHECK_INTERVAL);
logDebug(`Started URL check interval (${CONFIG.UNDEFINED_URL_CHECK_INTERVAL}ms) for /undefined toasts.`);
mainObserver.observe(document.body, { childList: true, subtree: true });
logDebug("Started main MutationObserver.");
toastObserver.observe(document.body, { childList: true, subtree: true });
logDebug("Started toast MutationObserver for back-navigation.");
window.addEventListener('load', () => {
logDebug("Page fully loaded. Performing initial checks.");
if (isCorrectPage()) { addButtons(); }
const resultsGridExists = document.querySelector(CONFIG.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG.CRITICAL_FLAG_SELECTOR);
if (resultsGridExists && !isScanningActive) {
startContinuousScanning();
}
});
logDebug("Initialization complete.");
} catch (error) {
console.error("[Lab Suite] Critical error during initialization:", error);
showModal("A critical error occurred in the Lab Enhancement Suite. Please check the browser console (F12) for details.");
}
})();