- // ==UserScript==
- // @name Alerts to User (Dynamic Scanning with Notification Persistence)
- // @namespace Violentmonkey Scripts
- // @match *://his.kaauh.org/lab/*
- // @grant none
- // @version 3.5
- // @author Hamad AlShegifi
- // @description Continuously scans for specific events and ensures the user gets notifications again if they return to the same event.
- // @license MIT
- // ==/UserScript==
-
- (function () {
- 'use strict';
-
- // Configuration Options
- const CONFIG = {
- SCAN_INTERVAL: 100, // Interval for scanning (in ms)
- FLASH_COLOR: "pink", // Flashing color for rows with ">"
- FLASH_INTERVAL: 500, // Interval for flashing effect (in ms)
- MODAL_TIMEOUT: 10000, // Timeout for modal auto-dismissal (in ms)
- DEBUG_MODE: true, // Enable/disable debug logs
- };
-
- // Debug Logging
- function logDebug(message) {
- if (CONFIG.DEBUG_MODE) console.log(`[Alerts to User] ${message}`);
- }
-
- // Function to create a custom modal dialog
- function showModal(message, onConfirm) {
- // Create overlay
- const overlay = document.createElement("div");
- 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 = "999";
- document.body.appendChild(overlay);
-
- // Create modal
- const modal = document.createElement("div");
- 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 = "1000";
- modal.style.borderRadius = "15px";
- modal.style.textAlign = "center";
- modal.style.transition = "transform 0.3s ease, opacity 0.3s ease";
- modal.style.opacity = "0";
-
- // Add heading
- 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);
-
- // Add message
- const content = document.createElement("p");
- 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);
-
- // Add input field for user comment (if onConfirm is provided)
- if (onConfirm) {
- const input = document.createElement("input");
- input.type = "text";
- input.placeholder = "Enter your comment";
- input.style.padding = "10px";
- input.style.width = "100%";
- input.style.marginBottom = "20px";
- input.style.border = "1px solid #ccc";
- input.style.borderRadius = "5px";
- input.style.fontSize = "16px";
- modal.appendChild(input);
-
- // Add Confirm button
- const confirmButton = createButton("Confirm", "#ff4081", () => {
- const comment = input.value.trim();
- if (comment) {
- onConfirm(comment);
- closeModal(modal, overlay);
- } else {
- alert("Please enter a comment!");
- }
- });
- modal.appendChild(confirmButton);
- } else {
- // Add OK button (for alerts without input)
- const okButton = createButton("OK", "#ff4081", () => {
- closeModal(modal, overlay);
- });
- modal.appendChild(okButton);
- }
-
- // Append modal to the body
- document.body.appendChild(modal);
-
- // Animate modal appearance
- setTimeout(() => {
- modal.style.transform = "translate(-50%, -50%) scale(1)";
- modal.style.opacity = "1";
- }, 10);
-
- // Auto-dismiss modal after timeout
- setTimeout(() => {
- closeModal(modal, overlay);
- }, CONFIG.MODAL_TIMEOUT);
- }
-
- // Helper function to create styled buttons
- function createButton(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";
-
- // Hover effects
- 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)";
- });
-
- // Click handler
- button.addEventListener("click", onClick);
-
- return button;
- }
-
- // Helper function to darken a color
- function darkenColor(color, percent) {
- const num = parseInt(color.slice(1), 16);
- const amt = Math.round(2.55 * percent);
- const R = (num >> 16) - amt;
- const G = ((num >> 8) & 0x00ff) - amt;
- const B = (num & 0x0000ff) - amt;
- return `#${(0x1000000 + (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
- (G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
- (B < 255 ? (B < 1 ? 0 : B) : 255))
- .toString(16)
- .slice(1)}`;
- }
-
- // Function to close a modal
- function closeModal(modal, overlay) {
- modal.style.transform = "translate(-50%, -50%) scale(0.5)";
- modal.style.opacity = "0";
- overlay.style.opacity = "0";
- setTimeout(() => {
- document.body.removeChild(modal);
- document.body.removeChild(overlay);
- }, 300); // Match transition duration
- }
-
- // Function to apply flashing effect to rows
- function applyFlashingEffect(rows) {
- rows.forEach(row => {
- row.style.transition = "background-color 0.5s ease";
- let isPink = false;
-
- const intervalId = setInterval(() => {
- isPink = !isPink;
- row.style.backgroundColor = isPink ? CONFIG.FLASH_COLOR : "transparent";
- }, CONFIG.FLASH_INTERVAL);
-
- // Stop flashing if the row is removed from the DOM
- const observer = new MutationObserver(() => {
- if (!document.contains(row)) {
- clearInterval(intervalId);
- observer.disconnect();
- }
- });
- observer.observe(document.body, { childList: true, subtree: true });
- });
- }
-
- // Function to replace "NO RESULT" or "NO-XRESULT" with user comment
- function replacePlaceholderWithComment(divElement, comment) {
- if (divElement) {
- divElement.innerText = comment;
- }
- }
-
- // Function to check for issues in the data
- function checkForIssues() {
- const resultDivs = document.querySelectorAll(
- 'div[role="gridcell"][col-id="TestResult"] app-result-value-render div'
- );
- const criticalDivs = document.querySelectorAll(
- 'div[role="gridcell"][col-id="LTFlag"] app-ref-high-low div span.critial-alret-indication'
- );
-
- let hasMatch = false;
-
- // Check for ">", "NO RESULT", and "NO-XRESULT"
- resultDivs.forEach(div => {
- const text = div.textContent.trim().toLowerCase();
-
- if (text.includes(">") && !hasAlreadyNotified("greaterThan")) {
- const row = div.closest("div[role='row']");
- if (row) {
- applyFlashingEffect([row]);
- showModal("Dilution is required for this sample (> detected)!");
- setNotificationFlag("greaterThan");
- hasMatch = true;
- }
- }
-
- if ((text === "no result" || text === "no-xresult") && !hasAlreadyNotified(text)) {
- showModal(`Please enter a comment to replace '${text.toUpperCase()}':`, (comment) => {
- replacePlaceholderWithComment(div, comment);
- setNotificationFlag(text);
- });
- hasMatch = true;
- }
- });
-
- // Check for critical results (CL/CH)
- criticalDivs.forEach(div => {
- const text = div.textContent.trim();
- if (text === "CL" && !hasAlreadyNotified("CL")) {
- showModal("CRITICAL LOW RESULT DETECTED !!");
- setNotificationFlag("CL");
- hasMatch = true;
- } else if (text === "CH" && !hasAlreadyNotified("CH")) {
- showModal("CRITICAL HIGH RESULT DETECTED !!");
- setNotificationFlag("CH");
- hasMatch = true;
- }
- });
-
- return hasMatch;
- }
-
- // Function to check if a user has already been notified about a specific event
- function hasAlreadyNotified(eventKey) {
- return localStorage.getItem(eventKey) !== null;
- }
-
- // Function to set a notification flag in localStorage
- function setNotificationFlag(eventKey) {
- localStorage.setItem(eventKey, true);
- }
-
- // Continuous scanning function
- function startContinuousScanning() {
- logDebug("Starting continuous scanning...");
- const intervalId = setInterval(() => {
- const hasMatch = checkForIssues();
- if (hasMatch) {
- clearInterval(intervalId);
- logDebug("Match found. Stopping continuous scanning.");
- }
- }, CONFIG.SCAN_INTERVAL);
- }
-
- // Use MutationObserver to detect dynamic content loading
- function observeDOMChanges() {
- const targetNode = document.body;
- const config = { childList: true, subtree: true };
-
- const observer = new MutationObserver(() => {
- const resultDivs = document.querySelectorAll(
- 'div[role="gridcell"][col-id="TestResult"] app-result-value-render div'
- );
- if (resultDivs.length > 0) {
- logDebug("Relevant elements detected. Starting continuous scanning...");
- startContinuousScanning();
- observer.disconnect(); // Stop observing once elements are found
- }
- });
-
- observer.observe(targetNode, config);
- }
-
- // Main execution
- try {
- window.addEventListener('load', () => {
- logDebug("Page fully loaded. Observing DOM changes...");
- observeDOMChanges();
- });
- } catch (error) {
- logDebug(`An error occurred: ${error.message}`);
- }
- })();