| Line 204... |
Line 204... |
| 204 |
<div class="modal-body" style="padding:0;">
|
204 |
<div class="modal-body" style="padding:0;">
|
| 205 |
<div id="liveTrackingInfo" style="padding:8px 15px; background:#f8f9fa; border-bottom:1px solid #ddd; font-size:13px;"></div>
|
205 |
<div id="liveTrackingInfo" style="padding:8px 15px; background:#f8f9fa; border-bottom:1px solid #ddd; font-size:13px;"></div>
|
| 206 |
<div id="liveTrackingMap" style="height:500px; width:100%;"></div>
|
206 |
<div id="liveTrackingMap" style="height:500px; width:100%;"></div>
|
| 207 |
</div>
|
207 |
</div>
|
| 208 |
<div class="modal-footer">
|
208 |
<div class="modal-footer">
|
| - |
|
209 |
<label style="float:left; margin-top:7px; font-weight:normal; font-size:13px;">
|
| - |
|
210 |
<input type="checkbox" id="autoRefreshToggle" checked style="margin-right:5px;"> Auto-refresh (30s)
|
| - |
|
211 |
</label>
|
| 209 |
<button type="button" class="btn btn-primary" id="refreshTrackingBtn"><i class="fa fa-refresh"></i> Refresh</button>
|
212 |
<button type="button" class="btn btn-primary" id="refreshTrackingBtn"><i class="fa fa-refresh"></i> Refresh</button>
|
| 210 |
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
213 |
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
| 211 |
</div>
|
214 |
</div>
|
| 212 |
</div>
|
215 |
</div>
|
| 213 |
</div>
|
216 |
</div>
|
| Line 314... |
Line 317... |
| 314 |
liveTrackingMap._markers.forEach(function (m) { m.setMap(null); });
|
317 |
liveTrackingMap._markers.forEach(function (m) { m.setMap(null); });
|
| 315 |
}
|
318 |
}
|
| 316 |
if (liveTrackingMap._polyline) {
|
319 |
if (liveTrackingMap._polyline) {
|
| 317 |
liveTrackingMap._polyline.setMap(null);
|
320 |
liveTrackingMap._polyline.setMap(null);
|
| 318 |
}
|
321 |
}
|
| - |
|
322 |
if (liveTrackingMap._visitRoute) {
|
| - |
|
323 |
liveTrackingMap._visitRoute.setMap(null);
|
| - |
|
324 |
}
|
| 319 |
if (liveTrackingMap._currentMarker) {
|
325 |
if (liveTrackingMap._currentMarker) {
|
| 320 |
liveTrackingMap._currentMarker.setMap(null);
|
326 |
liveTrackingMap._currentMarker.setMap(null);
|
| 321 |
}
|
327 |
}
|
| 322 |
liveTrackingMap._markers = [];
|
328 |
liveTrackingMap._markers = [];
|
| 323 |
|
329 |
|
| Line 361... |
Line 367... |
| 361 |
})(marker, infoContent);
|
367 |
})(marker, infoContent);
|
| 362 |
|
368 |
|
| 363 |
liveTrackingMap._markers.push(marker);
|
369 |
liveTrackingMap._markers.push(marker);
|
| 364 |
}
|
370 |
}
|
| 365 |
|
371 |
|
| - |
|
372 |
// Draw visit-to-visit route (dashed line with arrows)
|
| - |
|
373 |
var visitCoords = [];
|
| - |
|
374 |
for (var vi = 0; vi < visits.length; vi++) {
|
| - |
|
375 |
var vv = visits[vi];
|
| - |
|
376 |
if (!vv.lat || !vv.lng) continue;
|
| - |
|
377 |
var vLat = parseFloat(vv.lat);
|
| - |
|
378 |
var vLng = parseFloat(vv.lng);
|
| - |
|
379 |
if (isNaN(vLat) || isNaN(vLng) || (vLat === 0 && vLng === 0)) continue;
|
| - |
|
380 |
visitCoords.push({ lat: vLat, lng: vLng });
|
| - |
|
381 |
}
|
| - |
|
382 |
if (visitCoords.length > 1) {
|
| - |
|
383 |
liveTrackingMap._visitRoute = new google.maps.Polyline({
|
| - |
|
384 |
path: visitCoords,
|
| - |
|
385 |
geodesic: true,
|
| - |
|
386 |
strokeColor: '#8e44ad',
|
| - |
|
387 |
strokeOpacity: 0,
|
| - |
|
388 |
strokeWeight: 3,
|
| - |
|
389 |
icons: [{
|
| - |
|
390 |
icon: { path: 'M 0,-1 0,1', strokeOpacity: 0.6, strokeWeight: 3, scale: 3 },
|
| - |
|
391 |
offset: '0',
|
| - |
|
392 |
repeat: '16px'
|
| - |
|
393 |
}, {
|
| - |
|
394 |
icon: { path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW, strokeOpacity: 0.8, scale: 3 },
|
| - |
|
395 |
offset: '50%',
|
| - |
|
396 |
repeat: '200px'
|
| - |
|
397 |
}],
|
| - |
|
398 |
map: liveTrackingMap
|
| - |
|
399 |
});
|
| - |
|
400 |
}
|
| - |
|
401 |
|
| 366 |
if (pathPoints.length > 0) {
|
402 |
if (pathPoints.length > 0) {
|
| 367 |
var pathCoords = [];
|
403 |
var pathCoords = [];
|
| 368 |
for (var j = 0; j < pathPoints.length; j++) {
|
404 |
for (var j = 0; j < pathPoints.length; j++) {
|
| 369 |
var pt = pathPoints[j];
|
405 |
var pt = pathPoints[j];
|
| 370 |
var pLat = parseFloat(pt.lat);
|
406 |
var pLat = parseFloat(pt.lat);
|
| Line 415... |
Line 451... |
| 415 |
liveTrackingMap.fitBounds(bounds);
|
451 |
liveTrackingMap.fitBounds(bounds);
|
| 416 |
}
|
452 |
}
|
| 417 |
|
453 |
|
| 418 |
var totalVisits = visits.filter(function (v) { return v.markType !== 'PUNCHIN' && v.markType !== 'PUNCHOUT'; }).length;
|
454 |
var totalVisits = visits.filter(function (v) { return v.markType !== 'PUNCHIN' && v.markType !== 'PUNCHOUT'; }).length;
|
| 419 |
var completedVisits = visits.filter(function (v) { return v.markType === 'CHECKIN-CHECKOUT' || v.markType === 'CHECKOUT'; }).length;
|
455 |
var completedVisits = visits.filter(function (v) { return v.markType === 'CHECKIN-CHECKOUT' || v.markType === 'CHECKOUT'; }).length;
|
| - |
|
456 |
var now = new Date();
|
| - |
|
457 |
var timeStr = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0') + ':' + now.getSeconds().toString().padStart(2, '0');
|
| 420 |
$('#liveTrackingInfo').html(
|
458 |
$('#liveTrackingInfo').html(
|
| 421 |
'<strong>' + username + '</strong> | ' +
|
459 |
'<strong>' + username + '</strong> | ' +
|
| 422 |
'Visits: <strong>' + totalVisits + '</strong> | ' +
|
460 |
'Visits: <strong>' + totalVisits + '</strong> | ' +
|
| 423 |
'Completed: <strong>' + completedVisits + '</strong> | ' +
|
461 |
'Completed: <strong>' + completedVisits + '</strong> | ' +
|
| 424 |
'Path Points: <strong>' + pathPoints.length + '</strong>' +
|
462 |
'Path Points: <strong>' + pathPoints.length + '</strong>' +
|
| 425 |
(pathPoints.length > 0 ? ' | <span style="color:#e74c3c;"><i class="fa fa-circle"></i></span> Last Known Location' : '')
|
463 |
(pathPoints.length > 0 ? ' | <span style="color:#e74c3c;"><i class="fa fa-circle"></i></span> Last Known' : '') +
|
| - |
|
464 |
' | <span style="color:#3498db;">━</span> GPS Path' +
|
| - |
|
465 |
(visitCoords.length > 1 ? ' | <span style="color:#8e44ad;">┅</span> Visit Route' : '') +
|
| - |
|
466 |
' | <span style="color:#999;">Updated: ' + timeStr + '</span>'
|
| 426 |
);
|
467 |
);
|
| 427 |
}
|
468 |
}
|
| 428 |
|
469 |
|
| 429 |
function ensureGoogleMaps(callback) {
|
470 |
function ensureGoogleMaps(callback) {
|
| 430 |
if (window.google && window.google.maps) {
|
471 |
if (window.google && window.google.maps) {
|
| Line 435... |
Line 476... |
| 435 |
s.src = 'https://maps.googleapis.com/maps/api/js?key=AIzaSyAckO0y4Z6WhBOuMjNjioWLSYZDhGEvGBc&v=weekly';
|
476 |
s.src = 'https://maps.googleapis.com/maps/api/js?key=AIzaSyAckO0y4Z6WhBOuMjNjioWLSYZDhGEvGBc&v=weekly';
|
| 436 |
s.onload = callback;
|
477 |
s.onload = callback;
|
| 437 |
document.head.appendChild(s);
|
478 |
document.head.appendChild(s);
|
| 438 |
}
|
479 |
}
|
| 439 |
|
480 |
|
| - |
|
481 |
var liveTrackingUsername = '';
|
| - |
|
482 |
|
| - |
|
483 |
function startAutoRefresh() {
|
| - |
|
484 |
stopAutoRefresh();
|
| - |
|
485 |
if ($('#autoRefreshToggle').is(':checked') && liveTrackingUserId) {
|
| - |
|
486 |
liveTrackingInterval = setInterval(function () {
|
| - |
|
487 |
loadLiveTracking(liveTrackingUserId, liveTrackingUsername);
|
| - |
|
488 |
}, 30000);
|
| - |
|
489 |
}
|
| - |
|
490 |
}
|
| - |
|
491 |
|
| - |
|
492 |
function stopAutoRefresh() {
|
| - |
|
493 |
if (liveTrackingInterval) {
|
| - |
|
494 |
clearInterval(liveTrackingInterval);
|
| - |
|
495 |
liveTrackingInterval = null;
|
| - |
|
496 |
}
|
| - |
|
497 |
}
|
| - |
|
498 |
|
| 440 |
$(document).on('click.livetrack', '.live-tracking-btn', function () {
|
499 |
$(document).on('click.livetrack', '.live-tracking-btn', function () {
|
| 441 |
liveTrackingUserId = $(this).data('userid');
|
500 |
liveTrackingUserId = $(this).data('userid');
|
| 442 |
var username = $(this).data('username');
|
501 |
liveTrackingUsername = $(this).data('username');
|
| 443 |
$('#liveTrackingModalTitle').html('<i class="fa fa-map-marker"></i> Live Tracking - ' + username);
|
502 |
$('#liveTrackingModalTitle').html('<i class="fa fa-map-marker"></i> Live Tracking - ' + liveTrackingUsername);
|
| 444 |
$('#liveTrackingInfo').html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Loading...</div>');
|
503 |
$('#liveTrackingInfo').html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Loading...</div>');
|
| 445 |
$('#liveTrackingModal').modal('show');
|
504 |
$('#liveTrackingModal').modal('show');
|
| 446 |
|
505 |
|
| 447 |
setTimeout(function () {
|
506 |
setTimeout(function () {
|
| 448 |
liveTrackingMap = null;
|
507 |
liveTrackingMap = null;
|
| 449 |
ensureGoogleMaps(function () {
|
508 |
ensureGoogleMaps(function () {
|
| 450 |
loadLiveTracking(liveTrackingUserId, username);
|
509 |
loadLiveTracking(liveTrackingUserId, liveTrackingUsername);
|
| - |
|
510 |
startAutoRefresh();
|
| 451 |
});
|
511 |
});
|
| 452 |
}, 300);
|
512 |
}, 300);
|
| 453 |
});
|
513 |
});
|
| 454 |
|
514 |
|
| 455 |
$(document).on('click.livetrack', '#refreshTrackingBtn', function () {
|
515 |
$(document).on('click.livetrack', '#refreshTrackingBtn', function () {
|
| 456 |
if (liveTrackingUserId) {
|
516 |
if (liveTrackingUserId) {
|
| 457 |
var username = $('#liveTrackingModalTitle').text().replace('Live Tracking - ', '').trim();
|
- |
|
| 458 |
$('#liveTrackingInfo').html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Refreshing...</div>');
|
517 |
$('#liveTrackingInfo').html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Refreshing...</div>');
|
| 459 |
loadLiveTracking(liveTrackingUserId, username);
|
518 |
loadLiveTracking(liveTrackingUserId, liveTrackingUsername);
|
| 460 |
}
|
519 |
}
|
| 461 |
});
|
520 |
});
|
| 462 |
|
521 |
|
| 463 |
$('#liveTrackingModal').on('hidden.bs.modal', function () {
|
522 |
$(document).on('change', '#autoRefreshToggle', function () {
|
| 464 |
if (liveTrackingInterval) {
|
523 |
if ($(this).is(':checked')) {
|
| 465 |
clearInterval(liveTrackingInterval);
|
524 |
startAutoRefresh();
|
| - |
|
525 |
} else {
|
| 466 |
liveTrackingInterval = null;
|
526 |
stopAutoRefresh();
|
| 467 |
}
|
527 |
}
|
| 468 |
});
|
528 |
});
|
| - |
|
529 |
|
| - |
|
530 |
$('#liveTrackingModal').on('hidden.bs.modal', function () {
|
| - |
|
531 |
stopAutoRefresh();
|
| - |
|
532 |
liveTrackingUserId = null;
|
| - |
|
533 |
});
|
| 469 |
</script>
|
534 |
</script>
|