(() => { const qrInput = document.getElementById("qr-input"); const statusCard = document.getElementById("status-card"); const statusMiniIcon = document.getElementById("status-mini-icon"); const statusMiniHeadline = document.getElementById("status-mini-headline"); const statusMiniMessage = document.getElementById("status-mini-message"); const lastUpdatedTime = document.getElementById("last-updated-time"); const historySuccessBody = document.getElementById("history-success-body"); const historySuccessEmpty = document.getElementById("history-success-empty"); const historyRejectedBody = document.getElementById("history-rejected-body"); const historyRejectedEmpty = document.getElementById("history-rejected-empty"); const scanHistoryDialog = document.getElementById("scan-history-dialog"); const btnOpenScanHistory = document.getElementById("btn-open-scan-history"); const btnCloseScanHistory = document.getElementById("scan-history-dialog-close"); const scanHistoryFilter = document.getElementById("scan-history-filter"); const scanHistoryModalThead = document.getElementById("scan-history-modal-thead"); const scanHistoryModalTbody = document.getElementById("scan-history-modal-tbody"); const availableCartonsCountEl = document.getElementById("available-cartons-count"); const details = { model: document.getElementById("detail-model"), unique: document.getElementById("detail-unique"), exists: document.getElementById("detail-exists"), marked: document.getElementById("detail-marked"), time: document.getElementById("detail-time") }; const antiForgeryToken = document.querySelector('input[name="__RequestVerificationToken"]')?.value; const failureAlarm = new Audio("/resources/Sound/alert.wav"); failureAlarm.preload = "auto"; let isSubmitting = false; let autoSubmitTimer = null; let incompleteIdleTimer = null; /** Last time a non-paste text insertion was allowed (beforeinput insertText path). */ let lastBurstCharAt = 0; /** True while paste handler is applying clipboard text (avoids timing false positives). */ let fromPasteHandler = false; const maxInterKeyMs = 120; const autoSubmitIdleMs = 72; const incompleteNoSemicolonIdleMs = 420; const manualEntryMessage = "Manual entry is not allowed. Please scan or paste."; const completeQrPattern = /^[^;\r\n]+;[^;\r\n]+$/; const dashboardSummaryPollMs = 3000; function clearAutoSubmitTimer() { if (autoSubmitTimer !== null) { clearTimeout(autoSubmitTimer); autoSubmitTimer = null; } } function clearIncompleteIdleTimer() { if (incompleteIdleTimer !== null) { clearTimeout(incompleteIdleTimer); incompleteIdleTimer = null; } } function resetBurstTimer() { lastBurstCharAt = 0; } function resetTypingHeuristics() { resetBurstTimer(); clearIncompleteIdleTimer(); } function normalizeQrValue(value) { return (value ?? "").trim().replace(/\r?\n/g, ""); } function scheduleAutoSubmit() { clearAutoSubmitTimer(); autoSubmitTimer = window.setTimeout(() => { autoSubmitTimer = null; const v = normalizeQrValue(qrInput.value); if (!completeQrPattern.test(v) || isSubmitting || fromPasteHandler) { return; } void submitScan(v, "Scanner"); }, autoSubmitIdleMs); } function scheduleIncompleteIdleCheck() { clearIncompleteIdleTimer(); incompleteIdleTimer = window.setTimeout(() => { incompleteIdleTimer = null; if (isSubmitting || fromPasteHandler) { return; } const v = normalizeQrValue(qrInput.value); if (!v || completeQrPattern.test(v)) { return; } // Typing without a ';' (or abandoned partial) — not a scanner/paste flow. if (!v.includes(";")) { rejectManualTypingImmediate(v); } }, incompleteNoSemicolonIdleMs); } function rejectManualTypingImmediate(rawForServer) { clearAutoSubmitTimer(); clearIncompleteIdleTimer(); const toLog = normalizeQrValue(rawForServer ?? qrInput.value); qrInput.value = ""; resetBurstTimer(); setRejectedState(manualEntryMessage, null); resetDetails(); triggerFailureAlarm(); void persistManualRejection(toLog); } async function persistManualRejection(qrValue) { try { const response = await fetch("/api/verification/scan", { method: "POST", headers: { "Content-Type": "application/json", "RequestVerificationToken": antiForgeryToken ?? "" }, body: JSON.stringify({ qrValue, entryMethod: "Manual" }) }); if (!response.ok) { return; } const payload = await response.json(); renderSuccessfulHistory(payload.recentSuccessfulScans ?? []); renderRejectedHistory(payload.recentRejectedScans ?? []); updateAvailableCartonsCount(resolveAvailableCartons(payload)); } catch { // Ignore — UI already shows manual rejection. } } function isInsertFromKeyboard(beforeInputEvent) { const t = beforeInputEvent.inputType ?? ""; return t === "insertText" || t === "insertCompositionText"; } qrInput.addEventListener("beforeinput", event => { if (fromPasteHandler) { return; } if (event.inputType === "insertFromPaste") { return; } if (event.inputType?.startsWith("delete")) { return; } if (!isInsertFromKeyboard(event)) { return; } const now = Date.now(); if (lastBurstCharAt > 0 && now - lastBurstCharAt > maxInterKeyMs) { event.preventDefault(); const bufferBeforeBlockedChar = normalizeQrValue(qrInput.value); rejectManualTypingImmediate(bufferBeforeBlockedChar); return; } lastBurstCharAt = now; }); qrInput.addEventListener("keydown", event => { if (event.key !== "Enter") { return; } event.preventDefault(); clearAutoSubmitTimer(); clearIncompleteIdleTimer(); const v = normalizeQrValue(qrInput.value); if (!v || isSubmitting) { return; } void submitScan(v, "Scanner"); }); qrInput.addEventListener("paste", event => { const pastedValue = event.clipboardData?.getData("text"); if (!pastedValue) { return; } event.preventDefault(); fromPasteHandler = true; clearAutoSubmitTimer(); clearIncompleteIdleTimer(); resetBurstTimer(); const text = normalizeQrValue(pastedValue); qrInput.value = text; if (!text || isSubmitting) { fromPasteHandler = false; return; } void submitScan(text, "Paste").finally(() => { fromPasteHandler = false; }); }); qrInput.addEventListener("input", () => { if (isSubmitting || fromPasteHandler) { return; } const v = normalizeQrValue(qrInput.value); if (!v) { resetBurstTimer(); clearIncompleteIdleTimer(); clearAutoSubmitTimer(); return; } scheduleAutoSubmit(); scheduleIncompleteIdleCheck(); }); qrInput.addEventListener("keyup", event => { if (isSubmitting || fromPasteHandler) { return; } if (event.key === "Enter") { return; } const v = normalizeQrValue(qrInput.value); if (completeQrPattern.test(v)) { scheduleAutoSubmit(); } }); qrInput.addEventListener("focus", () => { if (!qrInput.value) { resetTypingHeuristics(); } }); async function submitScan(qrValue, entryMethod) { if (!qrValue || isSubmitting) { return; } isSubmitting = true; clearAutoSubmitTimer(); clearIncompleteIdleTimer(); resetBurstTimer(); try { const response = await fetch("/api/verification/scan", { method: "POST", headers: { "Content-Type": "application/json", "RequestVerificationToken": antiForgeryToken ?? "" }, body: JSON.stringify({ qrValue, entryMethod }) }); const payload = await response.json(); if (!response.ok) { const fallbackMessage = "Verification service is unavailable."; setRejectedState(payload.message ?? fallbackMessage, null); resetDetails(); triggerFailureAlarm(); return; } const result = payload.result; renderDetails(result); renderStatus(result); renderSuccessfulHistory(payload.recentSuccessfulScans ?? []); renderRejectedHistory(payload.recentRejectedScans ?? []); updateAvailableCartonsCount(resolveAvailableCartons(payload)); } catch { setRejectedState("Unexpected communication error.", null); triggerFailureAlarm(); resetDetails(); } finally { qrInput.value = ""; resetTypingHeuristics(); qrInput.focus(); isSubmitting = false; } } function renderStatus(result) { if (result.isSuccessful) { setStatusClasses("status-success"); statusMiniIcon.textContent = "\u2713"; statusMiniHeadline.textContent = "VERIFIED"; statusMiniMessage.textContent = result.message; lastUpdatedTime.textContent = formatDisplayTime(result.processedAtUtc); return; } setRejectedState(result.message, result.processedAtUtc); triggerFailureAlarm(); } function setRejectedState(message, processedAtUtc) { setStatusClasses("status-failed"); statusMiniIcon.textContent = "!"; statusMiniHeadline.textContent = "REJECTED"; statusMiniMessage.textContent = message; lastUpdatedTime.textContent = processedAtUtc ? formatDisplayTime(processedAtUtc) : "-"; } function setStatusClasses(statusClass) { statusCard.className = `panel panel-status ${statusClass}`; } function renderDetails(result) { details.model.textContent = result.modelNumber || "-"; details.unique.textContent = result.uniqueNumber || "-"; details.exists.textContent = result.existsInSystem ? "YES" : "NO"; details.marked.textContent = result.shipmentMarked ? "TRUE" : "FALSE"; details.time.textContent = formatDisplayTime(result.processedAtUtc); } function resetDetails() { details.model.textContent = "-"; details.unique.textContent = "-"; details.exists.textContent = "-"; details.marked.textContent = "-"; details.time.textContent = "-"; } function renderSuccessfulHistory(records) { historySuccessBody.innerHTML = ""; if (records.length === 0) { historySuccessEmpty.classList.remove("is-hidden"); return; } historySuccessEmpty.classList.add("is-hidden"); records.forEach((record, index) => { const row = document.createElement("tr"); row.innerHTML = `