From 694f41968a3527abbba0bf7e592077c94d5c1923 Mon Sep 17 00:00:00 2001 From: Mahdi Date: Wed, 23 Sep 2026 13:07:21 +0330 Subject: [PATCH] fix office location detection radius and GPS accuracy --- resources/views/admin/index.blade.php | 25 +-- resources/views/attendance/index.blade.php | 167 +++++++++++++++------ 2 files changed, 141 insertions(+), 51 deletions(-) diff --git a/resources/views/admin/index.blade.php b/resources/views/admin/index.blade.php index 5f5e397b..5abcd99d 100755 --- a/resources/views/admin/index.blade.php +++ b/resources/views/admin/index.blade.php @@ -251,6 +251,8 @@ SVG; const referenceLat = 32.6504751; const referenceLng = 51.6936045; + const OFFICE_RADIUS_METERS = 100; + const MAX_ACCURACY_CREDIT_METERS = 80; const submitBtn = document.getElementById("submitBtn"); const latInput = document.getElementById("latitude"); @@ -278,26 +280,31 @@ SVG; return earthRadius * c; } + function isInsideOffice(distanceMeters, accuracyMeters) { + const accuracyCredit = Math.min(Math.max(0, accuracyMeters || 0), MAX_ACCURACY_CREDIT_METERS); + return Math.max(0, distanceMeters - accuracyCredit) <= OFFICE_RADIUS_METERS; + } + navigator.geolocation.getCurrentPosition( position => { const lat = position.coords.latitude; const lng = position.coords.longitude; + const accuracy = position.coords.accuracy || 0; latInput.value = lat; lngInput.value = lng; const dist = Math.round(getDistanceInMeters(referenceLat, referenceLng, lat, lng)); distanceInput.value = dist; - if (dist < 20) { - distanceMessage.textContent = `مکان: شرکت`; - } else { - distanceMessage.textContent = `مکان: خارج از شرکت`; - } + const inside = isInsideOffice(dist, accuracy); + distanceMessage.textContent = inside + ? `مکان: شرکت (~${dist}م)` + : `مکان: خارج از شرکت (~${dist}م)`; - distanceMessage.classList.remove("bg-gray-400", "bg-blue-500", "bg-green-500", "bg-orange-500", "cursor-not-allowed"); - if (dist < 10) { + distanceMessage.classList.remove("bg-gray-400", "bg-blue-500", "bg-green-500", "bg-orange-500", "cursor-not-allowed", "text-green-500", "text-blue-500", "text-orange-500", "text-gray-400"); + if (inside) { distanceMessage.classList.add("text-green-500"); - } else if (dist < 50) { + } else if (dist < OFFICE_RADIUS_METERS * 2) { distanceMessage.classList.add("text-blue-500"); } else if (dist > 2000) { distanceMessage.classList.add("text-orange-500"); @@ -311,7 +318,7 @@ SVG; }, error => alert("⛔ خطا در دریافت موقعیت مکانی: " + error.message), { enableHighAccuracy: true, - timeout: 10000, + timeout: 20000, maximumAge: 0 } ); diff --git a/resources/views/attendance/index.blade.php b/resources/views/attendance/index.blade.php index 8cb81bcc..e7bf56ca 100755 --- a/resources/views/attendance/index.blade.php +++ b/resources/views/attendance/index.blade.php @@ -311,12 +311,12 @@ if (!empty($user->tasks)) { {{-- زمان ورود --}} - + {{ $item->entered_at ? \Carbon\Carbon::parse($item->entered_at)->format('H:i') : '-' }} {{-- زمان خروج --}} - + {{ $item->exited_at ? \Carbon\Carbon::parse($item->exited_at)->format('H:i') : '-' }} @@ -646,8 +646,12 @@ if (!empty($user->tasks)) { const distanceMessage = document.getElementById("distanceMessage"); + // مختصات دفتر / شرکت const referenceLat = 32.6504751; const referenceLng = 51.6936045; + // شعاع مجاز (متر) — GPS داخل ساختمان معمولاً ۲۰–۸۰ متر خطا دارد + const OFFICE_RADIUS_METERS = 100; + const MAX_ACCURACY_CREDIT_METERS = 80; const submitBtn = document.getElementById("submitBtn"); const latInput = document.getElementById("latitude"); @@ -677,57 +681,136 @@ if (!empty($user->tasks)) { return earthRadius * c; } - navigator.geolocation.getCurrentPosition( - position => { - const lat = position.coords.latitude; - const lng = position.coords.longitude; + function isInsideOffice(distanceMeters, accuracyMeters) { + const accuracyCredit = Math.min(Math.max(0, accuracyMeters || 0), MAX_ACCURACY_CREDIT_METERS); + const effectiveDistance = Math.max(0, distanceMeters - accuracyCredit); + return effectiveDistance <= OFFICE_RADIUS_METERS; + } - latInput.value = lat; - lngInput.value = lng; + function applyLocationResult(lat, lng, accuracyMeters) { + latInput.value = lat; + lngInput.value = lng; - const dist = Math.round(getDistanceInMeters(referenceLat, referenceLng, lat, lng)); - distanceInput.value = dist; - if (dist < 20) { - distanceMessage.textContent = `مکان: شرکت`; - } else { - distanceMessage.textContent = `مکان: خارج از شرکت`; - } + const dist = Math.round(getDistanceInMeters(referenceLat, referenceLng, lat, lng)); + distanceInput.value = dist; + const inside = isInsideOffice(dist, accuracyMeters); + const accuracyText = accuracyMeters + ? ` · دقت ±${Math.round(accuracyMeters)}م` + : ''; - distanceMessage.classList.remove("bg-gray-400", "bg-blue-500", "bg-green-500", "bg-orange-500", "cursor-not-allowed"); - if (dist < 10) { - distanceMessage.classList.add("text-green-500"); - } else if (dist < 50) { - distanceMessage.classList.add("text-blue-500"); - } else if (dist > 2000) { - distanceMessage.classList.add("text-orange-500"); - } else { - distanceMessage.classList.add("text-gray-400"); - } - submitBtn.disabled = false; - submitBtn.classList.remove("bg-gray-400", "cursor-not-allowed"); - submitBtn.classList.add("bg-cyan-500", "hover:bg-cyan-700", "cursor-pointer"); + distanceMessage.textContent = inside + ? `مکان: شرکت (~${dist}م)${accuracyText}` + : `مکان: خارج از شرکت (~${dist}م)${accuracyText}`; - }, - error => { - const lat = 32.6520532791536; // از مختصات ثابت استفاده کنید - const lng = 51.68472614422439; // از مختصات ثابت استفاده کنید + distanceMessage.classList.remove( + "bg-gray-400", "bg-blue-500", "bg-green-500", "bg-orange-500", + "cursor-not-allowed", "text-green-500", "text-blue-500", + "text-orange-500", "text-gray-400" + ); + if (inside) { + distanceMessage.classList.add("text-green-500"); + } else if (dist <= OFFICE_RADIUS_METERS * 2) { + distanceMessage.classList.add("text-blue-500"); + } else if (dist > 2000) { + distanceMessage.classList.add("text-orange-500"); + } else { + distanceMessage.classList.add("text-gray-400"); + } - const dist = Math.round(getDistanceInMeters(referenceLat, referenceLng, lat, lng)); - distanceInput.value = dist; - distanceMessage.textContent = `مکان: خارج از شرکت`; + submitBtn.disabled = false; + submitBtn.classList.remove("bg-gray-400", "cursor-not-allowed"); + submitBtn.classList.add("bg-cyan-500", "hover:bg-cyan-700", "cursor-pointer"); + } - distanceMessage.classList.remove("bg-gray-400", "bg-blue-500", "bg-green-500", "bg-orange-500", "cursor-not-allowed"); + function locateUser() { + if (!navigator.geolocation) { + distanceMessage.textContent = 'موقعیت‌یاب در دسترس نیست'; distanceMessage.classList.add("text-orange-500"); submitBtn.disabled = false; submitBtn.classList.remove("bg-gray-400", "cursor-not-allowed"); submitBtn.classList.add("bg-cyan-500", "hover:bg-cyan-700", "cursor-pointer"); - - // alert("⛔ خطا در دریافت موقعیت مکانی: " + error.message), { - // enableHighAccuracy: true, - // timeout: 10000, - // maximumAge: 0 + return; } - ); + + distanceMessage.textContent = 'در حال بررسی موقعیت...'; + distanceMessage.classList.remove("text-green-500", "text-blue-500", "text-orange-500", "text-gray-400"); + + const geoOptions = { + enableHighAccuracy: true, + timeout: 20000, + maximumAge: 0, + }; + + let bestReading = null; + let watchId = null; + let finished = false; + + const finishWithBest = () => { + if (finished) return; + finished = true; + if (watchId !== null) { + navigator.geolocation.clearWatch(watchId); + } + if (bestReading) { + applyLocationResult( + bestReading.lat, + bestReading.lng, + bestReading.accuracy + ); + } + }; + + const considerReading = (position) => { + const lat = position.coords.latitude; + const lng = position.coords.longitude; + const accuracy = position.coords.accuracy || 9999; + const dist = getDistanceInMeters(referenceLat, referenceLng, lat, lng); + const score = dist + Math.min(accuracy, 150); + + if (!bestReading || score < bestReading.score) { + bestReading = { lat, lng, accuracy, dist, score }; + } + + // اگر با دقت خوب داخل محدوده شدیم، زودتر تمام کن + if (isInsideOffice(dist, accuracy) && accuracy <= 60) { + finishWithBest(); + } + }; + + navigator.geolocation.getCurrentPosition( + (position) => { + considerReading(position); + // چند ثانیه watch برای خواندن دقیق‌تر داخل ساختمان + watchId = navigator.geolocation.watchPosition( + considerReading, + () => finishWithBest(), + geoOptions + ); + setTimeout(finishWithBest, 5000); + }, + () => { + // تلاش دوم بدون الزام دقت بالا + navigator.geolocation.getCurrentPosition( + (position) => { + considerReading(position); + finishWithBest(); + }, + () => { + distanceMessage.textContent = 'موقعیت دریافت نشد — می‌توانید ورود بزنید'; + distanceMessage.classList.add("text-orange-500"); + distanceInput.value = ''; + submitBtn.disabled = false; + submitBtn.classList.remove("bg-gray-400", "cursor-not-allowed"); + submitBtn.classList.add("bg-cyan-500", "hover:bg-cyan-700", "cursor-pointer"); + }, + { enableHighAccuracy: false, timeout: 10000, maximumAge: 60000 } + ); + }, + geoOptions + ); + } + + locateUser(); const attendanceForm = document.getElementById('attendanceForm'); const output = document.getElementById('output');