gps_system/resources/views/livewire/reports-and-history.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

415 lines
21 KiB
PHP

<div class="space-y-6" wire:key="reports-component">
{{-- Page Header --}}
<div class="flex items-center justify-between">
<div>
<flux:heading size="lg">Reports & History</flux:heading>
<flux:subheading>Generate comprehensive tracking reports and view historical data</flux:subheading>
</div>
<div class="flex space-x-3">
<flux:button wire:click="generateReport" variant="primary" size="sm" icon="document-arrow-down"
wire:loading.attr="disabled">
<span wire:loading.remove>Generate Report</span>
<span wire:loading>Generating...</span>
</flux:button>
<flux:button wire:click="exportData" variant="outline" size="sm" icon="arrow-down-tray">
Export Data
</flux:button>
</div>
</div>
{{-- Report Configuration --}}
<div class="bg-white shadow rounded-lg border border-zinc-200">
<div class="px-4 py-5 sm:p-6">
<flux:heading size="base" class="mb-4">Report Configuration</flux:heading>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div>
<flux:field>
<flux:label>Report Type</flux:label>
<flux:select wire:model.live="reportType" size="sm">
<option value="summary">Summary Report</option>
<option value="detailed">Detailed History</option>
<option value="trips">Trip Analysis</option>
<option value="stops">Stops Report</option>
<option value="geofence">Geofence Events</option>
<option value="speed">Speed Analysis</option>
<option value="fuel">Fuel Consumption</option>
</flux:select>
</flux:field>
</div>
<div>
<flux:field>
<flux:label>Device</flux:label>
<flux:select wire:model.live="selectedDevice" size="sm">
<option value="">All Devices</option>
@foreach($devices as $device)
<option value="{{ $device->id }}">{{ $device->name }}</option>
@endforeach
</flux:select>
</flux:field>
</div>
<div>
<flux:field>
<flux:label>Start Date</flux:label>
<flux:input wire:model.live="startDate" type="datetime-local" size="sm" />
</flux:field>
</div>
<div>
<flux:field>
<flux:label>End Date</flux:label>
<flux:input wire:model.live="endDate" type="datetime-local" size="sm" />
</flux:field>
</div>
</div>
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<flux:field>
<flux:label>Quick Date Range</flux:label>
<div class="grid grid-cols-3 gap-2">
<flux:button wire:click="setDateRange('today')" variant="ghost" size="xs">Today</flux:button>
<flux:button wire:click="setDateRange('yesterday')" variant="ghost" size="xs">Yesterday</flux:button>
<flux:button wire:click="setDateRange('week')" variant="ghost" size="xs">This Week</flux:button>
<flux:button wire:click="setDateRange('month')" variant="ghost" size="xs">This Month</flux:button>
<flux:button wire:click="setDateRange('lastMonth')" variant="ghost" size="xs">Last Month</flux:button>
<flux:button wire:click="setDateRange('custom')" variant="ghost" size="xs">Custom</flux:button>
</div>
</flux:field>
</div>
<div>
<flux:field>
<flux:label>Output Format</flux:label>
<flux:select wire:model.live="outputFormat" size="sm">
<option value="pdf">PDF Report</option>
<option value="excel">Excel Spreadsheet</option>
<option value="csv">CSV Data</option>
<option value="html">HTML View</option>
</flux:select>
</flux:field>
</div>
<div>
<flux:field>
<flux:label>Include Options</flux:label>
<div class="space-y-2">
<label class="flex items-center space-x-2">
<input type="checkbox" wire:model.live="includeMap" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="text-sm">Include Map</span>
</label>
<label class="flex items-center space-x-2">
<input type="checkbox" wire:model.live="includeCharts" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="text-sm">Include Charts</span>
</label>
<label class="flex items-center space-x-2">
<input type="checkbox" wire:model.live="includeEvents" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="text-sm">Include Events</span>
</label>
</div>
</flux:field>
</div>
</div>
</div>
</div>
{{-- Report Summary Stats --}}
@if($reportData)
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<flux:icon name="map" class="w-4 h-4 text-white" />
</div>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-blue-600">Total Distance</p>
<p class="text-lg font-semibold text-blue-900">{{ number_format($reportData['total_distance'] ?? 0, 1) }} km</p>
</div>
</div>
</div>
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
<flux:icon name="clock" class="w-4 h-4 text-white" />
</div>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-green-600">Total Time</p>
<p class="text-lg font-semibold text-green-900">{{ $reportData['total_time'] ?? '0h 0m' }}</p>
</div>
</div>
</div>
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-yellow-500 rounded-full flex items-center justify-center">
<flux:icon name="bolt" class="w-4 h-4 text-white" />
</div>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-yellow-600">Avg Speed</p>
<p class="text-lg font-semibold text-yellow-900">{{ number_format($reportData['avg_speed'] ?? 0, 1) }} km/h</p>
</div>
</div>
</div>
<div class="bg-purple-50 border border-purple-200 rounded-lg p-4">
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-purple-500 rounded-full flex items-center justify-center">
<flux:icon name="pause" class="w-4 h-4 text-white" />
</div>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-purple-600">Total Stops</p>
<p class="text-lg font-semibold text-purple-900">{{ $reportData['total_stops'] ?? 0 }}</p>
</div>
</div>
</div>
</div>
@endif
{{-- Report Content Area --}}
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
{{-- Report Display --}}
<div class="lg:col-span-2">
<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">
{{ ucfirst(str_replace('_', ' ', $reportType)) }} Report
</flux:heading>
@if($reportData)
<div class="text-sm text-gray-500">
{{ \Carbon\Carbon::parse($startDate)->format('M j, Y') }} -
{{ \Carbon\Carbon::parse($endDate)->format('M j, Y') }}
</div>
@endif
</div>
@if($reportData)
@if($reportType === 'summary')
@include('livewire.reports.summary-report')
@elseif($reportType === 'detailed')
@include('livewire.reports.detailed-report')
@elseif($reportType === 'trips')
@include('livewire.reports.trips-report')
@elseif($reportType === 'stops')
@include('livewire.reports.stops-report')
@elseif($reportType === 'geofence')
@include('livewire.reports.geofence-report')
@elseif($reportType === 'speed')
@include('livewire.reports.speed-report')
@elseif($reportType === 'fuel')
@include('livewire.reports.fuel-report')
@endif
@else
<div class="text-center py-12">
<flux:icon name="document-chart-bar" class="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 class="text-lg font-medium text-gray-900 mb-2">No Report Generated</h3>
<p class="text-gray-500 mb-4">Configure your report parameters and click "Generate Report" to view data.</p>
<flux:button wire:click="generateReport" variant="primary" size="sm">
Generate Report
</flux:button>
</div>
@endif
</div>
</div>
</div>
{{-- Historical Timeline & Map --}}
<div class="lg:col-span-1">
{{-- Map Display --}}
@if($includeMap && $reportData)
<div class="bg-white shadow rounded-lg border border-zinc-200 mb-6">
<div class="px-4 py-5 sm:p-6">
<flux:heading size="base" class="mb-4">Route Map</flux:heading>
<div id="reportMap" class="w-full h-64 rounded-lg bg-gray-100"></div>
</div>
</div>
@endif
{{-- Quick Statistics --}}
<div class="bg-white shadow rounded-lg border border-zinc-200">
<div class="px-4 py-5 sm:p-6">
<flux:heading size="base" class="mb-4">Quick Statistics</flux:heading>
@if($reportData)
<div class="space-y-4">
<div class="flex justify-between">
<span class="text-sm text-gray-600">Max Speed</span>
<span class="text-sm font-medium text-gray-900">{{ number_format($reportData['max_speed'] ?? 0, 1) }} km/h</span>
</div>
<div class="flex justify-between">
<span class="text-sm text-gray-600">Min Speed</span>
<span class="text-sm font-medium text-gray-900">{{ number_format($reportData['min_speed'] ?? 0, 1) }} km/h</span>
</div>
<div class="flex justify-between">
<span class="text-sm text-gray-600">Total Events</span>
<span class="text-sm font-medium text-gray-900">{{ $reportData['total_events'] ?? 0 }}</span>
</div>
<div class="flex justify-between">
<span class="text-sm text-gray-600">Driving Time</span>
<span class="text-sm font-medium text-gray-900">{{ $reportData['driving_time'] ?? '0h 0m' }}</span>
</div>
<div class="flex justify-between">
<span class="text-sm text-gray-600">Idle Time</span>
<span class="text-sm font-medium text-gray-900">{{ $reportData['idle_time'] ?? '0h 0m' }}</span>
</div>
@if(isset($reportData['fuel_consumption']))
<div class="flex justify-between">
<span class="text-sm text-gray-600">Fuel Used</span>
<span class="text-sm font-medium text-gray-900">{{ number_format($reportData['fuel_consumption'], 2) }} L</span>
</div>
@endif
</div>
@else
<div class="text-center py-6 text-gray-500">
<div class="text-sm">Generate a report to view statistics</div>
</div>
@endif
</div>
</div>
{{-- Recent Activity --}}
<div class="bg-white shadow rounded-lg border border-zinc-200 mt-6">
<div class="px-4 py-5 sm:p-6">
<flux:heading size="base" class="mb-4">Recent Activity</flux:heading>
@if($recentEvents && count($recentEvents) > 0)
<div class="space-y-3">
@foreach($recentEvents as $event)
<div class="flex items-start space-x-3">
<div class="flex-shrink-0">
@php
$iconClass = match($event['type']) {
'geofenceEnter', 'geofenceExit' => 'bg-blue-500',
'alarm' => 'bg-red-500',
'ignitionOn', 'ignitionOff' => 'bg-green-500',
default => 'bg-gray-500'
};
@endphp
<div class="w-2 h-2 rounded-full {{ $iconClass }}"></div>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900">{{ $event['message'] }}</p>
<p class="text-xs text-gray-500">{{ $event['time'] }}</p>
</div>
</div>
@endforeach
</div>
@else
<div class="text-center py-6 text-gray-500">
<div class="text-sm">No recent activity</div>
</div>
@endif
</div>
</div>
</div>
</div>
{{-- Load map assets if map is included --}}
@if($includeMap)
@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 reportMap = null;
function initReportMap() {
if (reportMap) {
reportMap.remove();
}
const mapCenter = @json($mapCenter ?? ['lat' => 40.7128, 'lng' => -74.0060]);
const reportData = @json($reportData);
if (document.getElementById('reportMap')) {
reportMap = L.map('reportMap').setView([mapCenter.lat, mapCenter.lng], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
maxZoom: 19
}).addTo(reportMap);
// Add route path if available
if (reportData && reportData.route_coordinates) {
const coordinates = reportData.route_coordinates.map(coord => [coord.lat, coord.lng]);
if (coordinates.length > 0) {
// Add route line
L.polyline(coordinates, {
color: 'blue',
weight: 3,
opacity: 0.7
}).addTo(reportMap);
// Add start marker
L.marker(coordinates[0], {
icon: L.divIcon({
className: 'custom-div-icon',
html: '<div class="bg-green-500 w-4 h-4 rounded-full border-2 border-white"></div>',
iconSize: [16, 16],
iconAnchor: [8, 8]
})
}).bindPopup('Start').addTo(reportMap);
// Add end marker
if (coordinates.length > 1) {
L.marker(coordinates[coordinates.length - 1], {
icon: L.divIcon({
className: 'custom-div-icon',
html: '<div class="bg-red-500 w-4 h-4 rounded-full border-2 border-white"></div>',
iconSize: [16, 16],
iconAnchor: [8, 8]
})
}).bindPopup('End').addTo(reportMap);
}
// Fit map to route
reportMap.fitBounds(coordinates);
}
}
}
}
// Initialize map when report data is updated
$wire.on('reportGenerated', () => {
setTimeout(() => {
initReportMap();
}, 100);
});
// Cleanup when component is destroyed
document.addEventListener('livewire:navigate', function() {
if (reportMap) {
reportMap.remove();
reportMap = null;
}
});
</script>
@endscript
@endif
<style>
.custom-div-icon {
background: transparent;
border: none;
}
</style>
</div>