417 lines
14 KiB
PHP
417 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire;
|
|
|
|
use Livewire\Component;
|
|
use Livewire\WithPagination;
|
|
use App\Models\Event;
|
|
use App\Models\Device;
|
|
use App\Models\Geofence;
|
|
use App\Models\NotificationPreference;
|
|
use App\Services\TraccarService;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Carbon\Carbon;
|
|
|
|
class EventsAndAlerts extends Component
|
|
{
|
|
use WithPagination;
|
|
|
|
public $search = '';
|
|
public $deviceFilter = 'all';
|
|
public $eventTypeFilter = 'all';
|
|
public $dateRange = '7'; // days
|
|
public $selectedEvent = null;
|
|
public $showNotificationSettings = false;
|
|
|
|
// Filter properties
|
|
public $filterType = '';
|
|
public $filterDevice = '';
|
|
public $filterDateFrom = '';
|
|
public $filterDateTo = '';
|
|
public $filterStatus = '';
|
|
public $filterAcknowledged = '';
|
|
|
|
// Notification settings
|
|
public $emailNotifications = true;
|
|
public $pushNotifications = true;
|
|
public $smsNotifications = false;
|
|
public $notificationTypes = [];
|
|
|
|
protected $traccarService;
|
|
|
|
public function boot(TraccarService $traccarService)
|
|
{
|
|
$this->traccarService = $traccarService;
|
|
}
|
|
|
|
public function mount()
|
|
{
|
|
$this->loadNotificationSettings();
|
|
}
|
|
|
|
public function loadNotificationSettings()
|
|
{
|
|
$user = Auth::user();
|
|
$preferences = NotificationPreference::where('user_id', $user->id)->first();
|
|
|
|
if ($preferences) {
|
|
$this->emailNotifications = $preferences->email_enabled;
|
|
$this->pushNotifications = $preferences->push_enabled;
|
|
$this->smsNotifications = $preferences->sms_enabled;
|
|
$this->notificationTypes = $preferences->event_types ?? [];
|
|
}
|
|
}
|
|
|
|
public function saveNotificationSettings()
|
|
{
|
|
try {
|
|
$user = Auth::user();
|
|
|
|
NotificationPreference::updateOrCreate(
|
|
['user_id' => $user->id],
|
|
[
|
|
'email_enabled' => $this->emailNotifications,
|
|
'push_enabled' => $this->pushNotifications,
|
|
'sms_enabled' => $this->smsNotifications,
|
|
'event_types' => $this->notificationTypes,
|
|
]
|
|
);
|
|
|
|
$this->showNotificationSettings = false;
|
|
session()->flash('success', 'Notification settings saved successfully!');
|
|
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to save notification settings: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function viewEventDetails($eventId)
|
|
{
|
|
$this->selectedEvent = Event::with(['device', 'geofence'])->findOrFail($eventId);
|
|
}
|
|
|
|
public function closeEventDetails()
|
|
{
|
|
$this->selectedEvent = null;
|
|
}
|
|
|
|
public function markAsRead($eventId)
|
|
{
|
|
try {
|
|
$event = Event::findOrFail($eventId);
|
|
$event->update(['acknowledged' => true, 'acknowledged_at' => now()]);
|
|
|
|
session()->flash('success', 'Event marked as read');
|
|
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to mark event as read: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function syncEvents()
|
|
{
|
|
try {
|
|
// Get events from Traccar
|
|
$traccarEvents = $this->traccarService->getEvents();
|
|
|
|
if ($traccarEvents) {
|
|
foreach ($traccarEvents as $traccarEvent) {
|
|
$device = Device::where('traccar_id', $traccarEvent['deviceId'])->first();
|
|
$geofence = null;
|
|
|
|
if (isset($traccarEvent['geofenceId'])) {
|
|
$geofence = Geofence::where('traccar_id', $traccarEvent['geofenceId'])->first();
|
|
}
|
|
|
|
if ($device) {
|
|
Event::updateOrCreate(
|
|
['traccar_id' => $traccarEvent['id']],
|
|
[
|
|
'device_id' => $device->id,
|
|
'geofence_id' => $geofence?->id,
|
|
'event_type' => $traccarEvent['type'],
|
|
'event_time' => Carbon::parse($traccarEvent['eventTime']),
|
|
'position_id' => null, // Would need to match position
|
|
'attributes' => $traccarEvent['attributes'] ?? null,
|
|
'acknowledged' => false,
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
session()->flash('success', 'Events synchronized successfully!');
|
|
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to sync events: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function getEventIcon($eventType)
|
|
{
|
|
return match ($eventType) {
|
|
'deviceOnline' => 'signal',
|
|
'deviceOffline' => 'signal-slash',
|
|
'deviceOverspeed' => 'exclamation-triangle',
|
|
'deviceFuelDrop' => 'fire',
|
|
'geofenceEnter' => 'map-pin',
|
|
'geofenceExit' => 'map-pin',
|
|
'alarm' => 'bell',
|
|
'ignitionOn' => 'key',
|
|
'ignitionOff' => 'key',
|
|
'maintenance' => 'wrench',
|
|
'textMessage' => 'chat-bubble-left',
|
|
'driverChanged' => 'user',
|
|
default => 'information-circle'
|
|
};
|
|
}
|
|
|
|
public function getEventColor($eventType)
|
|
{
|
|
return match ($eventType) {
|
|
'deviceOnline' => 'text-green-600 dark:text-green-400',
|
|
'deviceOffline' => 'text-red-600 dark:text-red-400',
|
|
'deviceOverspeed' => 'text-orange-600 dark:text-orange-400',
|
|
'deviceFuelDrop' => 'text-red-600 dark:text-red-400',
|
|
'geofenceEnter' => 'text-blue-600 dark:text-blue-400',
|
|
'geofenceExit' => 'text-purple-600 dark:text-purple-400',
|
|
'alarm' => 'text-red-600 dark:text-red-400',
|
|
'ignitionOn' => 'text-green-600 dark:text-green-400',
|
|
'ignitionOff' => 'text-gray-600 dark:text-gray-400',
|
|
'maintenance' => 'text-yellow-600 dark:text-yellow-400',
|
|
'textMessage' => 'text-blue-600 dark:text-blue-400',
|
|
'driverChanged' => 'text-indigo-600 dark:text-indigo-400',
|
|
default => 'text-gray-600 dark:text-gray-400'
|
|
};
|
|
}
|
|
|
|
public function refreshEvents()
|
|
{
|
|
$this->resetPage();
|
|
session()->flash('success', 'Events refreshed!');
|
|
}
|
|
|
|
public function markAllAsRead()
|
|
{
|
|
try {
|
|
Event::whereHas('device', function($q) {
|
|
$q->where('user_id', Auth::id());
|
|
})
|
|
->where('acknowledged', false)
|
|
->update(['acknowledged' => true, 'acknowledged_at' => now()]);
|
|
|
|
session()->flash('success', 'All events marked as read');
|
|
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to mark all events as read: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function acknowledgeEvent($eventId)
|
|
{
|
|
try {
|
|
$event = Event::findOrFail($eventId);
|
|
$event->update([
|
|
'acknowledged' => true,
|
|
'acknowledged_at' => now(),
|
|
'acknowledged_by' => Auth::id()
|
|
]);
|
|
|
|
session()->flash('success', 'Event acknowledged');
|
|
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to acknowledge event: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function showEventDetails($eventId)
|
|
{
|
|
$this->selectedEvent = Event::with(['device', 'geofence', 'position'])->findOrFail($eventId);
|
|
}
|
|
|
|
public function showOnMap($latitude, $longitude)
|
|
{
|
|
// Emit event to show location on map
|
|
$this->dispatch('showLocationOnMap', ['lat' => $latitude, 'lng' => $longitude]);
|
|
session()->flash('info', "Location: {$latitude}, {$longitude}");
|
|
}
|
|
|
|
public function clearFilters()
|
|
{
|
|
$this->filterType = '';
|
|
$this->filterDevice = '';
|
|
$this->filterDateFrom = '';
|
|
$this->filterDateTo = '';
|
|
$this->filterStatus = '';
|
|
$this->filterAcknowledged = '';
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function hasActiveFilters()
|
|
{
|
|
return !empty($this->filterType) ||
|
|
!empty($this->filterDevice) ||
|
|
!empty($this->filterDateFrom) ||
|
|
!empty($this->filterDateTo) ||
|
|
!empty($this->filterStatus) ||
|
|
!empty($this->filterAcknowledged);
|
|
}
|
|
|
|
// Computed properties for stats
|
|
public function getCriticalCountProperty()
|
|
{
|
|
$criticalTypes = ['alarm', 'deviceOffline', 'deviceOverspeed', 'panic', 'accident', 'deviceFuelDrop'];
|
|
|
|
return Event::whereHas('device', function($q) {
|
|
$q->where('user_id', Auth::id());
|
|
})
|
|
->whereIn('type', $criticalTypes)
|
|
->where('event_time', '>=', now()->subDays(30))
|
|
->count();
|
|
}
|
|
|
|
public function getWarningCountProperty()
|
|
{
|
|
$warningTypes = ['maintenance', 'batteryLow', 'deviceMoving', 'deviceStopped', 'geofenceExit', 'ignitionOff'];
|
|
|
|
return Event::whereHas('device', function($q) {
|
|
$q->where('user_id', Auth::id());
|
|
})
|
|
->whereIn('type', $warningTypes)
|
|
->where('event_time', '>=', now()->subDays(30))
|
|
->count();
|
|
}
|
|
|
|
public function getInfoCountProperty()
|
|
{
|
|
$infoTypes = ['deviceOnline', 'geofenceEnter', 'ignitionOn', 'driverChanged', 'textMessage'];
|
|
|
|
return Event::whereHas('device', function($q) {
|
|
$q->where('user_id', Auth::id());
|
|
})
|
|
->whereIn('type', $infoTypes)
|
|
->where('event_time', '>=', now()->subDays(30))
|
|
->count();
|
|
}
|
|
|
|
public function getTotalCountProperty()
|
|
{
|
|
return Event::whereHas('device', function($q) {
|
|
$q->where('user_id', Auth::id());
|
|
})
|
|
->where('event_time', '>=', now()->subDays(30))
|
|
->count();
|
|
}
|
|
|
|
public function getEventSeverity($eventType)
|
|
{
|
|
$criticalTypes = ['alarm', 'deviceOffline', 'deviceOverspeed', 'panic', 'accident', 'deviceFuelDrop'];
|
|
$warningTypes = ['maintenance', 'batteryLow', 'deviceMoving', 'deviceStopped', 'geofenceExit', 'ignitionOff'];
|
|
|
|
if (in_array($eventType, $criticalTypes)) {
|
|
return 'critical';
|
|
} elseif (in_array($eventType, $warningTypes)) {
|
|
return 'warning';
|
|
} else {
|
|
return 'info';
|
|
}
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
// Build query with filters
|
|
$query = Event::with(['device', 'geofence', 'position'])
|
|
->whereHas('device', function($q) {
|
|
$q->where('user_id', Auth::id());
|
|
});
|
|
|
|
// Apply new filters
|
|
if ($this->filterType) {
|
|
$query->where('type', $this->filterType);
|
|
}
|
|
|
|
if ($this->filterDevice) {
|
|
$query->where('device_id', $this->filterDevice);
|
|
}
|
|
|
|
if ($this->filterDateFrom) {
|
|
$query->where('event_time', '>=', $this->filterDateFrom);
|
|
}
|
|
|
|
if ($this->filterDateTo) {
|
|
$query->where('event_time', '<=', $this->filterDateTo);
|
|
}
|
|
|
|
if ($this->filterStatus) {
|
|
if ($this->filterStatus === 'unread') {
|
|
$query->where('acknowledged', false);
|
|
} elseif ($this->filterStatus === 'read') {
|
|
$query->where('acknowledged', true);
|
|
}
|
|
}
|
|
|
|
if ($this->filterAcknowledged) {
|
|
if ($this->filterAcknowledged === 'yes') {
|
|
$query->where('acknowledged', true);
|
|
} elseif ($this->filterAcknowledged === 'no') {
|
|
$query->where('acknowledged', false);
|
|
}
|
|
}
|
|
|
|
// Apply legacy search filter
|
|
if ($this->search) {
|
|
$query->where(function($q) {
|
|
$q->whereHas('device', function($deviceQuery) {
|
|
$deviceQuery->where('name', 'like', '%' . $this->search . '%');
|
|
})->orWhere('type', 'like', '%' . $this->search . '%');
|
|
});
|
|
}
|
|
|
|
// Apply legacy filters for backward compatibility
|
|
if ($this->deviceFilter !== 'all') {
|
|
$query->where('device_id', $this->deviceFilter);
|
|
}
|
|
|
|
if ($this->eventTypeFilter !== 'all') {
|
|
$query->where('type', $this->eventTypeFilter);
|
|
}
|
|
|
|
if ($this->dateRange !== 'all') {
|
|
$query->where('event_time', '>=', now()->subDays((int)$this->dateRange));
|
|
}
|
|
|
|
$events = $query->orderBy('event_time', 'desc')->paginate(20);
|
|
|
|
$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();
|
|
|
|
$eventTypes = Event::whereHas('device', function($q) {
|
|
$q->where('user_id', Auth::id());
|
|
})
|
|
->distinct()
|
|
->pluck('type')
|
|
->sort();
|
|
|
|
$unreadCount = Event::whereHas('device', function($q) {
|
|
$q->where('user_id', Auth::id());
|
|
})
|
|
->where('acknowledged', false)
|
|
->count();
|
|
|
|
return view('livewire.events-and-alerts', [
|
|
'events' => $events,
|
|
'devices' => $devices,
|
|
'eventTypes' => $eventTypes,
|
|
'unreadCount' => $unreadCount
|
|
]);
|
|
}
|
|
}
|