fix office location detection radius and GPS accuracy

This commit is contained in:
2026-09-23 13:07:21 +03:30
parent 9e6407d648
commit 694f41968a
2 changed files with 141 additions and 51 deletions
+16 -9
View File
@@ -251,6 +251,8 @@ SVG;
const referenceLat = 32.6504751; const referenceLat = 32.6504751;
const referenceLng = 51.6936045; const referenceLng = 51.6936045;
const OFFICE_RADIUS_METERS = 100;
const MAX_ACCURACY_CREDIT_METERS = 80;
const submitBtn = document.getElementById("submitBtn"); const submitBtn = document.getElementById("submitBtn");
const latInput = document.getElementById("latitude"); const latInput = document.getElementById("latitude");
@@ -278,26 +280,31 @@ SVG;
return earthRadius * c; 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( navigator.geolocation.getCurrentPosition(
position => { position => {
const lat = position.coords.latitude; const lat = position.coords.latitude;
const lng = position.coords.longitude; const lng = position.coords.longitude;
const accuracy = position.coords.accuracy || 0;
latInput.value = lat; latInput.value = lat;
lngInput.value = lng; lngInput.value = lng;
const dist = Math.round(getDistanceInMeters(referenceLat, referenceLng, lat, lng)); const dist = Math.round(getDistanceInMeters(referenceLat, referenceLng, lat, lng));
distanceInput.value = dist; distanceInput.value = dist;
if (dist < 20) { const inside = isInsideOffice(dist, accuracy);
distanceMessage.textContent = `مکان: شرکت`; distanceMessage.textContent = inside
} else { ? `مکان: شرکت (~${dist}م)`
distanceMessage.textContent = `مکان: خارج از شرکت`; : `مکان: خارج از شرکت (~${dist}م)`;
}
distanceMessage.classList.remove("bg-gray-400", "bg-blue-500", "bg-green-500", "bg-orange-500", "cursor-not-allowed"); 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 (dist < 10) { if (inside) {
distanceMessage.classList.add("text-green-500"); distanceMessage.classList.add("text-green-500");
} else if (dist < 50) { } else if (dist < OFFICE_RADIUS_METERS * 2) {
distanceMessage.classList.add("text-blue-500"); distanceMessage.classList.add("text-blue-500");
} else if (dist > 2000) { } else if (dist > 2000) {
distanceMessage.classList.add("text-orange-500"); distanceMessage.classList.add("text-orange-500");
@@ -311,7 +318,7 @@ SVG;
}, },
error => alert("⛔ خطا در دریافت موقعیت مکانی: " + error.message), { error => alert("⛔ خطا در دریافت موقعیت مکانی: " + error.message), {
enableHighAccuracy: true, enableHighAccuracy: true,
timeout: 10000, timeout: 20000,
maximumAge: 0 maximumAge: 0
} }
); );
+125 -42
View File
@@ -311,12 +311,12 @@ if (!empty($user->tasks)) {
</td> </td>
{{-- زمان ورود --}} {{-- زمان ورود --}}
<td style="border-right: 1px solid #ddd; padding: 4px; background-color: {{$item->entered_at && $item->distance ? ($item->requested !== null ? '#fad1d1' :($item->distance < 50 ? '#d1fae5' : '#edae80')) : '' }};"> <td style="border-right: 1px solid #ddd; padding: 4px; background-color: {{$item->entered_at && $item->distance ? ($item->requested !== null ? '#fad1d1' :($item->distance < 100 ? '#d1fae5' : '#edae80')) : '' }};">
{{ $item->entered_at ? \Carbon\Carbon::parse($item->entered_at)->format('H:i') : '-' }} {{ $item->entered_at ? \Carbon\Carbon::parse($item->entered_at)->format('H:i') : '-' }}
</td> </td>
{{-- زمان خروج --}} {{-- زمان خروج --}}
<td style="border-right: 1px solid #ddd; padding: 4px; background-color: {{$item->exited_at && $item->distance_exit ? ($item->requested !== null ? '#fad1d1' :($item->distance_exit < 50 ? '#d1fae5' : '#edae80')) : '' }};"> <td style="border-right: 1px solid #ddd; padding: 4px; background-color: {{$item->exited_at && $item->distance_exit ? ($item->requested !== null ? '#fad1d1' :($item->distance_exit < 100 ? '#d1fae5' : '#edae80')) : '' }};">
{{ $item->exited_at ? \Carbon\Carbon::parse($item->exited_at)->format('H:i') : '-' }} {{ $item->exited_at ? \Carbon\Carbon::parse($item->exited_at)->format('H:i') : '-' }}
</td> </td>
@@ -646,8 +646,12 @@ if (!empty($user->tasks)) {
const distanceMessage = document.getElementById("distanceMessage"); const distanceMessage = document.getElementById("distanceMessage");
// مختصات دفتر / شرکت
const referenceLat = 32.6504751; const referenceLat = 32.6504751;
const referenceLng = 51.6936045; const referenceLng = 51.6936045;
// شعاع مجاز (متر) — GPS داخل ساختمان معمولاً ۲۰–۸۰ متر خطا دارد
const OFFICE_RADIUS_METERS = 100;
const MAX_ACCURACY_CREDIT_METERS = 80;
const submitBtn = document.getElementById("submitBtn"); const submitBtn = document.getElementById("submitBtn");
const latInput = document.getElementById("latitude"); const latInput = document.getElementById("latitude");
@@ -677,57 +681,136 @@ if (!empty($user->tasks)) {
return earthRadius * c; return earthRadius * c;
} }
navigator.geolocation.getCurrentPosition( function isInsideOffice(distanceMeters, accuracyMeters) {
position => { const accuracyCredit = Math.min(Math.max(0, accuracyMeters || 0), MAX_ACCURACY_CREDIT_METERS);
const lat = position.coords.latitude; const effectiveDistance = Math.max(0, distanceMeters - accuracyCredit);
const lng = position.coords.longitude; return effectiveDistance <= OFFICE_RADIUS_METERS;
}
latInput.value = lat; function applyLocationResult(lat, lng, accuracyMeters) {
lngInput.value = lng; latInput.value = lat;
lngInput.value = lng;
const dist = Math.round(getDistanceInMeters(referenceLat, referenceLng, lat, lng)); const dist = Math.round(getDistanceInMeters(referenceLat, referenceLng, lat, lng));
distanceInput.value = dist; distanceInput.value = dist;
if (dist < 20) { const inside = isInsideOffice(dist, accuracyMeters);
distanceMessage.textContent = `مکان: شرکت`; const accuracyText = accuracyMeters
} else { ? ` · دقت ±${Math.round(accuracyMeters)}م`
distanceMessage.textContent = `مکان: خارج از شرکت`; : '';
}
distanceMessage.classList.remove("bg-gray-400", "bg-blue-500", "bg-green-500", "bg-orange-500", "cursor-not-allowed"); distanceMessage.textContent = inside
if (dist < 10) { ? `مکان: شرکت (~${dist}م)${accuracyText}`
distanceMessage.classList.add("text-green-500"); : `مکان: خارج از شرکت (~${dist}م)${accuracyText}`;
} 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.classList.remove(
error => { "bg-gray-400", "bg-blue-500", "bg-green-500", "bg-orange-500",
const lat = 32.6520532791536; // از مختصات ثابت استفاده کنید "cursor-not-allowed", "text-green-500", "text-blue-500",
const lng = 51.68472614422439; // از مختصات ثابت استفاده کنید "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)); submitBtn.disabled = false;
distanceInput.value = dist; submitBtn.classList.remove("bg-gray-400", "cursor-not-allowed");
distanceMessage.textContent = `مکان: خارج از شرکت`; 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"); distanceMessage.classList.add("text-orange-500");
submitBtn.disabled = false; submitBtn.disabled = false;
submitBtn.classList.remove("bg-gray-400", "cursor-not-allowed"); submitBtn.classList.remove("bg-gray-400", "cursor-not-allowed");
submitBtn.classList.add("bg-cyan-500", "hover:bg-cyan-700", "cursor-pointer"); submitBtn.classList.add("bg-cyan-500", "hover:bg-cyan-700", "cursor-pointer");
return;
// alert("⛔ خطا در دریافت موقعیت مکانی: " + error.message), {
// enableHighAccuracy: true,
// timeout: 10000,
// maximumAge: 0
} }
);
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 attendanceForm = document.getElementById('attendanceForm');
const output = document.getElementById('output'); const output = document.getElementById('output');