255 lines
14 KiB
PHP
255 lines
14 KiB
PHP
<div class="space-y-6">
|
|
{{-- Page Header --}}
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h1 class="text-2xl font-semibold text-gray-900">Dashboard</h1>
|
|
<p class="text-sm text-gray-600">Overview of your GPS tracking system</p>
|
|
</div>
|
|
<button wire:click="refreshData" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50">
|
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
Refresh Data
|
|
</button>
|
|
</div>
|
|
|
|
{{-- Statistics Cards --}}
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
{{-- Total Devices --}}
|
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
<div class="p-5">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-8 w-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-5 w-0 flex-1">
|
|
<dl>
|
|
<dt class="text-sm font-medium text-gray-500 truncate">Total Devices</dt>
|
|
<dd class="text-lg font-medium text-gray-900">{{ $stats['total_devices'] ?? 0 }}</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Online Devices --}}
|
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
<div class="p-5">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-8 w-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-5 w-0 flex-1">
|
|
<dl>
|
|
<dt class="text-sm font-medium text-gray-500 truncate">Online</dt>
|
|
<dd class="text-lg font-medium text-green-600">{{ $stats['online_devices'] ?? 0 }}</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Offline Devices --}}
|
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
<div class="p-5">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-8 w-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L5.636 5.636" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-5 w-0 flex-1">
|
|
<dl>
|
|
<dt class="text-sm font-medium text-gray-500 truncate">Offline</dt>
|
|
<dd class="text-lg font-medium text-red-600">{{ $stats['offline_devices'] ?? 0 }}</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Recent Alerts --}}
|
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
<div class="p-5">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<svg class="h-8 w-8 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L4.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
</svg>
|
|
</div>
|
|
<div class="ml-5 w-0 flex-1">
|
|
<dl>
|
|
<dt class="text-sm font-medium text-gray-500 truncate">Recent Alerts</dt>
|
|
<dd class="text-lg font-medium text-yellow-600">{{ $stats['recent_alerts'] ?? 0 }}</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{{-- Device Status Widget --}}
|
|
<div class="bg-white shadow rounded-lg">
|
|
<div class="px-4 py-5 sm:p-6">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Device Status</h3>
|
|
<div class="space-y-4">
|
|
@forelse($devicesStatus as $device)
|
|
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
|
<div class="flex items-center space-x-3">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-3 h-3 rounded-full {{ $device['status'] === 'online' ? 'bg-green-500' : ($device['status'] === 'offline' ? 'bg-red-500' : 'bg-gray-500') }}"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-900">{{ $device['name'] }}</div>
|
|
<div class="text-xs text-gray-500">
|
|
@if($device['last_update'])
|
|
Last seen: {{ \Carbon\Carbon::parse($device['last_update'])->diffForHumans() }}
|
|
@else
|
|
Never seen
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ $device['status'] === 'online' ? 'bg-green-100 text-green-800' : ($device['status'] === 'offline' ? 'bg-red-100 text-red-800' : 'bg-gray-100 text-gray-800') }}">
|
|
{{ ucfirst($device['status']) }}
|
|
</span>
|
|
</div>
|
|
@empty
|
|
<div class="text-center py-8 text-gray-500">
|
|
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
|
</svg>
|
|
<h3 class="mt-2 text-sm font-medium text-gray-900">No devices</h3>
|
|
<p class="mt-1 text-sm text-gray-500">Get started by adding your first device.</p>
|
|
<div class="mt-6">
|
|
<a href="{{ route('devices.index') }}" class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700">
|
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
|
</svg>
|
|
Add Device
|
|
</a>
|
|
</div>
|
|
</div>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Recent Events --}}
|
|
<div class="bg-white shadow rounded-lg">
|
|
<div class="px-4 py-5 sm:p-6">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Recent Events</h3>
|
|
<div class="space-y-3">
|
|
@forelse($recentEvents as $event)
|
|
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
|
<div class="flex items-center space-x-3">
|
|
<div class="flex-shrink-0">
|
|
<div class="w-3 h-3 rounded-full bg-{{ $event['color'] }}-500"></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-900">{{ $event['type'] }}</div>
|
|
<div class="text-xs text-gray-500">
|
|
{{ $event['device_name'] }} • {{ \Carbon\Carbon::parse($event['event_time'])->diffForHumans() }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center space-x-2">
|
|
@if(!$event['acknowledged'])
|
|
<button wire:click="acknowledgeEvent({{ $event['id'] }})" class="inline-flex items-center px-2.5 py-1.5 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50">
|
|
Acknowledge
|
|
</button>
|
|
@else
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
|
Acknowledged
|
|
</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<div class="text-center py-8 text-gray-500">
|
|
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-5 5-5-5h5v-5a7.5 7.5 0 015.5-7.21" />
|
|
</svg>
|
|
<h3 class="mt-2 text-sm font-medium text-gray-900">No recent events</h3>
|
|
<p class="mt-1 text-sm text-gray-500">Events and alerts will appear here.</p>
|
|
</div>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Live Map Widget --}}
|
|
@if(count($mapDevices) > 0)
|
|
<div class="bg-white shadow rounded-lg">
|
|
<div class="px-4 py-5 sm:p-6">
|
|
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Live Tracking</h3>
|
|
<div id="dashboard-map" class="h-96 rounded-lg bg-gray-100"></div>
|
|
</div>
|
|
</div>
|
|
|
|
@push('scripts')
|
|
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const mapDevices = @json($mapDevices);
|
|
|
|
if (mapDevices.length > 0) {
|
|
// Initialize map
|
|
const map = L.map('dashboard-map').setView([mapDevices[0].latitude, mapDevices[0].longitude], 10);
|
|
|
|
// Add tile layer
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© OpenStreetMap contributors'
|
|
}).addTo(map);
|
|
|
|
// Add device markers
|
|
const bounds = L.latLngBounds();
|
|
|
|
mapDevices.forEach(device => {
|
|
const color = device.status === 'online' ? 'green' :
|
|
device.status === 'offline' ? 'red' : 'gray';
|
|
|
|
const marker = L.circleMarker([device.latitude, device.longitude], {
|
|
color: color,
|
|
fillColor: color,
|
|
fillOpacity: 0.7,
|
|
radius: 8
|
|
}).addTo(map);
|
|
|
|
marker.bindPopup(`
|
|
<strong>${device.name}</strong><br>
|
|
Status: ${device.status}<br>
|
|
Speed: ${device.speed}<br>
|
|
Address: ${device.address}<br>
|
|
Last Update: ${new Date(device.last_update).toLocaleString()}
|
|
`);
|
|
|
|
bounds.extend([device.latitude, device.longitude]);
|
|
});
|
|
|
|
// Fit map to show all devices
|
|
if (mapDevices.length > 1) {
|
|
map.fitBounds(bounds, { padding: [20, 20] });
|
|
}
|
|
}
|
|
});
|
|
|
|
// Listen for data refresh events
|
|
document.addEventListener('livewire:init', () => {
|
|
Livewire.on('data-refreshed', () => {
|
|
location.reload(); // Simple reload for now
|
|
});
|
|
});
|
|
</script>
|
|
@endpush
|
|
@endif
|
|
</div>
|