gps_system/resources/views/livewire/dashboard.blade.php
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

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>