Marks "All Clear" OR "Action Required" as the complete status.
// ==UserScript==
// @name Pending Barcodes (Dashboard Box - Optimized Edition)
// @namespace http://tampermonkey.net/
// @version 5.3
// @description Marks "All Clear" OR "Action Required" as the complete status.
// @author Hamad AlShegifi
// @match *://his.kaauh.org/lab/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// --- Configuration & Constants ---
const TABLE_BODY_SELECTOR = 'tbody[formarrayname="TubeTypeList"]';
const BARCODE_DISPLAY_SELECTOR = '#barcode-display-box';
const STORAGE_KEY = 'collectedBarcodes_storage_v2';
const IN_PAGE_TABLE_ID = 'barcode-inpage-container';
const INJECTION_POINT_SELECTOR = '.row.labordertab';
const GRID_CONTAINER_SELECTOR = '.ag-center-cols-container';
const SIDEBAR_INJECTION_POINT_SELECTOR = '.sidebar-bottom csi-list-menu';
// NEW: Navbar selectors for sample counting
const NAVBAR_SELECTOR = 'ul.navbar-right';
const NOTIFICATION_BADGE_SELECTOR = '.counter.header_notification';
const DEBOUNCE_DELAY = 200;
const GRID_UPDATE_TIMEOUT = 2000;
const SLEEP_DURATION = 50;
const TIME_UPDATE_INTERVAL = 5000;
const CACHE_DURATION = 1000;
// --- State Flags & Cache ---
const collectedBarcodesThisSession = new Set();
let lastCheckedPatientBarcode = null;
let timeSinceInterval = null;
let sortState = { key: 'timestamp', direction: 'desc' };
let sidebarButtonInjected = false;
let statsBoxInjected = false;
let mainObserver = null;
let observerDebounceTimer = null;
// --- Storage Cache ---
let cachedBarcodes = null;
let cacheTimestamp = 0;
async function getCachedBarcodes() {
const now = Date.now();
if (!cachedBarcodes || (now - cacheTimestamp) > CACHE_DURATION) {
cachedBarcodes = await GM_getValue(STORAGE_KEY, []);
cacheTimestamp = now;
}
return cachedBarcodes;
}
async function saveBarcodesCached(barcodes) {
await GM_setValue(STORAGE_KEY, barcodes);
cachedBarcodes = barcodes;
cacheTimestamp = Date.now();
}
// --- Extract Sample Counts ---
function extractSampleCounts() {
const barcodes = cachedBarcodes || [];
const totalSamples = barcodes.length;
const completedSamples = barcodes.filter(b => b.found).length;
const pendingSamples = totalSamples - completedSamples;
return { totalSamples, completedSamples, pendingSamples };
}
// --- Inject Statistics Box in Navbar ---
function injectStatsBox() {
if (statsBoxInjected) return;
const navbar = document.querySelector(NAVBAR_SELECTOR);
if (!navbar) return;
const statsContainer = document.createElement('li');
statsContainer.className = 'nav-item';
statsContainer.id = 'bc-stats-container';
statsContainer.innerHTML = `
<div class="bc-stats-box">
<div class="bc-stat-item">
<span class="bc-stat-label">Total:</span>
<span id="bc-total-count" class="bc-stat-value">0</span>
</div>
<div class="bc-stat-item">
<span class="bc-stat-label">Completed:</span>
<span id="bc-completed-count" class="bc-stat-value bc-completed">0</span>
</div>
<div class="bc-stat-item">
<span class="bc-stat-label">Pending:</span>
<span id="bc-pending-count" class="bc-stat-value bc-pending">0</span>
</div>
</div>
`;
// Insert before the clock element
const clockElement = navbar.querySelector('.clock-nav');
if (clockElement) {
navbar.insertBefore(statsContainer, clockElement);
} else {
navbar.insertBefore(statsContainer, navbar.firstChild);
}
statsBoxInjected = true;
console.log("Barcode Collector: Stats box injected into navbar.");
}
// --- Update Statistics Display ---
function updateStatsDisplay(stats) {
const totalEl = document.getElementById('bc-total-count');
const completedEl = document.getElementById('bc-completed-count');
const pendingEl = document.getElementById('bc-pending-count');
if (totalEl) totalEl.textContent = stats.totalSamples;
if (completedEl) completedEl.textContent = stats.completedSamples;
if (pendingEl) pendingEl.textContent = stats.pendingSamples;
}
// --- Check for post-redirect filter ---
function checkAndApplyPostRedirectFilter() {
const barcodeToFilter = sessionStorage.getItem('barcodeToFilterAfterRedirect');
if (!barcodeToFilter) return;
// We have a barcode. Clear the flag immediately.
sessionStorage.removeItem('barcodeToFilterAfterRedirect');
// Are we on the right page?
const targetPage = 'https://his.kaauh.org/lab/#/lab-orders/lab-test-analyzer';
if (window.location.href === targetPage) {
setTimeout(async () => {
console.log(`Barcode Collector: Applying post-redirect filter for ${barcodeToFilter}`);
await enterBarcodeInFilter(barcodeToFilter);
}, 750);
} else {
console.warn(`Barcode Collector: Post-redirect filter for ${barcodeToFilter} ignored, user is not on the correct page.`);
}
}
// --- Main Logic ---
function initialize() {
mainObserver = new MutationObserver((mutations, obs) => {
if (observerDebounceTimer) clearTimeout(observerDebounceTimer);
observerDebounceTimer = setTimeout(observerCallback, DEBOUNCE_DELAY);
});
// Added characterData: true to watch for text changes (e.g., status updates)
mainObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
window.addEventListener('beforeunload', cleanup);
// Click outside to close panel
window.addEventListener('click', (event) => {
const panel = document.getElementById(IN_PAGE_TABLE_ID);
const toggleButton = document.getElementById('pending-barcodes-toggle');
if (!panel) return;
if (panel.style.display === 'flex') {
const isClickInsidePanel = panel.contains(event.target);
const isClickOnButton = toggleButton ? toggleButton.contains(event.target) : false;
if (!isClickInsidePanel && !isClickOnButton) {
panel.style.display = 'none';
}
}
});
}
async function observerCallback() {
checkAndApplyPostRedirectFilter();
// Inject Stats Box in Navbar
injectStatsBox();
// Sidebar Button
const sidebarMenu = document.querySelector(SIDEBAR_INJECTION_POINT_SELECTOR);
if (sidebarMenu && !sidebarButtonInjected) {
injectSidebarButton(sidebarMenu);
}
// Update/Insert Table
await updateOrInsertBarcodeTable();
// Patient Barcode Logic - FIXED
checkAndMarkPatientBarcode();
// Collect New Barcodes
const allBarcodeRows = document.querySelectorAll(`${TABLE_BODY_SELECTOR} tr`);
if (allBarcodeRows.length > 0) {
for (const row of allBarcodeRows) {
const barcodeInput = row.querySelector('input[formcontrolname="Barcode"]');
const workbenchInput = row.querySelector('input[formcontrolname="TestSection"]');
if (barcodeInput && barcodeInput.value) {
const barcode = barcodeInput.value.trim();
const workbench = workbenchInput && workbenchInput.value ? workbenchInput.value.trim() : 'N/A';
await saveBarcode(barcode, workbench);
}
}
}
// Update statistics display
const stats = extractSampleCounts();
updateStatsDisplay(stats);
}
function cleanup() {
if (timeSinceInterval) {
clearInterval(timeSinceInterval);
timeSinceInterval = null;
}
if (mainObserver) {
mainObserver.disconnect();
mainObserver = null;
}
console.log("Barcode Collector: Cleaned up resources.");
}
function injectSidebarButton(sidebarMenu) {
if (document.getElementById('pending-barcodes-toggle')) return;
const newMenuItemContainer = document.createElement('div');
newMenuItemContainer.innerHTML = `
<div>
<csi-main-menu>
<a id="pending-barcodes-toggle" title="Toggle Pending Barcodes Panel">
<span class="icon-holder csi-menu-icon">
<i class="nova-icon-scan"></i>
</span>
<div class="csi-menu-text-wrapper">
<span class="csi-menu-text sidemenu-title" title="Pending Barcodes">Pending</span>
<span id="pending-count-badge" class="bc-sidebar-badge">0</span>
</div>
</a>
</csi-main-menu>
</div>
`;
sidebarMenu.appendChild(newMenuItemContainer);
document.getElementById('pending-barcodes-toggle').addEventListener('click', (e) => {
e.preventDefault();
const panel = document.getElementById(IN_PAGE_TABLE_ID);
if (panel) {
panel.style.display = panel.style.display === 'flex' ? 'none' : 'flex';
}
});
sidebarButtonInjected = true;
console.log("Barcode Collector: Sidebar button injected.");
}
async function updatePendingCounts(count) {
const badge = document.getElementById('pending-count-badge');
if (badge) {
badge.textContent = count;
badge.classList.toggle('visible', count > 0);
}
}
async function saveBarcode(barcode, workbench) {
if (collectedBarcodesThisSession.has(barcode)) return;
try {
let barcodes = await getCachedBarcodes();
if (barcodes.some(entry => entry.barcode === barcode)) {
collectedBarcodesThisSession.add(barcode);
return;
}
const newEntry = {
count: barcodes.length + 1,
barcode: barcode,
workbench: workbench,
timestamp: new Date().toISOString(),
found: false
};
barcodes.push(newEntry);
await saveBarcodesCached(barcodes);
collectedBarcodesThisSession.add(barcode);
await updateOrInsertBarcodeTable();
} catch (error) {
console.error(`Barcode Collector: Error saving barcode ${barcode}:`, error);
}
}
function formatTimeSince(isoTimestamp) {
const date = new Date(isoTimestamp);
const now = new Date();
const totalMinutes = Math.floor((now - date) / (1000 * 60));
if (totalMinutes < 1) return "00:00 ago";
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')} ago`;
}
async function markBarcodeAsFoundAndUpdateStorage(barcodeToMark) {
let barcodes = await getCachedBarcodes();
const entry = barcodes.find(b => b.barcode === barcodeToMark);
if (entry && !entry.found) {
entry.found = true;
await saveBarcodesCached(barcodes);
await updateOrInsertBarcodeTable();
}
}
// --- THIS FUNCTION IS MODIFIED ---
async function checkAndMarkPatientBarcode() {
const patientBarcodeBox = document.querySelector(BARCODE_DISPLAY_SELECTOR);
const barcodeOnPage = patientBarcodeBox ?
Array.from(patientBarcodeBox.querySelectorAll('div'))
.find(div => div.textContent.includes('Sample Barcode:'))?.nextElementSibling?.textContent.trim()
: null;
if (barcodeOnPage && barcodeOnPage !== lastCheckedPatientBarcode) {
lastCheckedPatientBarcode = barcodeOnPage;
}
if (barcodeOnPage) {
const summaryContainer = document.querySelector('#test-summary-container');
if (summaryContainer) {
// The status text is in the H2 tag
const statusHeader = summaryContainer.querySelector('h2');
let statusText = '';
if (statusHeader) {
// Normalize whitespace just in case
statusText = statusHeader.textContent.replace(/\s+/g, ' ').trim();
}
// --- THIS IS THE CHANGED LINE ---
// Check if the status is one of the two "complete" messages
const isCompleteStatus = (statusText === 'All Clear: No Pending Actions' || statusText === 'ACTION REQUIRED! : Verification Required');
// --- END OF CHANGE ---
if (isCompleteStatus) {
console.log(`Barcode Collector: Marking ${barcodeOnPage} as completed (Status: ${statusText})`);
await markBarcodeAsFoundAndUpdateStorage(barcodeOnPage);
} else {
// Log the status we actually found
console.log(`Barcode Collector: Barcode ${barcodeOnPage} not marked (status: [${statusText ? statusText : 'unknown'}])`);
}
}
} else {
lastCheckedPatientBarcode = null;
}
}
// --- END OF MODIFIED FUNCTION ---
function findFloatingFilterInputByHeader(headerText) {
const headerViewport = document.querySelector('.ag-header-viewport');
if (!headerViewport) return null;
const allTitleCells = Array.from(headerViewport.querySelectorAll('.ag-header-row[aria-rowindex="1"] .ag-header-cell'));
let targetColumnIndex = allTitleCells.findIndex(cell => {
const cellTextElement = cell.querySelector('.ag-header-cell-text');
return cellTextElement && cellTextElement.textContent.trim().toLowerCase() === headerText.toLowerCase();
});
if (targetColumnIndex === -1) return null;
const filterRow = headerViewport.querySelector('.ag-header-row[aria-rowindex="2"]');
if (!filterRow) return null;
const filterCell = filterRow.children[targetColumnIndex];
if (!filterCell) return null;
return filterCell.querySelector('input.ag-floating-filter-input');
}
function waitForGridUpdateAndClick() {
return new Promise((resolve, reject) => {
const gridContainer = document.querySelector(GRID_CONTAINER_SELECTOR);
if (!gridContainer) return reject("AG-Grid container not found.");
const timeout = setTimeout(() => { observer.disconnect(); reject("Timeout: AG-Grid did not update."); }, GRID_UPDATE_TIMEOUT);
const observer = new MutationObserver((mutations, obs) => {
const firstRow = gridContainer.querySelector('.ag-row[row-index="0"]');
if (firstRow) {
firstRow.click();
clearTimeout(timeout);
obs.disconnect();
resolve();
}
});
observer.observe(gridContainer, { childList: true, subtree: true });
});
}
function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
async function enterBarcodeInFilter(barcode) {
const targetInput = findFloatingFilterInputByHeader('Barcode');
if (!targetInput) { console.error('Barcode Collector: Could not find "Barcode" filter input.'); return; }
try {
targetInput.focus(); await sleep(SLEEP_DURATION);
targetInput.value = barcode;
targetInput.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); await sleep(100);
targetInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
targetInput.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
await waitForGridUpdateAndClick();
} catch (error) {
console.error("Barcode Collector: Error while filtering/clicking.", error);
} finally {
if (targetInput) targetInput.blur();
}
}
function handleSortClick() {
sortState.direction = sortState.direction === 'desc' ? 'asc' : 'desc';
updateOrInsertBarcodeTable();
}
async function updateOrInsertBarcodeTable() {
try {
const barcodes = await getCachedBarcodes();
const pendingCount = barcodes.filter(b => !b.found).length;
await updatePendingCounts(pendingCount);
// Update stats display
const stats = extractSampleCounts();
updateStatsDisplay(stats);
let container = document.getElementById(IN_PAGE_TABLE_ID);
if (sortState.key === 'timestamp') {
barcodes.sort((a, b) => {
const dateA = new Date(a.timestamp);
const dateB = new Date(b.timestamp);
return sortState.direction === 'asc' ? dateA - dateB : dateB - dateA;
});
}
const uniqueWorkbenches = ['All', ...new Set(barcodes.map(b => b.workbench).filter(Boolean))];
if (!container) {
container = document.createElement('div');
container.id = IN_PAGE_TABLE_ID;
container.style.display = 'none';
document.body.appendChild(container);
container.innerHTML = `
<div class="bc-table-header">
<div class="bc-header-top-row">
<h2>Pending</h2>
<span id="close-bc-panel-btn">×</span>
</div>
<div class="bc-table-controls">
<div class="bc-filter-container"><label for="workbench-filter">Workbench:</label><select id="workbench-filter"></select></div>
<div class="bc-button-group">
<button id="delete-completed-btn" class="bc-btn bc-btn-completed">Clear Completed</button>
<button id="clear-all-btn" class="bc-btn bc-btn-clear-all">Clear All</button>
</div>
</div>
</div>
<div class="bc-table-body">
<table>
<thead>
<tr>
<th>#</th><th>Barcode</th><th>Workbench</th>
<th id="sort-by-time-header" class="sortable-header">Added <span id="sort-indicator"></span></th>
<th>Pending</th><th>Actions</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
`;
container.querySelector('#clear-all-btn').addEventListener('click', async () => {
if (confirm("Are you sure you want to delete ALL pending barcodes? This cannot be undone.")) {
await saveBarcodesCached([]);
await updateOrInsertBarcodeTable();
}
});
container.querySelector('#delete-completed-btn').addEventListener('click', deleteCompletedBarcodes);
container.querySelector('#workbench-filter').addEventListener('change', updateOrInsertBarcodeTable);
container.querySelector('#sort-by-time-header').addEventListener('click', handleSortClick);
container.querySelector('#close-bc-panel-btn').addEventListener('click', () => {
container.style.display = 'none';
});
}
const sortIndicator = container.querySelector('#sort-indicator');
if (sortIndicator) {
sortIndicator.textContent = sortState.direction === 'asc' ? '▲' : '▼';
}
const filterDropdown = container.querySelector('#workbench-filter');
const currentFilterValue = filterDropdown.value;
filterDropdown.innerHTML = uniqueWorkbenches.map(wb => `<option value="${wb}">${wb}</option>`).join('');
if (uniqueWorkbenches.includes(currentFilterValue)) filterDropdown.value = currentFilterValue;
const selectedWorkbench = filterDropdown.value;
const filteredBarcodes = selectedWorkbench === 'All' ? barcodes : barcodes.filter(b => b.workbench === selectedWorkbench);
let tableRows = filteredBarcodes.map(entry => `
<tr data-barcode-row="${entry.barcode}" class="${entry.found ? 'barcode-found' : ''}">
<td>${entry.count}</td><td>${entry.barcode}</td><td>${entry.workbench || 'N/A'}</td>
<td>${new Date(entry.timestamp).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true })}</td>
<td data-timestamp="${entry.timestamp}">${formatTimeSince(entry.timestamp)}</td>
<td class="action-cell-bc"><span class="delete-barcode-btn" data-barcode="${entry.barcode}" title="Delete">×</span></td>
</tr>
`).join('');
if (filteredBarcodes.length === 0) {
tableRows = '<tr><td colspan="6">No pending barcodes match the filter.</td></tr>';
}
const tableBody = container.querySelector('tbody');
if (tableBody.innerHTML !== tableRows) tableBody.innerHTML = tableRows;
tableBody.removeEventListener('click', handleTableClick);
tableBody.addEventListener('click', handleTableClick);
if (timeSinceInterval) clearInterval(timeSinceInterval);
timeSinceInterval = setInterval(() => {
container.querySelectorAll('td[data-timestamp]').forEach(cell => {
cell.textContent = formatTimeSince(cell.dataset.timestamp);
});
}, TIME_UPDATE_INTERVAL);
} catch (error) {
console.error("Barcode Collector: Error updating table:", error);
}
}
async function handleTableClick(event) {
const row = event.target.closest('tr');
if (!row || !row.dataset.barcodeRow) return;
if (event.target.classList.contains('delete-barcode-btn')) {
await deleteBarcode(event.target.dataset.barcode);
} else {
const barcodeToFilter = row.dataset.barcodeRow;
const targetPage = 'https://his.kaauh.org/lab/#/lab-orders/lab-test-analyzer';
// Hide the panel
document.getElementById(IN_PAGE_TABLE_ID).style.display = 'none';
// Check if we are on the right page
if (window.location.href === targetPage) {
// We are already on the page, just filter
await enterBarcodeInFilter(barcodeToFilter);
} else {
// We are on the wrong page. Set storage and redirect.
console.log(`Barcode Collector: Wrong page. Storing ${barcodeToFilter} and redirecting...`);
sessionStorage.setItem('barcodeToFilterAfterRedirect', barcodeToFilter);
window.location.href = targetPage;
}
}
}
async function deleteCompletedBarcodes() {
if (confirm("Are you sure you want to delete all completed (green) barcodes?")) {
let barcodes = await getCachedBarcodes();
let updatedBarcodes = barcodes.filter(entry => !entry.found);
updatedBarcodes.forEach((entry, index) => { entry.count = index + 1; });
await saveBarcodesCached(updatedBarcodes);
await updateOrInsertBarcodeTable();
}
}
async function deleteBarcode(barcodeToDelete) {
let barcodes = await getCachedBarcodes();
let updatedBarcodes = barcodes.filter(entry => entry.barcode !== barcodeToDelete);
updatedBarcodes.forEach((entry, index) => { entry.count = index + 1; });
await saveBarcodesCached(updatedBarcodes);
await updateOrInsertBarcodeTable();
}
// --- Optimized CSS ---
GM_addStyle(`
:root {
--bc-primary-color: #ef5350;
--bc-primary-dark: #d32f2f;
--bc-secondary-color: #0288d1;
--bc-success-color: #4caf50;
}
/* Stats Box in Navbar */
#bc-stats-container {
display: flex !important;
align-items: center;
padding: 0 15px;
margin-right: auto;
}
.bc-stats-box {
display: flex;
gap: 15px;
align-items: center;
background-color: rgba(0, 0, 0, 0.05);
padding: 8px 15px;
border-radius: 6px;
font-size: 13px;
}
.bc-stat-item {
display: flex;
align-items: center;
gap: 5px;
}
.bc-stat-label {
color: #444;
font-weight: 500;
}
.bc-stat-value {
color: #111;
font-weight: bold;
font-size: 15px;
min-width: 25px;
text-align: center;
padding: 2px 8px;
border-radius: 4px;
background-color: rgba(0, 0, 0, 0.1);
}
.bc-stat-value.bc-completed {
background-color: var(--bc-success-color);
}
.bc-stat-value.bc-pending {
background-color: var(--bc-primary-color);
}
.bc-stat-value.bc-completed,
.bc-stat-value.bc-pending {
color: #fff !important;
}
#pending-barcodes-toggle {
cursor: pointer;
display: flex;
align-items: center;
}
#pending-barcodes-toggle .csi-menu-text-wrapper {
display: flex;
width: 100%;
align-items: center;
}
.bc-sidebar-badge {
background-color: var(--bc-primary-color);
color: white;
border-radius: 10px;
padding: 2px 6px;
font-size: 11px;
font-weight: bold;
margin-left: auto;
margin-right: 10px;
display: none;
line-height: 1;
}
.bc-sidebar-badge.visible {
display: inline-block;
}
#${IN_PAGE_TABLE_ID} {
position: fixed;
top: 55px;
left: 70px;
width: 500px;
height: 85vh;
z-index: 2000;
display: none;
margin: 0;
border: 1px solid #ccc;
border-radius: 8px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
background-color: #fff;
flex-direction: column;
}
.bc-table-header {
padding: 10px 16px;
background-color: #f7f7f7;
border-bottom: 1px solid #ccc;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.bc-header-top-row {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.bc-table-header h2 {
margin: 0;
font-size: 1.1em;
color: #333;
}
#close-bc-panel-btn {
font-size: 24px;
font-weight: bold;
color: #aaa;
cursor: pointer;
padding: 0 5px;
}
#close-bc-panel-btn:hover { color: #333; }
.bc-table-controls {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.bc-filter-container {
display: flex;
align-items: center;
gap: 8px;
}
.bc-filter-container label { font-weight: bold; font-size: 0.9em; }
#workbench-filter { padding: 4px; border-radius: 4px; border: 1px solid #ccc; }
.bc-button-group { display: flex; gap: 8px; flex-shrink: 0; }
.bc-btn {
border: none; padding: 6px 12px; border-radius: 5px; cursor: pointer;
font-weight: bold; font-size: 0.9em; color: white; transition: background-color 0.2s;
}
.bc-btn:hover { opacity: 0.9; }
.bc-btn-clear-all { background-color: var(--bc-primary-color); }
.bc-btn-clear-all:hover { background-color: var(--bc-primary-dark); }
.bc-btn-completed { background-color: var(--bc-secondary-color); }
.bc-btn-completed:hover { background-color: #0277bd; }
.bc-table-body {
padding: 8px; overflow-y: auto; flex-grow: 1; min-height: 0;
}
.bc-table-body table { width: 100%; border-collapse: collapse; }
.bc-table-body th, .bc-table-body td {
border: 1px solid #ddd; padding: 4px 8px; text-align: left; font-size: 0.9em;
}
.bc-table-body th { background-color: #f2f2f2; }
.bc-table-body .sortable-header { cursor: pointer; }
.bc-table-body .sortable-header:hover { background-color: #e0e0e0; }
#sort-indicator { font-size: 0.8em; margin-left: 4px; }
.bc-table-body tbody tr { cursor: pointer; }
.bc-table-body tbody tr:hover { background-color: #e8eaf6; }
.bc-table-body tbody tr.barcode-found { background-color: #a5d6a7 !important; color: #1b5e20; }
.bc-table-body tbody tr.barcode-found:hover { background-color: #81c784 !important; }
.action-cell-bc { text-align: center !important; }
.delete-barcode-btn {
cursor: pointer; font-weight: bold; font-size: 18px; color: var(--bc-primary-color);
padding: 0 4px; border-radius: 4px;
}
.delete-barcode-btn:hover { color: white; background-color: var(--bc-primary-dark); }
`);
// Boot
initialize();
})();