gps_system/resources/views/livewire/live-tracking.blade.php.backup
sackey 6b878bb0a0
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
Initial commit
2025-09-12 16:19:56 +00:00

352 lines
16 KiB
Plaintext

<div class="space-y-6">
<div class="space-y-6" wire:key="live-tracking-component">
{{-- Page Header --}}
<div class="flex items-center justify-between">
<div>
<flux:heading size="lg">Live Tracking</flux:heading>
<flux:subheading>Real-time GPS tracking and monitoring</flux:subheading>
</div>
<div class="flex space-x-3">
<flux:button wire:click="refreshData" variant="ghost" size="sm" icon="arrow-path">
Refresh
</flux:button>
<flux:button wire:click="toggleAutoRefresh" variant="{{ $autoRefresh ? 'primary' : 'ghost' }}" size="sm" icon="play">
{{ $autoRefresh ? 'Auto Refresh ON' : 'Auto Refresh OFF' }}
</flux:button>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
{{-- Device List Panel --}}
<div class="lg:col-span-1">
<div class="bg-white shadow rounded-lg border border-zinc-200">
<div class="px-4 py-5 sm:p-6">
<div class="flex items-center justify-between mb-4">
<flux:heading size="base">Devices</flux:heading>
<flux:button wire:click="toggleAllDevices" variant="ghost" size="xs">
{{ $allDevicesSelected ? 'Deselect All' : 'Select All' }}
</flux:button>
</div>
<div class="space-y-2 max-h-96 overflow-y-auto">
@foreach($devices as $device)
<div class="flex items-center justify-between p-3 rounded-lg border {{ in_array($device->id, $selectedDevices) ? 'border-blue-500 bg-blue-50' : 'border-gray-200' }}">
<div class="flex items-center space-x-3">
<input type="checkbox"
wire:click="toggleDevice({{ $device->id }})"
{{ in_array($device->id, $selectedDevices) ? 'checked' : '' }}
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<div class="flex-1">
<div class="flex items-center space-x-2">
<span class="font-medium text-sm">{{ $device->name }}</span>
@php
$deviceData = collect($deviceDetails)->firstWhere('device_id', $device->id);
$status = $deviceData['status'] ?? 'offline';
@endphp
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium
{{ $status === 'online' ? 'bg-green-100 text-green-800' :
($status === 'idle' ? 'bg-yellow-100 text-yellow-800' : 'bg-red-100 text-red-800') }}">
{{ ucfirst($status) }}
</span>
</div>
@if($deviceData)
<div class="text-xs text-gray-500 mt-1">
{{ $deviceData['speed'] }} km/h • {{ \Carbon\Carbon::parse($deviceData['last_update'])->diffForHumans() }}
</div>
@endif
</div>
</div>
<div class="flex space-x-1">
@if($deviceData)
<flux:button wire:click="selectDevice({{ $device->id }})" variant="ghost" size="xs" icon="eye">
</flux:button>
<flux:button wire:click="followDevice({{ $device->id }})"
variant="{{ $followDevice === $device->id ? 'primary' : 'ghost' }}"
size="xs" icon="map-pin">
</flux:button>
@endif
</div>
</div>
@endforeach
</div>
</div>
</div>
{{-- Map Controls --}}
<div class="bg-white shadow rounded-lg border border-zinc-200 mt-4">
<div class="px-4 py-5 sm:p-6">
<flux:heading size="base" class="mb-4">Map Controls</flux:heading>
<div class="space-y-4">
<div>
<flux:label>Map Style</flux:label>
<div class="grid grid-cols-2 gap-2 mt-2">
<flux:button wire:click="changeMapStyle('streets')"
variant="{{ $mapStyle === 'streets' ? 'primary' : 'ghost' }}"
size="sm">Streets</flux:button>
<flux:button wire:click="changeMapStyle('satellite')"
variant="{{ $mapStyle === 'satellite' ? 'primary' : 'ghost' }}"
size="sm">Satellite</flux:button>
</div>
</div>
<div>
<div class="flex items-center justify-between">
<flux:label>Show Trails</flux:label>
<flux:button wire:click="toggleTrails"
variant="{{ $showTrails ? 'primary' : 'ghost' }}"
size="xs">{{ $showTrails ? 'ON' : 'OFF' }}</flux:button>
</div>
@if($showTrails)
<div class="mt-2">
<flux:label>Trail Duration (hours)</flux:label>
<flux:select wire:model.live="trailDuration" size="sm">
<option value="1">1 Hour</option>
<option value="6">6 Hours</option>
<option value="12">12 Hours</option>
<option value="24">24 Hours</option>
<option value="48">48 Hours</option>
</flux:select>
</div>
@endif
</div>
<div>
<flux:label>Auto Refresh Interval</flux:label>
<flux:select wire:model.live="refreshInterval" size="sm">
<option value="10">10 seconds</option>
<option value="30">30 seconds</option>
<option value="60">1 minute</option>
<option value="300">5 minutes</option>
</flux:select>
</div>
<flux:button wire:click="autoCenter" variant="outline" size="sm" class="w-full" icon="map">
Center Map
</flux:button>
</div>
</div>
</div>
</div>
{{-- Map Area --}}
<div class="lg:col-span-3">
<div class="bg-white shadow rounded-lg border border-zinc-200 h-[600px] relative">
<div id="liveTrackingMap" class="w-full h-full rounded-lg"></div>
{{-- Map Overlay Info --}}
@if($selectedDevice)
@php
$deviceData = collect($deviceDetails)->firstWhere('device_id', $selectedDevice);
@endphp
@if($deviceData)
<div class="absolute top-4 right-4 bg-white shadow-lg rounded-lg p-4 border border-zinc-200 max-w-sm">
<div class="flex items-center justify-between mb-2">
<flux:heading size="sm">{{ $deviceData['device_name'] }}</flux:heading>
<flux:button wire:click="$set('selectedDevice', null)" variant="ghost" size="xs" icon="x-mark">
</flux:button>
</div>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="text-gray-600">Status:</span>
<span class="font-medium capitalize">{{ $deviceData['status'] }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Speed:</span>
<span class="font-medium">{{ $deviceData['speed'] }} km/h</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Direction:</span>
<span class="font-medium">{{ $deviceData['course'] }}°</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600">Last Update:</span>
<span class="font-medium">{{ \Carbon\Carbon::parse($deviceData['last_update'])->format('H:i:s') }}</span>
</div>
@if($deviceData['address'])
<div class="pt-2 border-t border-gray-200">
<span class="text-gray-600">Address:</span>
<p class="text-sm font-medium">{{ $deviceData['address'] }}</p>
</div>
@endif
</div>
</div>
@endif
@endif
</div>
</div>
</div>
{{-- Load Leaflet Assets --}}
@assets
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
@endassets
@script
<script>
let liveTrackingMap = null;
let liveTrackingMarkers = {};
let liveTrackingTrails = {};
let autoRefreshInterval = null;
// Initialize the map
function initLiveTrackingMap() {
if (liveTrackingMap) {
liveTrackingMap.remove();
}
const mapCenter = @json($mapCenter);
const zoomLevel = @json($zoomLevel);
const mapStyle = @json($mapStyle);
liveTrackingMap = L.map('liveTrackingMap').setView([mapCenter.lat, mapCenter.lng], zoomLevel);
updateMapStyle(mapStyle);
updateMarkers();
setupAutoRefresh();
}
function updateMapStyle(style) {
if (liveTrackingMap.tileLayer) {
liveTrackingMap.removeLayer(liveTrackingMap.tileLayer);
}
let tileUrl, attribution;
if (style === 'satellite') {
tileUrl = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}';
attribution = '&copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community';
} else {
tileUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
attribution = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
}
liveTrackingMap.tileLayer = L.tileLayer(tileUrl, {
attribution: attribution,
maxZoom: 19
}).addTo(liveTrackingMap);
}
function updateMarkers() {
if (!liveTrackingMap) return;
const devices = @json($deviceDetails);
// Clear existing markers
Object.values(liveTrackingMarkers).forEach(marker => {
liveTrackingMap.removeLayer(marker);
});
liveTrackingMarkers = {};
// Add new markers
devices.forEach(device => {
if (device.latitude && device.longitude) {
const icon = getDeviceIcon(device.status);
const marker = L.marker([device.latitude, device.longitude], { icon })
.addTo(liveTrackingMap);
const popupContent = `
<div class="p-2">
<h3 class="font-semibold">${device.device_name}</h3>
<p><strong>Speed:</strong> ${device.speed} km/h</p>
<p><strong>Status:</strong> ${device.status}</p>
<p><strong>Last Update:</strong> ${new Date(device.last_update).toLocaleString()}</p>
${device.address ? `<p><strong>Address:</strong> ${device.address}</p>` : ''}
</div>
`;
marker.bindPopup(popupContent);
liveTrackingMarkers[device.device_id] = marker;
}
});
}
function getDeviceIcon(status) {
const colors = {
online: '#10b981',
idle: '#f59e0b',
offline: '#ef4444'
};
const color = colors[status] || colors.offline;
return L.divIcon({
className: 'device-marker',
html: `<div style="
width: 16px;
height: 16px;
border-radius: 50%;
background-color: ${color};
border: 2px solid white;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
"></div>`,
iconSize: [16, 16],
iconAnchor: [8, 8]
});
}
function setupAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
}
const autoRefresh = @json($autoRefresh);
const refreshInterval = @json($refreshInterval);
if (autoRefresh) {
autoRefreshInterval = setInterval(() => {
$wire.refreshData();
}, refreshInterval * 1000);
}
}
// Initialize map when component loads
initLiveTrackingMap();
// Listen for Livewire events
$wire.on('refreshMap', () => {
updateMarkers();
});
$wire.on('changeMapStyle', (style) => {
updateMapStyle(style);
});
$wire.on('centerMap', (center, zoom) => {
if (liveTrackingMap) {
liveTrackingMap.setView([center.lat, center.lng], zoom || 13);
}
});
// Cleanup when component is destroyed
document.addEventListener('livewire:navigate', function() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
}
if (liveTrackingMap) {
liveTrackingMap.remove();
liveTrackingMap = null;
}
});
</script>
@endscript
<style>
.device-marker {
background: transparent;
border: none;
}
#liveTrackingMap {
z-index: 1;
}
</style>
</div>