407 lines
14 KiB
PHP
407 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire;
|
|
|
|
use Livewire\Component;
|
|
use App\Models\Device;
|
|
use App\Models\Position;
|
|
use App\Models\Event;
|
|
use App\Services\TraccarService;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Carbon\Carbon;
|
|
|
|
class ReportsAndHistory extends Component
|
|
{
|
|
public $selectedDevices = [];
|
|
public $startDate = '';
|
|
public $endDate = '';
|
|
public $reportType = 'route';
|
|
public $showMap = true;
|
|
public $includeMap = true;
|
|
public $exportFormat = 'pdf';
|
|
|
|
// Report data
|
|
public $reportData = [];
|
|
public $routeData = [];
|
|
public $summaryStats = [];
|
|
public $eventsData = [];
|
|
public $stopsData = [];
|
|
|
|
// Report settings
|
|
public $includeStops = true;
|
|
public $minStopDuration = 5; // minutes
|
|
public $maxSpeed = 120; // km/h for overspeed detection
|
|
public $groupByDate = false;
|
|
|
|
protected $traccarService;
|
|
|
|
public function boot(TraccarService $traccarService)
|
|
{
|
|
$this->traccarService = $traccarService;
|
|
}
|
|
|
|
public function mount()
|
|
{
|
|
// Set default date range (last 7 days)
|
|
$this->endDate = now()->format('Y-m-d');
|
|
$this->startDate = now()->subDays(7)->format('Y-m-d');
|
|
|
|
// Select all user devices by default
|
|
$deviceQuery = Device::where('user_id', Auth::id());
|
|
|
|
// Check if device groups have users relationship (safely handle missing relationship)
|
|
if (method_exists(\App\Models\DeviceGroup::class, 'users')) {
|
|
$deviceQuery->orWhereHas('group.users', function($subQuery) {
|
|
$subQuery->where('user_id', Auth::id());
|
|
});
|
|
}
|
|
|
|
$this->selectedDevices = $deviceQuery->pluck('id')->toArray();
|
|
}
|
|
|
|
public function generateReport()
|
|
{
|
|
$this->validate([
|
|
'selectedDevices' => 'required|array|min:1',
|
|
'startDate' => 'required|date',
|
|
'endDate' => 'required|date|after_or_equal:startDate',
|
|
]);
|
|
|
|
try {
|
|
switch ($this->reportType) {
|
|
case 'route':
|
|
$this->generateRouteReport();
|
|
break;
|
|
case 'summary':
|
|
$this->generateSummaryReport();
|
|
break;
|
|
case 'events':
|
|
$this->generateEventsReport();
|
|
break;
|
|
case 'stops':
|
|
$this->generateStopsReport();
|
|
break;
|
|
case 'trips':
|
|
$this->generateTripsReport();
|
|
break;
|
|
}
|
|
|
|
session()->flash('success', 'Report generated successfully!');
|
|
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to generate report: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
private function generateRouteReport()
|
|
{
|
|
$startDateTime = Carbon::parse($this->startDate)->startOfDay();
|
|
$endDateTime = Carbon::parse($this->endDate)->endOfDay();
|
|
|
|
$this->routeData = [];
|
|
|
|
foreach ($this->selectedDevices as $deviceId) {
|
|
$device = Device::find($deviceId);
|
|
if (!$device) continue;
|
|
|
|
$positions = Position::where('device_id', $deviceId)
|
|
->whereBetween('device_time', [$startDateTime, $endDateTime])
|
|
->orderBy('device_time')
|
|
->get();
|
|
|
|
if ($positions->count() > 0) {
|
|
$route = [
|
|
'device' => $device,
|
|
'positions' => $positions,
|
|
'start_time' => $positions->first()->device_time,
|
|
'end_time' => $positions->last()->device_time,
|
|
'total_distance' => $this->calculateTotalDistance($positions),
|
|
'max_speed' => $positions->max('speed') ?? 0,
|
|
'avg_speed' => $positions->avg('speed') ?? 0,
|
|
];
|
|
|
|
if ($this->includeStops) {
|
|
$route['stops'] = $this->detectStops($positions);
|
|
}
|
|
|
|
$this->routeData[] = $route;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function generateSummaryReport()
|
|
{
|
|
$startDateTime = Carbon::parse($this->startDate)->startOfDay();
|
|
$endDateTime = Carbon::parse($this->endDate)->endOfDay();
|
|
|
|
$this->summaryStats = [];
|
|
|
|
foreach ($this->selectedDevices as $deviceId) {
|
|
$device = Device::find($deviceId);
|
|
if (!$device) continue;
|
|
|
|
$positions = Position::where('device_id', $deviceId)
|
|
->whereBetween('device_time', [$startDateTime, $endDateTime])
|
|
->get();
|
|
|
|
$events = Event::where('device_id', $deviceId)
|
|
->whereBetween('event_time', [$startDateTime, $endDateTime])
|
|
->get();
|
|
|
|
$totalDistance = $this->calculateTotalDistance($positions);
|
|
$totalTime = $positions->count() > 1 ?
|
|
$positions->last()->device_time->diffInMinutes($positions->first()->device_time) : 0;
|
|
|
|
$this->summaryStats[] = [
|
|
'device' => $device,
|
|
'total_distance' => $totalDistance,
|
|
'total_time' => $totalTime,
|
|
'avg_speed' => $totalTime > 0 ? ($totalDistance / ($totalTime / 60)) : 0,
|
|
'max_speed' => $positions->max('speed') ?? 0,
|
|
'total_events' => $events->count(),
|
|
'overspeed_events' => $events->where('type', 'deviceOverspeed')->count(),
|
|
'online_time' => $this->calculateOnlineTime($positions),
|
|
'fuel_consumption' => $this->calculateFuelConsumption($positions),
|
|
];
|
|
}
|
|
}
|
|
|
|
private function generateEventsReport()
|
|
{
|
|
$startDateTime = Carbon::parse($this->startDate)->startOfDay();
|
|
$endDateTime = Carbon::parse($this->endDate)->endOfDay();
|
|
|
|
$this->eventsData = Event::with(['device', 'geofence'])
|
|
->whereIn('device_id', $this->selectedDevices)
|
|
->whereBetween('event_time', [$startDateTime, $endDateTime])
|
|
->orderBy('event_time', 'desc')
|
|
->get()
|
|
->groupBy(function($event) {
|
|
return $this->groupByDate ?
|
|
$event->event_time->format('Y-m-d') :
|
|
$event->device->name;
|
|
});
|
|
}
|
|
|
|
private function generateStopsReport()
|
|
{
|
|
$startDateTime = Carbon::parse($this->startDate)->startOfDay();
|
|
$endDateTime = Carbon::parse($this->endDate)->endOfDay();
|
|
|
|
$this->stopsData = [];
|
|
|
|
foreach ($this->selectedDevices as $deviceId) {
|
|
$device = Device::find($deviceId);
|
|
if (!$device) continue;
|
|
|
|
$positions = Position::where('device_id', $deviceId)
|
|
->whereBetween('device_time', [$startDateTime, $endDateTime])
|
|
->orderBy('device_time')
|
|
->get();
|
|
|
|
$stops = $this->detectStops($positions);
|
|
|
|
if (!empty($stops)) {
|
|
$this->stopsData[] = [
|
|
'device' => $device,
|
|
'stops' => $stops,
|
|
'total_stops' => count($stops),
|
|
'total_stop_time' => array_sum(array_column($stops, 'duration')),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
private function generateTripsReport()
|
|
{
|
|
$startDateTime = Carbon::parse($this->startDate)->startOfDay();
|
|
$endDateTime = Carbon::parse($this->endDate)->endOfDay();
|
|
|
|
$this->reportData = [];
|
|
|
|
foreach ($this->selectedDevices as $deviceId) {
|
|
$device = Device::find($deviceId);
|
|
if (!$device) continue;
|
|
|
|
$positions = Position::where('device_id', $deviceId)
|
|
->whereBetween('device_time', [$startDateTime, $endDateTime])
|
|
->orderBy('device_time')
|
|
->get();
|
|
|
|
$trips = $this->detectTrips($positions);
|
|
|
|
$this->reportData[] = [
|
|
'device' => $device,
|
|
'trips' => $trips,
|
|
'total_trips' => count($trips),
|
|
'total_distance' => array_sum(array_column($trips, 'distance')),
|
|
'total_duration' => array_sum(array_column($trips, 'duration')),
|
|
];
|
|
}
|
|
}
|
|
|
|
private function calculateTotalDistance($positions)
|
|
{
|
|
$totalDistance = 0;
|
|
$previousPosition = null;
|
|
|
|
foreach ($positions as $position) {
|
|
if ($previousPosition) {
|
|
$distance = $this->calculateDistance(
|
|
$previousPosition->latitude,
|
|
$previousPosition->longitude,
|
|
$position->latitude,
|
|
$position->longitude
|
|
);
|
|
$totalDistance += $distance;
|
|
}
|
|
$previousPosition = $position;
|
|
}
|
|
|
|
return round($totalDistance, 2);
|
|
}
|
|
|
|
private function calculateDistance($lat1, $lon1, $lat2, $lon2)
|
|
{
|
|
$earthRadius = 6371; // kilometers
|
|
|
|
$dLat = deg2rad($lat2 - $lat1);
|
|
$dLon = deg2rad($lon2 - $lon1);
|
|
|
|
$a = sin($dLat/2) * sin($dLat/2) +
|
|
cos(deg2rad($lat1)) * cos(deg2rad($lat2)) *
|
|
sin($dLon/2) * sin($dLon/2);
|
|
|
|
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
|
|
|
|
return $earthRadius * $c;
|
|
}
|
|
|
|
private function detectStops($positions)
|
|
{
|
|
$stops = [];
|
|
$currentStop = null;
|
|
$speedThreshold = 2; // km/h
|
|
|
|
foreach ($positions as $position) {
|
|
$speed = $position->speed ?? 0;
|
|
|
|
if ($speed <= $speedThreshold) {
|
|
if (!$currentStop) {
|
|
$currentStop = [
|
|
'start_time' => $position->device_time,
|
|
'latitude' => $position->latitude,
|
|
'longitude' => $position->longitude,
|
|
'address' => $position->address,
|
|
];
|
|
}
|
|
} else {
|
|
if ($currentStop) {
|
|
$currentStop['end_time'] = $position->device_time;
|
|
$currentStop['duration'] = $currentStop['start_time']->diffInMinutes($currentStop['end_time']);
|
|
|
|
if ($currentStop['duration'] >= $this->minStopDuration) {
|
|
$stops[] = $currentStop;
|
|
}
|
|
|
|
$currentStop = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $stops;
|
|
}
|
|
|
|
private function detectTrips($positions)
|
|
{
|
|
$trips = [];
|
|
$currentTrip = null;
|
|
$speedThreshold = 2; // km/h
|
|
|
|
foreach ($positions as $position) {
|
|
$speed = $position->speed ?? 0;
|
|
|
|
if ($speed > $speedThreshold) {
|
|
if (!$currentTrip) {
|
|
$currentTrip = [
|
|
'start_time' => $position->device_time,
|
|
'start_latitude' => $position->latitude,
|
|
'start_longitude' => $position->longitude,
|
|
'start_address' => $position->address,
|
|
'positions' => [$position],
|
|
];
|
|
} else {
|
|
$currentTrip['positions'][] = $position;
|
|
}
|
|
} else {
|
|
if ($currentTrip && count($currentTrip['positions']) > 1) {
|
|
$lastPosition = end($currentTrip['positions']);
|
|
$currentTrip['end_time'] = $lastPosition->device_time;
|
|
$currentTrip['end_latitude'] = $lastPosition->latitude;
|
|
$currentTrip['end_longitude'] = $lastPosition->longitude;
|
|
$currentTrip['end_address'] = $lastPosition->address;
|
|
$currentTrip['duration'] = $currentTrip['start_time']->diffInMinutes($currentTrip['end_time']);
|
|
$currentTrip['distance'] = $this->calculateTotalDistance(collect($currentTrip['positions']));
|
|
$currentTrip['max_speed'] = collect($currentTrip['positions'])->max('speed') ?? 0;
|
|
|
|
unset($currentTrip['positions']); // Remove positions to save memory
|
|
$trips[] = $currentTrip;
|
|
$currentTrip = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $trips;
|
|
}
|
|
|
|
private function calculateOnlineTime($positions)
|
|
{
|
|
if ($positions->count() < 2) return 0;
|
|
|
|
return $positions->first()->device_time->diffInMinutes($positions->last()->device_time);
|
|
}
|
|
|
|
private function calculateFuelConsumption($positions)
|
|
{
|
|
// This is a simplified calculation - in reality you'd use more sophisticated methods
|
|
$totalDistance = $this->calculateTotalDistance($positions);
|
|
$avgConsumption = 8; // liters per 100km (default)
|
|
|
|
return round(($totalDistance / 100) * $avgConsumption, 2);
|
|
}
|
|
|
|
public function exportReport()
|
|
{
|
|
// This would implement actual export functionality
|
|
session()->flash('success', 'Report export started. You will receive a download link shortly.');
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
$deviceQuery = Device::where('user_id', Auth::id());
|
|
|
|
// Check if device groups have users relationship (safely handle missing relationship)
|
|
if (method_exists(\App\Models\DeviceGroup::class, 'users')) {
|
|
$deviceQuery->orWhereHas('group.users', function($subQuery) {
|
|
$subQuery->where('user_id', Auth::id());
|
|
});
|
|
}
|
|
|
|
$devices = $deviceQuery->get();
|
|
|
|
// Get recent events for the current user
|
|
$recentEvents = Event::with(['device', 'position'])
|
|
->whereHas('device', function($q) {
|
|
$q->where('user_id', Auth::id());
|
|
})
|
|
->orderBy('event_time', 'desc')
|
|
->limit(10)
|
|
->get();
|
|
|
|
return view('livewire.reports-and-history', [
|
|
'devices' => $devices,
|
|
'recentEvents' => $recentEvents,
|
|
]);
|
|
}
|
|
}
|