diff --git a/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html b/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html index ecfccf3..d36e29c 100644 --- a/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html +++ b/app/modules/employees/templates/employees/portal_partials/attendance_punch_card.html @@ -10,12 +10,25 @@ Attendance not marked for today. {% endif %}

+ {% if today_attendance %} +

+ Approval: {{ today_attendance.approval_status.replace('_',' ').title() }} ยท + Geo: {{ today_attendance.punch_in_geo_status.replace('_',' ').title() if today_attendance.punch_in_geo_status else 'Not Captured' }} + {% if today_attendance.punch_out_geo_status %} / {{ today_attendance.punch_out_geo_status.replace('_',' ').title() }}{% endif %} +

+ {% endif %} Full View {% if employee %} -
Location not captured yet.
+
+ Click Punch In/Punch Out. The app will ask for browser location before submitting attendance. +
+
+ If branch geofence/IP verification is not configured, attendance may be approved without location check. Configure branch attendance geo/IP settings to enforce verification. +
+
@@ -36,6 +49,29 @@
+ + {% endif %} @@ -51,6 +87,7 @@ window.EmployeePortalGeo = window.EmployeePortalGeo || (function () { 'border-slate-200 bg-slate-50 text-slate-600' ); } + function setHidden(form, lat, lon, acc) { var latInput = form.querySelector('input[name="latitude"]'); var lonInput = form.querySelector('input[name="longitude"]'); @@ -59,35 +96,84 @@ window.EmployeePortalGeo = window.EmployeePortalGeo || (function () { if (lonInput) lonInput.value = lon || ''; if (accInput) accInput.value = acc || ''; } + + function openLocationModal() { + return new Promise(function (resolve) { + var modal = document.getElementById('employee-location-consent-modal'); + var modalContinue = document.getElementById('employee-location-modal-continue'); + var modalCancel = document.getElementById('employee-location-modal-cancel'); + if (!modal || !modalContinue || !modalCancel) { + resolve(true); + return; + } + + function cleanup(result) { + modal.classList.add('hidden'); + modal.classList.remove('flex'); + modal.setAttribute('aria-hidden', 'true'); + modalContinue.removeEventListener('click', onContinue); + modalCancel.removeEventListener('click', onCancel); + resolve(result); + } + function onContinue() { cleanup(true); } + function onCancel() { + updateStatus('Attendance punch cancelled. Location permission was not requested.', 'warn'); + cleanup(false); + } + + modalContinue.addEventListener('click', onContinue); + modalCancel.addEventListener('click', onCancel); + modal.classList.remove('hidden'); + modal.classList.add('flex'); + modal.setAttribute('aria-hidden', 'false'); + modalContinue.focus(); + }); + } + + function captureLocation(form) { + return new Promise(function (resolve) { + if (!navigator.geolocation) { + setHidden(form, '', '', ''); + updateStatus('Browser geolocation is not available. Attendance can be saved, but geofence verification cannot be performed.', 'warn'); + resolve(false); + return; + } + updateStatus('Browser location popup opened. Please choose Allow to capture location...', 'info'); + navigator.geolocation.getCurrentPosition(function (pos) { + var c = pos.coords || {}; + setHidden(form, c.latitude, c.longitude, c.accuracy); + updateStatus('Location captured. Accuracy: ' + Math.round(c.accuracy || 0) + ' meters.', 'ok'); + resolve(true); + }, function (err) { + setHidden(form, '', '', ''); + var reason = 'Location permission denied or unavailable.'; + if (err && err.code === 1) reason = 'Location permission denied. Allow location for this site in browser settings.'; + if (err && err.code === 2) reason = 'Location unavailable. Enable device location service and try again.'; + if (err && err.code === 3) reason = 'Location capture timed out. Try again with stable GPS/location service.'; + updateStatus(reason + ' Attendance can be saved, but geofence verification cannot be performed.', 'warn'); + resolve(false); + }, { enableHighAccuracy: true, timeout: 20000, maximumAge: 0 }); + }); + } + function bind() { document.querySelectorAll('.employee-attendance-geo-form').forEach(function (form) { if (form.dataset.geoBound === '1') return; form.dataset.geoBound = '1'; - form.addEventListener('submit', function (event) { + form.addEventListener('submit', async function (event) { if (form.dataset.geoSubmitted === '1') return; event.preventDefault(); - if (!navigator.geolocation) { - updateStatus('Browser geolocation is not available. Punch will continue without location.', 'warn'); - form.dataset.geoSubmitted = '1'; - form.submit(); - return; - } - updateStatus('Requesting browser location. Please click Allow...', 'info'); - navigator.geolocation.getCurrentPosition(function (pos) { - var c = pos.coords || {}; - setHidden(form, c.latitude, c.longitude, c.accuracy); - updateStatus('Location captured. Accuracy: ' + Math.round(c.accuracy || 0) + ' meters.', 'ok'); - form.dataset.geoSubmitted = '1'; - form.submit(); - }, function () { - setHidden(form, '', '', ''); - updateStatus('Location denied/unavailable. Punch will continue and may need approval if branch geofence is enabled.', 'warn'); - form.dataset.geoSubmitted = '1'; - form.submit(); - }, {enableHighAccuracy: true, timeout: 12000, maximumAge: 0}); + + var proceed = await openLocationModal(); + if (!proceed) return; + + await captureLocation(form); + form.dataset.geoSubmitted = '1'; + form.submit(); }); }); } + return {bind: bind}; })(); window.EmployeePortalGeo.bind(); diff --git a/app/modules/employees/templates/employees/self_attendance.html b/app/modules/employees/templates/employees/self_attendance.html index 6eec7d4..9e11066 100644 --- a/app/modules/employees/templates/employees/self_attendance.html +++ b/app/modules/employees/templates/employees/self_attendance.html @@ -115,7 +115,7 @@ Click Continue, then choose Allow in the browser location popup.

- If you are outside the branch or location is denied/unavailable, your attendance can still be saved as pending approval for OD/client visit review. + If you are outside the branch or location is denied/unavailable, your attendance can still be saved. It will require approval only when branch geofence/IP verification is configured.

@@ -200,7 +200,7 @@ geoAttempted = true; if (!navigator.geolocation) { setHidden('', '', ''); - updateStatus('Browser geolocation is not available. Punch will be saved as pending approval if branch geofence is enabled.', 'warn'); + updateStatus('Browser geolocation is not available. Attendance can be saved, but geofence verification cannot be performed if location is denied/unavailable.', 'warn'); resolve(false); return; } @@ -219,7 +219,7 @@ if (err && err.code === 1) reason = 'Location permission denied. Please allow location for this site in browser settings.'; if (err && err.code === 2) reason = 'Location unavailable. Please enable device GPS/location service and try again.'; if (err && err.code === 3) reason = 'Location capture timed out. Please try again near a window or with GPS enabled.'; - updateStatus(reason + ' Punch will be saved as pending approval if branch geofence is enabled.', 'warn'); + updateStatus(reason + ' Attendance can be saved, but geofence verification cannot be performed if location is denied/unavailable.', 'warn'); resolve(false); }, { enableHighAccuracy: true, timeout: 20000, maximumAge: 0 }); });