feat(fusion_clock): NFC kiosk on-screen debug overlay + clearer settings label

This commit is contained in:
gsinghpal
2026-05-14 08:03:47 -04:00
parent 878d05685c
commit 98cb42d2e5
3 changed files with 59 additions and 11 deletions

View File

@@ -253,11 +253,13 @@ class ResConfigSettings(models.TransientModel):
"Leave empty to fall back to manager-group membership only.", "Leave empty to fall back to manager-group membership only.",
) )
fclk_nfc_kiosk_debug = fields.Boolean( fclk_nfc_kiosk_debug = fields.Boolean(
string='Enable Mock-Tap Debug', string='Debug Mode (overlay + mock-tap)',
config_parameter='fusion_clock.nfc_kiosk_debug', config_parameter='fusion_clock.nfc_kiosk_debug',
default=False, default=False,
help="Enables a Ctrl+Shift+T keyboard shortcut on the kiosk page for " help="Enables two dev/troubleshooting features on the NFC kiosk page: "
"simulating a tap with a configurable UID. Off in production.", "(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( fclk_nfc_kiosk_location_id = fields.Many2one(
related='company_id.x_fclk_nfc_kiosk_location_id', related='company_id.x_fclk_nfc_kiosk_location_id',

View File

@@ -16,6 +16,29 @@
const debugEnabled = root.dataset.debugEnabled === "1"; const debugEnabled = root.dataset.debugEnabled === "1";
const locationConfigured = root.dataset.locationConfigured === "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 // State machine
// ────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────
@@ -281,49 +304,68 @@
let nfcReady = false; let nfcReady = false;
async function startNfcReader() { async function startNfcReader() {
debugLog("startNfcReader: NDEFReader in window = " + ("NDEFReader" in window));
if (!("NDEFReader" in window)) { if (!("NDEFReader" in window)) {
throw new Error("Web NFC not supported on this browser/device. Use Chrome on Android."); throw new Error("Web NFC not supported on this browser/device. Use Chrome on Android.");
} }
ndefReader = new NDEFReader(); ndefReader = new NDEFReader();
debugLog("startNfcReader: ndefReader created, calling scan()...");
await ndefReader.scan(); await ndefReader.scan();
debugLog("startNfcReader: scan() resolved ✓");
ndefReader.addEventListener("reading", onNfcReading); ndefReader.addEventListener("reading", onNfcReading);
ndefReader.addEventListener("readingerror", () => { ndefReader.addEventListener("readingerror", (ev) => {
debugLog("readingerror event fired");
console.warn("[nfc-kiosk] reading error; reader still active"); console.warn("[nfc-kiosk] reading error; reader still active");
}); });
nfcReady = true; nfcReady = true;
debugLog("startNfcReader: listeners attached, nfcReady=true");
} }
function onNfcReading(event) { function onNfcReading(event) {
// event.serialNumber is the card UID — works for raw MIFARE access cards // event.serialNumber is the card UID — works for raw MIFARE access cards
const uid = (event.serialNumber || "").toUpperCase(); const rawSerial = event.serialNumber || "";
if (!uid) return; 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) { 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); window.__nfcKiosk._onEnrollTap && window.__nfcKiosk._onEnrollTap(uid);
return; 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); handleTap(uid);
} }
async function handleTap(uid) { async function handleTap(uid) {
debugLog("handleTap: uid=" + uid);
setState(STATE.PROCESSING); setState(STATE.PROCESSING);
let photoB64 = ""; let photoB64 = "";
try { try {
photoB64 = await capturePhoto(); photoB64 = await capturePhoto();
debugLog("handleTap: photo captured, size=" + photoB64.length);
} catch (e) { } catch (e) {
debugLog("handleTap: photo capture failed: " + e.message);
console.warn("[nfc-kiosk] camera capture failed", e); console.warn("[nfc-kiosk] camera capture failed", e);
// Server enforces photo_required if needed
} }
try { try {
debugLog("handleTap: POST /fusion_clock/kiosk/nfc/tap...");
const result = await postJson("/fusion_clock/kiosk/nfc/tap", { card_uid: uid, photo_b64: photoB64 }); 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") { if (result.error === "debounce") {
// silent — back to IDLE
setState(STATE.IDLE); setState(STATE.IDLE);
return; return;
} }
setState(STATE.RESULT, result); setState(STATE.RESULT, result);
} catch (e) { } catch (e) {
debugLog("handleTap: POST failed: " + e.message);
setState(STATE.RESULT, { error: "network", message: "No connection. Please try again." }); setState(STATE.RESULT, { error: "network", message: "No connection. Please try again." });
} }
} }
@@ -374,11 +416,15 @@
const setupBtn = document.getElementById("nfc_setup_start"); const setupBtn = document.getElementById("nfc_setup_start");
if (setupBtn) { if (setupBtn) {
setupBtn.addEventListener("click", async () => { setupBtn.addEventListener("click", async () => {
debugLog("setup button clicked");
try { try {
await startNfcReader(); await startNfcReader();
debugLog("setup: NFC ready, starting camera...");
try { try {
await startCamera(); await startCamera();
debugLog("setup: camera ready ✓");
} catch (camErr) { } catch (camErr) {
debugLog("setup: camera failed: " + camErr.message);
if (photoRequired) throw camErr; if (photoRequired) throw camErr;
console.warn("[nfc-kiosk] camera unavailable, continuing (photo not required)", camErr); console.warn("[nfc-kiosk] camera unavailable, continuing (photo not required)", camErr);
} }

View File

@@ -263,7 +263,7 @@
<field name="fclk_nfc_enroll_password" password="True"/> <field name="fclk_nfc_enroll_password" password="True"/>
</div> </div>
<div class="row mt8"> <div class="row mt8">
<label for="fclk_nfc_kiosk_debug" string="Mock-Tap Debug" class="col-lg-5 o_light_label"/> <label for="fclk_nfc_kiosk_debug" string="Debug Overlay" class="col-lg-5 o_light_label"/>
<field name="fclk_nfc_kiosk_debug"/> <field name="fclk_nfc_kiosk_debug"/>
</div> </div>
</div> </div>