40.7128, 'lng' => -74.0060]; public $zoomLevel = 13; protected $traccarService; public function boot(TraccarService $traccarService) { $this->traccarService = $traccarService; } protected $rules = [ 'geofenceName' => 'required|string|max:255', 'geofenceDescription' => 'nullable|string|max:1000', 'geofenceType' => 'required|string|in:entry,exit,both,zone', 'selectedDevicesForGeofence' => 'array', 'selectedDevicesForGeofence.*' => 'exists:devices,id', ]; public function mount() { // Set default map center based on user's devices $userDevices = Device::where('user_id', Auth::id())->get(); if ($userDevices->isNotEmpty()) { $avgLat = $userDevices->avg('last_position_lat') ?: 40.7128; $avgLng = $userDevices->avg('last_position_lng') ?: -74.0060; $this->mapCenter = ['lat' => $avgLat, 'lng' => $avgLng]; } } public function showGeofenceModal() { $this->resetForm(); $this->showModal = true; } public function closeModal() { $this->showModal = false; $this->resetForm(); $this->dispatch('clearDrawnShape'); } public function selectGeofence($geofenceId) { $this->selectedGeofence = $geofenceId; } public function editGeofence($geofenceId) { $geofence = Geofence::findOrFail($geofenceId); $this->editingGeofence = $geofence; $this->geofenceName = $geofence->name; $this->geofenceDescription = $geofence->description; $this->geofenceType = $geofence->type ?? 'entry'; $this->geofenceActive = $geofence->is_active; $this->selectedDevicesForGeofence = $geofence->devices->pluck('id')->toArray(); $this->showModal = true; } public function saveGeofence() { $this->validate(); if (!$this->tempCoordinates && !$this->editingGeofence) { session()->flash('error', 'Please draw a geofence area on the map first.'); return; } try { $geofenceData = [ 'name' => $this->geofenceName, 'description' => $this->geofenceDescription, 'type' => $this->geofenceType, 'is_active' => $this->geofenceActive, 'coordinates' => $this->tempCoordinates ? json_encode($this->tempCoordinates) : null, ]; if ($this->editingGeofence) { // Update existing geofence $this->editingGeofence->update($geofenceData); $geofence = $this->editingGeofence; // Update in Traccar if it has traccar_id if ($geofence->traccar_id) { $traccarData = [ 'id' => $geofence->traccar_id, 'name' => $this->geofenceName, 'description' => $this->geofenceDescription, 'area' => $this->tempCoordinates ? $this->convertCoordinatesToTraccarFormat($this->tempCoordinates) : $geofence->area, ]; $this->traccarService->updateGeofence($geofence->traccar_id, $traccarData); } } else { // Create new geofence $geofenceData['user_id'] = Auth::id(); // Create in Traccar first $traccarData = [ 'name' => $this->geofenceName, 'description' => $this->geofenceDescription, 'area' => $this->convertCoordinatesToTraccarFormat($this->tempCoordinates), ]; $traccarGeofence = $this->traccarService->createGeofence($traccarData); if ($traccarGeofence && isset($traccarGeofence['id'])) { $geofenceData['traccar_id'] = $traccarGeofence['id']; } $geofence = Geofence::create($geofenceData); } // Sync device associations $geofence->devices()->sync($this->selectedDevicesForGeofence); // Sync device-geofence associations in Traccar foreach ($this->selectedDevicesForGeofence as $deviceId) { $device = Device::find($deviceId); if ($device && $device->traccar_id && $geofence->traccar_id) { $this->traccarService->linkGeofenceToDevice($geofence->traccar_id, $device->traccar_id); } } $this->closeModal(); $this->dispatch('geofencesUpdated'); session()->flash('success', $this->editingGeofence ? 'Geofence updated successfully!' : 'Geofence created successfully!'); } catch (\Exception $e) { session()->flash('error', 'Failed to save geofence: ' . $e->getMessage()); } } private function convertCoordinatesToTraccarFormat($coordinates) { if (!$coordinates) return ''; if ($coordinates['type'] === 'circle') { // For circles, create a polygon approximation $center = $coordinates['center']; $radius = $coordinates['radius']; $points = []; for ($i = 0; $i < 16; $i++) { $angle = ($i * 360 / 16) * (M_PI / 180); $lat = $center[0] + ($radius / 111000) * cos($angle); $lng = $center[1] + ($radius / (111000 * cos($center[0] * M_PI / 180))) * sin($angle); $points[] = "$lat $lng"; } return "POLYGON((" . implode(', ', $points) . "))"; } else { // For polygons $points = array_map(function($coord) { return $coord[0] . ' ' . $coord[1]; }, $coordinates['coordinates']); return "POLYGON((" . implode(', ', $points) . "))"; } } public function deleteGeofence($geofenceId) { try { $geofence = Geofence::findOrFail($geofenceId); // Delete from Traccar if it exists if ($geofence->traccar_id) { $this->traccarService->deleteGeofence($geofence->traccar_id); } $geofence->delete(); $this->dispatch('geofencesUpdated'); session()->flash('success', 'Geofence deleted successfully!'); } catch (\Exception $e) { session()->flash('error', 'Failed to delete geofence: ' . $e->getMessage()); } } public function refreshGeofences() { $this->dispatch('geofencesUpdated'); session()->flash('success', 'Geofences refreshed!'); } private function resetForm() { $this->editingGeofence = null; $this->geofenceName = ''; $this->geofenceDescription = ''; $this->geofenceType = 'entry'; $this->geofenceActive = true; $this->selectedDevicesForGeofence = []; $this->tempCoordinates = null; } public function render() { $geofences = Geofence::where('user_id', Auth::id()) ->when($this->search, function($query) { $query->where('name', 'like', '%' . $this->search . '%') ->orWhere('description', 'like', '%' . $this->search . '%'); }) ->with('devices') ->orderBy('created_at', 'desc') ->get(); $query = 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')) { $query->orWhereHas('group.users', function($subQuery) { $subQuery->where('user_id', Auth::id()); }); } $devices = $query->get(); return view('livewire.geofence-management', [ 'geofences' => $geofences, 'devices' => $devices ]); } }