const API_BASE = '/api'; const tokenKey = 'uent_staff_token'; const userKey = 'uent_staff_user'; const loadingScreen = document.getElementById('loadingScreen'); const loginScreen = document.getElementById('loginScreen'); const homeScreen = document.getElementById('homeScreen'); const loginBtn = document.getElementById('loginBtn'); const logoutBtn = document.getElementById('logoutBtn'); const clockInBtn = document.getElementById('clockInBtn'); const clockOutBtn = document.getElementById('clockOutBtn'); const workingLunchBtn = document.getElementById('workingLunchBtn'); const useBrowserLocationBtn = document.getElementById('useBrowserLocationBtn'); const loginMessage = document.getElementById('loginMessage'); const welcomeText = document.getElementById('welcomeText'); const worksiteText = document.getElementById('worksiteText'); const clockInTime = document.getElementById('clockInTime'); const clockOutTime = document.getElementById('clockOutTime'); const workingLunchTime = document.getElementById('workingLunchTime'); const actionMessage = document.getElementById('actionMessage'); let currentUser = null; let currentWorksite = null; let pingTimer = null; let workingLunchMarked = false; function showScreen(screen) { [loadingScreen, loginScreen, homeScreen].forEach(s => s.classList.remove('active')); screen.classList.add('active'); } function getToken() { return localStorage.getItem(tokenKey) || ''; } function setToken(token) { localStorage.setItem(tokenKey, token); } function stopLocationPing() { if (pingTimer) { clearInterval(pingTimer); pingTimer = null; } } function clearToken() { localStorage.removeItem(tokenKey); localStorage.removeItem(userKey); stopLocationPing(); } function setStoredUser(user) { localStorage.setItem(userKey, JSON.stringify(user || null)); } function getStoredUser() { const raw = localStorage.getItem(userKey); if (!raw) return null; try { return JSON.parse(raw); } catch (e) { return null; } } function setActionMessage(message, isError = false) { actionMessage.textContent = message || ''; actionMessage.style.color = isError ? '#d83a34' : '#1872b9'; } function fmtDateTime(value) { return value ? value.replace('T', ' ') : null; } async function parseResponse(response) { const text = await response.text(); try { return JSON.parse(text); } catch (e) { return { raw_response: text, status: response.status }; } } function makeButtonActive(button, brandClass) { button.disabled = false; button.className = `btn action-btn ${brandClass}`; } function makeButtonInactive(button, paleClass) { button.disabled = true; button.className = `btn action-btn ${paleClass}`; } function setCoords(latitude, longitude, accuracy) { document.getElementById('latitude').value = latitude; document.getElementById('longitude').value = longitude; document.getElementById('accuracy').value = accuracy || 10; } async function sendLocationPing(latitude, longitude, accuracy, speedKmh = null, silent = true) { const token = getToken(); if (!token) return; const response = await fetch(`${API_BASE}/location-ping.php`, { method: 'POST', headers: {'Content-Type': 'application/json','Authorization': 'Bearer ' + token}, body: JSON.stringify({ latitude, longitude, accuracy_meters: accuracy, speed_kmh: speedKmh }) }); const data = await parseResponse(response); if (!silent) setActionMessage(data.message || 'Location sent.', !data.success); } function pingCurrentPosition(silent = true) { if (!navigator.geolocation || !getToken()) return; navigator.geolocation.getCurrentPosition( async (position) => { const latitude = position.coords.latitude; const longitude = position.coords.longitude; const accuracy = position.coords.accuracy; const speedMs = position.coords.speed; const speedKmh = (speedMs !== null && speedMs !== undefined) ? (speedMs * 3.6) : null; setCoords(latitude, longitude, accuracy); await sendLocationPing(latitude, longitude, accuracy, speedKmh, silent); }, (error) => { if (!silent) setActionMessage(error.message, true); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); } function startLocationPing() { stopLocationPing(); pingCurrentPosition(true); pingTimer = setInterval(() => pingCurrentPosition(true), 60000); } async function fetchWorkingLunchStatus(workSessionId) { if (!workSessionId) { workingLunchMarked = false; workingLunchTime.textContent = 'Not marked yet'; return; } try { const response = await fetch(`${API_BASE}/working-lunch-status.php?work_session_id=${encodeURIComponent(workSessionId)}`, { method: 'GET', headers: { 'Authorization': 'Bearer ' + getToken() } }); const data = await parseResponse(response); if (data.success && data.event) { workingLunchMarked = true; workingLunchTime.textContent = 'Marked: ' + fmtDateTime(data.event.event_time); } else { workingLunchMarked = false; workingLunchTime.textContent = 'Not marked yet'; } } catch (e) { workingLunchMarked = false; workingLunchTime.textContent = 'Not marked yet'; } } async function applyAttendanceState(history) { const today = new Date().toISOString().slice(0, 10); const todaySessions = Array.isArray(history) ? history.filter(x => x.session_date === today) : []; const openSession = todaySessions.find(x => !x.clock_out_time) || null; const latestClockIn = todaySessions.length ? todaySessions[0].clock_in_time : null; let latestClockOut = null; for (const session of todaySessions) { if (session.clock_out_time) { latestClockOut = session.clock_out_time; break; } } clockInTime.textContent = latestClockIn ? 'Last clock in: ' + fmtDateTime(latestClockIn) : 'Not clocked in yet'; clockOutTime.textContent = latestClockOut ? 'Last clock out: ' + fmtDateTime(latestClockOut) : 'Not clocked out yet'; if (openSession) { makeButtonInactive(clockInBtn, 'pale-green'); makeButtonActive(clockOutBtn, 'brand-red'); await fetchWorkingLunchStatus(openSession.id || openSession.work_session_id || null); if (workingLunchMarked) makeButtonInactive(workingLunchBtn, 'pale-orange'); else makeButtonActive(workingLunchBtn, 'brand-orange'); setActionMessage('You are clocked in.'); startLocationPing(); return; } makeButtonActive(clockInBtn, 'brand-green'); makeButtonInactive(clockOutBtn, 'pale-blue'); makeButtonInactive(workingLunchBtn, 'pale-orange'); workingLunchMarked = false; workingLunchTime.textContent = 'Not marked yet'; stopLocationPing(); if (todaySessions.length > 0) setActionMessage('Ready for next session.'); else setActionMessage('Ready.'); } async function login() { loginBtn.disabled = true; loginMessage.textContent = 'Logging in...'; const response = await fetch(`${API_BASE}/login.php`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: document.getElementById('email').value.trim(), password: document.getElementById('password').value }) }); const data = await parseResponse(response); if (data.success && data.token) { setToken(data.token); currentUser = data.user || null; setStoredUser(currentUser); loginMessage.textContent = ''; await loadHome(); } else { loginBtn.disabled = false; loginMessage.textContent = data.message || 'Login failed.'; setActionMessage(data.message || 'Login failed.', true); showScreen(loginScreen); } } async function getMyWorksite() { const response = await fetch(`${API_BASE}/my-worksite.php`, { method: 'GET', headers: { 'Authorization': 'Bearer ' + getToken() } }); return parseResponse(response); } async function getAttendanceHistory() { const response = await fetch(`${API_BASE}/attendance-history.php`, { method: 'GET', headers: { 'Authorization': 'Bearer ' + getToken() } }); return parseResponse(response); } async function loadHome() { const token = getToken(); if (!token) { loginBtn.disabled = false; showScreen(loginScreen); return; } const worksiteData = await getMyWorksite(); if (!worksiteData.success || !worksiteData.worksite) { clearToken(); loginBtn.disabled = false; showScreen(loginScreen); return; } currentWorksite = worksiteData.worksite; currentUser = currentUser || getStoredUser(); welcomeText.textContent = currentUser ? `Welcome ${currentUser.first_name} ${currentUser.last_name}` : 'Welcome'; worksiteText.textContent = currentWorksite.city; setCoords(currentWorksite.latitude, currentWorksite.longitude, 10); const historyData = await getAttendanceHistory(); await applyAttendanceState(historyData.success ? historyData.history : []); showScreen(homeScreen); } function useBrowserLocation() { pingCurrentPosition(false); } async function attendanceAction(endpoint, label) { const token = getToken(); if (!token) { clearToken(); showScreen(loginScreen); return; } if (endpoint === 'clock-in.php') clockInBtn.disabled = true; else if (endpoint === 'clock-out.php') clockOutBtn.disabled = true; else if (endpoint === 'working-lunch.php') workingLunchBtn.disabled = true; setActionMessage(`${label} in progress...`); await new Promise(resolve => { if (!navigator.geolocation) return resolve(); navigator.geolocation.getCurrentPosition( (position) => { setCoords(position.coords.latitude, position.coords.longitude, position.coords.accuracy); resolve(); }, () => resolve(), { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); }); const response = await fetch(`${API_BASE}/${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ latitude: parseFloat(document.getElementById('latitude').value), longitude: parseFloat(document.getElementById('longitude').value), accuracy_meters: parseFloat(document.getElementById('accuracy').value), device_info: 'Mobile Browser' }) }); const data = await parseResponse(response); setActionMessage(data.message || label, !data.success); if (data.success && endpoint === 'clock-in.php') pingCurrentPosition(true); if (data.success && endpoint === 'working-lunch.php') workingLunchMarked = true; await loadHome(); } async function bootstrap() { showScreen(loadingScreen); currentUser = getStoredUser(); if (!getToken()) { loginBtn.disabled = false; showScreen(loginScreen); return; } await loadHome(); } loginBtn.addEventListener('click', login); logoutBtn.addEventListener('click', () => { clearToken(); currentUser = null; currentWorksite = null; loginBtn.disabled = false; setActionMessage('Logged out.'); showScreen(loginScreen); }); useBrowserLocationBtn.addEventListener('click', useBrowserLocation); clockInBtn.addEventListener('click', () => attendanceAction('clock-in.php', 'Clock In')); clockOutBtn.addEventListener('click', () => attendanceAction('clock-out.php', 'Clock Out')); workingLunchBtn.addEventListener('click', () => attendanceAction('working-lunch.php', 'Working Lunch')); if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('service-worker.js?v=4').catch(() => {}); }); } bootstrap();