/** @odoo-module **/ import { Interaction } from "@web/public/interaction"; import { registry } from "@web/core/registry"; export class FusionClockKiosk extends Interaction { static selector = "#fclk-kiosk"; setup() { this.selectedEmployeeId = 0; this.resetTimer = null; this.searchTimeout = null; const pinAttr = this.el.dataset.pinRequired; this.pinRequired = pinAttr === "true" || pinAttr === "True"; this._startClock(); this._bindEvents(); } _startClock() { const el = document.getElementById("fclk-kiosk-time"); if (!el) return; const update = () => { el.textContent = new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); }; update(); setInterval(update, 1000); } _bindEvents() { const queryInput = document.getElementById("fclk-kiosk-query"); if (queryInput) { queryInput.addEventListener("input", (e) => this._onSearch(e.target.value)); } const backBtn = document.getElementById("fclk-kiosk-back-btn"); if (backBtn) { backBtn.addEventListener("click", () => this._resetKiosk()); } const clockBtn = document.getElementById("fclk-kiosk-clock-btn"); if (clockBtn) { clockBtn.addEventListener("click", () => this._onClock()); } } _resetKiosk() { const search = document.getElementById("fclk-kiosk-search"); const pin = document.getElementById("fclk-kiosk-pin"); const result = document.getElementById("fclk-kiosk-result"); const error = document.getElementById("fclk-kiosk-error"); const query = document.getElementById("fclk-kiosk-query"); const results = document.getElementById("fclk-kiosk-results"); const pinInput = document.getElementById("fclk-kiosk-pin-input"); if (search) search.style.display = ""; if (pin) pin.style.display = "none"; if (result) result.style.display = "none"; if (error) error.style.display = "none"; if (query) query.value = ""; if (results) results.innerHTML = ""; if (pinInput) pinInput.value = ""; this.selectedEmployeeId = 0; if (this.resetTimer) clearTimeout(this.resetTimer); } _showError(msg) { const el = document.getElementById("fclk-kiosk-error"); if (el) { el.textContent = msg; el.style.display = ""; } } _onSearch(value) { if (this.searchTimeout) clearTimeout(this.searchTimeout); const q = value.trim(); if (q.length < 2) { const container = document.getElementById("fclk-kiosk-results"); if (container) container.innerHTML = ""; return; } this.searchTimeout = setTimeout(async () => { try { const resp = await fetch("/fusion_clock/kiosk/search", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", method: "call", params: { query: q } }), }); const data = await resp.json(); const employees = (data.result || {}).employees || []; const container = document.getElementById("fclk-kiosk-results"); if (!container) return; container.innerHTML = ""; for (const emp of employees) { const item = document.createElement("a"); item.href = "#"; item.className = "list-group-item list-group-item-action d-flex justify-content-between"; const statusBadge = emp.is_checked_in ? "bg-success" : "bg-secondary"; const statusText = emp.is_checked_in ? "In" : "Out"; item.innerHTML = `${emp.name} ${emp.department}` + `${statusText}`; item.addEventListener("click", (e) => { e.preventDefault(); this._selectEmployee(emp); }); container.appendChild(item); } } catch { this._showError("Search failed."); } }, 300); } _selectEmployee(emp) { this.selectedEmployeeId = emp.id; const nameEl = document.getElementById("fclk-kiosk-emp-name"); if (nameEl) nameEl.textContent = emp.name; const searchEl = document.getElementById("fclk-kiosk-search"); const pinEl = document.getElementById("fclk-kiosk-pin"); const errorEl = document.getElementById("fclk-kiosk-error"); if (searchEl) searchEl.style.display = "none"; if (pinEl) pinEl.style.display = ""; if (errorEl) errorEl.style.display = "none"; const clockBtn = document.getElementById("fclk-kiosk-clock-btn"); if (clockBtn) { clockBtn.textContent = emp.is_checked_in ? "Clock Out" : "Clock In"; clockBtn.className = "btn btn-lg " + (emp.is_checked_in ? "btn-danger" : "btn-success"); } } async _onClock() { if (!this.selectedEmployeeId) return; const btn = document.getElementById("fclk-kiosk-clock-btn"); if (btn) btn.disabled = true; const pinInput = document.getElementById("fclk-kiosk-pin-input"); const pin = pinInput ? pinInput.value : ""; if (this.pinRequired && pin.length === 0) { this._showError("Please enter your PIN."); if (btn) btn.disabled = false; return; } try { if (this.pinRequired) { const vResp = await fetch("/fusion_clock/kiosk/verify_pin", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", method: "call", params: { employee_id: this.selectedEmployeeId, pin }, }), }); const vData = await vResp.json(); if (vData.result && vData.result.error) { this._showError(vData.result.error); if (btn) btn.disabled = false; return; } } let lat = 0; let lng = 0; try { const pos = await new Promise((resolve, reject) => { navigator.geolocation.getCurrentPosition(resolve, reject, { timeout: 10000, enableHighAccuracy: true, }); }); lat = pos.coords.latitude; lng = pos.coords.longitude; } catch { // Native GPS unavailable -- try IP geolocation } if (lat === 0 && lng === 0) { try { const ipResp = await fetch("https://ipapi.co/json/"); if (ipResp.ok) { const ipData = await ipResp.json(); if (ipData.latitude && ipData.longitude) { lat = ipData.latitude; lng = ipData.longitude; } } } catch { // IP geolocation also unavailable } } const resp = await fetch("/fusion_clock/kiosk/clock", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", method: "call", params: { employee_id: this.selectedEmployeeId, latitude: lat, longitude: lng }, }), }); const data = await resp.json(); const result = data.result || {}; if (result.error) { this._showError(result.error); if (btn) btn.disabled = false; return; } const pinEl = document.getElementById("fclk-kiosk-pin"); const resultEl = document.getElementById("fclk-kiosk-result"); if (pinEl) pinEl.style.display = "none"; if (resultEl) resultEl.style.display = ""; const msgEl = document.getElementById("fclk-kiosk-result-msg"); if (msgEl) { const icon = result.action === "clock_in" ? "fa-check-circle text-success" : "fa-hand-paper-o text-warning"; let html = `
`; html += `
${result.message || "Done"}
`; if (result.net_hours !== undefined) { html += `
Net hours: ${result.net_hours}h
`; } msgEl.innerHTML = html; } this.resetTimer = setTimeout(() => this._resetKiosk(), 10000); } catch { this._showError("Operation failed."); } if (btn) btn.disabled = false; } } registry.category("public.interactions").add("fusion_clock.kiosk", FusionClockKiosk);