From 98cb42d2e547d79f0cf7f31f037b86432beaf0dc Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Thu, 14 May 2026 08:03:47 -0400 Subject: [PATCH] feat(fusion_clock): NFC kiosk on-screen debug overlay + clearer settings label --- fusion_clock/models/res_config_settings.py | 8 ++- .../static/src/js/fusion_clock_nfc_kiosk.js | 60 ++++++++++++++++--- .../views/res_config_settings_views.xml | 2 +- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/fusion_clock/models/res_config_settings.py b/fusion_clock/models/res_config_settings.py index c897c2f6..3518f0da 100644 --- a/fusion_clock/models/res_config_settings.py +++ b/fusion_clock/models/res_config_settings.py @@ -253,11 +253,13 @@ class ResConfigSettings(models.TransientModel): "Leave empty to fall back to manager-group membership only.", ) fclk_nfc_kiosk_debug = fields.Boolean( - string='Enable Mock-Tap Debug', + string='Debug Mode (overlay + mock-tap)', config_parameter='fusion_clock.nfc_kiosk_debug', default=False, - help="Enables a Ctrl+Shift+T keyboard shortcut on the kiosk page for " - "simulating a tap with a configurable UID. Off in production.", + help="Enables two dev/troubleshooting features on the NFC kiosk page: " + "(1) a green-text debug overlay at the top of the screen logging every NFC and tap event in real time, " + "and (2) a Ctrl+Shift+T keyboard shortcut that simulates a tap with a configurable UID. " + "Turn OFF in production — the overlay is intrusive for end users.", ) fclk_nfc_kiosk_location_id = fields.Many2one( related='company_id.x_fclk_nfc_kiosk_location_id', diff --git a/fusion_clock/static/src/js/fusion_clock_nfc_kiosk.js b/fusion_clock/static/src/js/fusion_clock_nfc_kiosk.js index 4a482a56..2b7cda12 100644 --- a/fusion_clock/static/src/js/fusion_clock_nfc_kiosk.js +++ b/fusion_clock/static/src/js/fusion_clock_nfc_kiosk.js @@ -16,6 +16,29 @@ const debugEnabled = root.dataset.debugEnabled === "1"; const locationConfigured = root.dataset.locationConfigured === "1"; + // ────────────────────────────────────────────────────────────── + // Debug overlay (visible only when fusion_clock.nfc_kiosk_debug = True) + // ────────────────────────────────────────────────────────────── + let _debugOverlayEl = null; + function debugLog(msg) { + try { console.log("[nfc-kiosk-debug]", msg); } catch (e) {} + if (!debugEnabled) return; + if (!_debugOverlayEl) { + _debugOverlayEl = document.createElement("div"); + _debugOverlayEl.style.cssText = "position:fixed;top:0;left:0;right:0;background:rgba(0,0,0,0.9);color:#0f0;font-family:monospace;font-size:11px;padding:0.5rem;max-height:35vh;overflow-y:auto;z-index:9999;line-height:1.3;border-bottom:1px solid #0f0;"; + document.body.appendChild(_debugOverlayEl); + } + const line = document.createElement("div"); + const ts = new Date().toLocaleTimeString(); + line.textContent = "[" + ts + "] " + msg; + _debugOverlayEl.appendChild(line); + while (_debugOverlayEl.childNodes.length > 40) { + _debugOverlayEl.removeChild(_debugOverlayEl.firstChild); + } + _debugOverlayEl.scrollTop = _debugOverlayEl.scrollHeight; + } + debugLog("page loaded; debugEnabled=" + debugEnabled + " photoRequired=" + photoRequired + " NDEFReader=" + ("NDEFReader" in window)); + // ────────────────────────────────────────────────────────────── // State machine // ────────────────────────────────────────────────────────────── @@ -281,49 +304,68 @@ let nfcReady = false; async function startNfcReader() { + debugLog("startNfcReader: NDEFReader in window = " + ("NDEFReader" in window)); if (!("NDEFReader" in window)) { throw new Error("Web NFC not supported on this browser/device. Use Chrome on Android."); } ndefReader = new NDEFReader(); + debugLog("startNfcReader: ndefReader created, calling scan()..."); await ndefReader.scan(); + debugLog("startNfcReader: scan() resolved ✓"); ndefReader.addEventListener("reading", onNfcReading); - ndefReader.addEventListener("readingerror", () => { + ndefReader.addEventListener("readingerror", (ev) => { + debugLog("readingerror event fired"); console.warn("[nfc-kiosk] reading error; reader still active"); }); nfcReady = true; + debugLog("startNfcReader: listeners attached, nfcReady=true"); } function onNfcReading(event) { // event.serialNumber is the card UID — works for raw MIFARE access cards - const uid = (event.serialNumber || "").toUpperCase(); - if (!uid) return; + const rawSerial = event.serialNumber || ""; + const uid = rawSerial.toUpperCase(); + const recCount = (event.message && event.message.records) ? event.message.records.length : 0; + debugLog("reading event: serialNumber=" + JSON.stringify(rawSerial) + " (len=" + rawSerial.length + ") records=" + recCount + " state=" + currentState); + if (!uid) { + debugLog(" → IGNORED: empty serialNumber"); + return; + } if (currentState === STATE.ENROLL) { - // Enroll Mode handles taps differently (wired up in Task 18) + debugLog(" → routing to _onEnrollTap"); window.__nfcKiosk._onEnrollTap && window.__nfcKiosk._onEnrollTap(uid); return; } - if (currentState !== STATE.IDLE) return; // ignore taps mid-result + if (currentState !== STATE.IDLE) { + debugLog(" → IGNORED: not in IDLE (state=" + currentState + ")"); + return; + } + debugLog(" → calling handleTap(" + uid + ")"); handleTap(uid); } async function handleTap(uid) { + debugLog("handleTap: uid=" + uid); setState(STATE.PROCESSING); let photoB64 = ""; try { photoB64 = await capturePhoto(); + debugLog("handleTap: photo captured, size=" + photoB64.length); } catch (e) { + debugLog("handleTap: photo capture failed: " + e.message); console.warn("[nfc-kiosk] camera capture failed", e); - // Server enforces photo_required if needed } try { + debugLog("handleTap: POST /fusion_clock/kiosk/nfc/tap..."); const result = await postJson("/fusion_clock/kiosk/nfc/tap", { card_uid: uid, photo_b64: photoB64 }); + debugLog("handleTap: response = " + JSON.stringify(result).slice(0, 200)); if (result.error === "debounce") { - // silent — back to IDLE setState(STATE.IDLE); return; } setState(STATE.RESULT, result); } catch (e) { + debugLog("handleTap: POST failed: " + e.message); setState(STATE.RESULT, { error: "network", message: "No connection. Please try again." }); } } @@ -374,11 +416,15 @@ const setupBtn = document.getElementById("nfc_setup_start"); if (setupBtn) { setupBtn.addEventListener("click", async () => { + debugLog("setup button clicked"); try { await startNfcReader(); + debugLog("setup: NFC ready, starting camera..."); try { await startCamera(); + debugLog("setup: camera ready ✓"); } catch (camErr) { + debugLog("setup: camera failed: " + camErr.message); if (photoRequired) throw camErr; console.warn("[nfc-kiosk] camera unavailable, continuing (photo not required)", camErr); } diff --git a/fusion_clock/views/res_config_settings_views.xml b/fusion_clock/views/res_config_settings_views.xml index 1b10c5ed..122a587c 100644 --- a/fusion_clock/views/res_config_settings_views.xml +++ b/fusion_clock/views/res_config_settings_views.xml @@ -263,7 +263,7 @@
-