110 lines
5.2 KiB
PHP
110 lines
5.2 KiB
PHP
<?php
|
|
// views/tracking/map.php
|
|
?>
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
|
|
<div class="mb-4 flex flex-wrap gap-4 justify-between items-end">
|
|
<div>
|
|
<h2 class="text-2xl font-bold text-slate-800 dark:text-white">Live Tracking (พิกัดเจ้าหน้าที่)</h2>
|
|
<p class="text-slate-500 dark:text-slate-400">ติดตามตำแหน่งเจ้าหน้าที่แบบ Real-time</p>
|
|
</div>
|
|
<div class="flex items-center gap-2 bg-emerald-50 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400 px-3 py-1.5 rounded-lg border border-emerald-200 dark:border-emerald-800 text-sm">
|
|
<span class="relative flex h-2.5 w-2.5">
|
|
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
|
<span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500"></span>
|
|
</span>
|
|
กำลังอัปเดต (Live)
|
|
</div>
|
|
</div>
|
|
|
|
<div class="glass-card overflow-hidden h-[70vh] relative border-4 border-white/50 dark:border-slate-800/50">
|
|
<div id="map" class="w-full h-full"></div>
|
|
|
|
<!-- Floating Staff List Panel -->
|
|
<div class="absolute top-4 right-4 z-[400] w-64 glass-card bg-white/90 dark:bg-slate-900/90 shadow-xl max-h-[80%] flex flex-col">
|
|
<div class="px-4 py-3 border-b border-slate-200 dark:border-slate-700 font-medium text-sm flex justify-between items-center">
|
|
เจ้าหน้าที่ในพื้นที่
|
|
<span class="bg-emerald-100 text-emerald-800 dark:bg-emerald-900 text-emerald-300 text-xs px-2 py-0.5 rounded-full" id="staffCount">0</span>
|
|
</div>
|
|
<div class="overflow-y-auto p-2 space-y-1" id="mapStaffList">
|
|
<!-- Populated by JS -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialize Map centered at Koh Samui Hospital approx
|
|
const map = L.map('map').setView([9.537, 99.938], 16);
|
|
|
|
// Modern Map style depending on Dark Mode
|
|
const isDark = document.documentElement.classList.contains('dark');
|
|
const tileUrl = isDark
|
|
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
|
|
: 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png';
|
|
|
|
L.tileLayer(tileUrl, {
|
|
maxZoom: 19,
|
|
attribution: '© OpenStreetMap contributors © CARTO'
|
|
}).addTo(map);
|
|
|
|
const markers = {};
|
|
|
|
function updateStaffLocations() {
|
|
// Mock data
|
|
const staffData = [
|
|
{ id: 1, name: "นายสมชาย ใจดี", lat: 9.5375, lng: 99.9385, status: "available" },
|
|
{ id: 2, name: "นางสาวสมศรี มีสุข", lat: 9.5368, lng: 99.9372, status: "busy" }
|
|
];
|
|
|
|
document.getElementById('staffCount').textContent = staffData.length;
|
|
const listContainer = document.getElementById('mapStaffList');
|
|
listContainer.innerHTML = '';
|
|
|
|
staffData.forEach(staff => {
|
|
// Update List
|
|
const statusColor = staff.status === 'available' ? 'bg-emerald-500' : 'bg-yellow-500';
|
|
const statusText = staff.status === 'available' ? 'ว่าง' : 'ติดงาน';
|
|
|
|
listContainer.innerHTML += `
|
|
<div class="flex items-center gap-2 p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg cursor-pointer transition-colors text-sm" onclick="focusStaff(${staff.lat}, ${staff.lng})">
|
|
<div class="relative flex-shrink-0">
|
|
<img src="api/avatar.php?name=${encodeURIComponent(staff.name)}" class="w-6 h-6 rounded-full object-cover">
|
|
<span class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 border border-white dark:border-slate-800 rounded-full ${statusColor}"></span>
|
|
</div>
|
|
<span class="truncate text-slate-700 dark:text-slate-300 flex-1">${staff.name}</span>
|
|
</div>
|
|
`;
|
|
|
|
// Update Map Markers
|
|
const iconColor = staff.status === 'available' ? 'green' : 'orange';
|
|
const customIcon = L.divIcon({
|
|
className: 'custom-div-icon',
|
|
html: `<div class="w-8 h-8 rounded-full border-2 border-white shadow-lg bg-${iconColor}-500 overflow-hidden"><img src="api/avatar.php?name=${encodeURIComponent(staff.name)}" class="w-full h-full object-cover"></div>`,
|
|
iconSize: [32, 32],
|
|
iconAnchor: [16, 16]
|
|
});
|
|
|
|
if (markers[staff.id]) {
|
|
markers[staff.id].setLatLng([staff.lat, staff.lng]);
|
|
} else {
|
|
markers[staff.id] = L.marker([staff.lat, staff.lng], {icon: customIcon})
|
|
.bindPopup(`<b>${staff.name}</b><br>สถานะ: ${statusText}`)
|
|
.addTo(map);
|
|
}
|
|
});
|
|
}
|
|
|
|
window.focusStaff = function(lat, lng) {
|
|
map.flyTo([lat, lng], 18, {
|
|
animate: true,
|
|
duration: 1.5
|
|
});
|
|
};
|
|
|
|
updateStaffLocations();
|
|
setInterval(updateStaffLocations, 10000); // 10s auto refresh
|
|
});
|
|
</script>
|