ensureCrmSubscription(); $business_id = $this->businessId(); $contact_id = $this->contactId(); $specialists = $this->specialistsWithPresence($business_id, $contact_id); $onlineSpecialists = $specialists->where('is_online', true)->values(); $business = Business::with(['locations' => fn ($q) => $q->limit(1)])->findOrFail($business_id); $chatEnabled = $this->chatEnabled(); $presenceEnabled = $this->presenceEnabled(); $support_phone = optional($business->locations->first())->mobile; return view('portal::customer.communications.index', compact( 'specialists', 'onlineSpecialists', 'business', 'chatEnabled', 'presenceEnabled', 'support_phone' )); } public function chat(int $specialist_id) { $this->ensureCrmSubscription(); if (! $this->chatEnabled()) { abort(404); } $business_id = $this->businessId(); $contact_id = $this->contactId(); $specialist = $this->specialistService->assertSpecialistForContact($business_id, $contact_id, $specialist_id); $room = $this->chatRoomService->findOrCreateDirectRoom($business_id, auth()->user(), $specialist->id); if ($this->presenceEnabled()) { $this->presenceService->heartbeat($business_id, auth()->id()); } $is_online = $this->presenceEnabled() && $this->presenceService->isOnline($business_id, $specialist->id); return view('portal::customer.communications.chat', [ 'specialist' => [ 'id' => $specialist->id, 'name' => trim($specialist->surname.' '.$specialist->first_name.' '.$specialist->last_name), 'mobile' => $specialist->contact_no, 'email' => $specialist->email, 'is_online' => $is_online, ], 'room_id' => $room->id, 'presenceEnabled' => $this->presenceEnabled(), ]); } public function presence() { $this->ensureCrmSubscription(); $business_id = $this->businessId(); $contact_id = $this->contactId(); return response()->json($this->specialistsWithPresence($business_id, $contact_id)); } public function specialistPresence(int $specialist_id) { $this->ensureCrmSubscription(); $business_id = $this->businessId(); $contact_id = $this->contactId(); $specialist = $this->specialistService->assertSpecialistForContact($business_id, $contact_id, $specialist_id); if ($this->presenceEnabled()) { $this->presenceService->heartbeat($business_id, auth()->id()); } return response()->json([ 'id' => $specialist->id, 'is_online' => $this->presenceEnabled() && $this->presenceService->isOnline($business_id, $specialist->id), ]); } public function heartbeat() { $this->ensureCrmSubscription(); if ($this->presenceEnabled()) { $this->presenceService->heartbeat($this->businessId(), auth()->id()); } return response()->json(['success' => true]); } public function messages(Request $request, int $room_id) { $this->ensureCrmSubscription(); $room = $this->assertCustomerRoom($room_id); $messages = ChatMessage::where('chat_room_id', $room->id) ->with('user:id,surname,first_name,last_name') ->orderBy('created_at') ->get() ->map(fn ($m) => [ 'id' => $m->id, 'body' => $m->body, 'user_id' => $m->user_id, 'created_at' => $m->created_at?->format('Y-m-d H:i'), 'is_mine' => (int) $m->user_id === (int) auth()->id(), ]); return response()->json($messages); } public function sendMessage(Request $request, int $room_id) { $this->ensureCrmSubscription(); $validated = $request->validate([ 'body' => 'required|string|max:5000', ]); $room = $this->assertCustomerRoom($room_id); $business_id = $this->businessId(); $message = ChatMessage::create([ 'chat_room_id' => $room->id, 'user_id' => auth()->id(), 'body' => $validated['body'], 'type' => 'text', ]); $room->update(['last_message_at' => now()]); $room->members()->updateExistingPivot(auth()->id(), ['last_read_at' => now()]); try { event(new MessageSent($message->load('user:id,surname,first_name,last_name,username,email'), $business_id)); } catch (\Throwable $e) { report($e); } return response()->json([ 'id' => $message->id, 'body' => $message->body, 'user_id' => $message->user_id, 'created_at' => $message->created_at?->format('Y-m-d H:i'), 'is_mine' => true, ]); } public function markRead(int $room_id) { $this->ensureCrmSubscription(); $room = $this->assertCustomerRoom($room_id); $room->members()->updateExistingPivot(auth()->id(), ['last_read_at' => now()]); return response()->json(['success' => true]); } private function chatEnabled(): bool { return Schema::hasTable('chat_rooms') && Schema::hasTable('chat_messages') && class_exists(ChatRoomService::class); } private function presenceEnabled(): bool { return $this->chatEnabled() && class_exists(PresenceService::class); } private function specialistsWithPresence(int $business_id, int $contact_id) { $specialists = $this->specialistService->specialistsForContact($business_id, $contact_id); if (! $this->presenceEnabled()) { return $specialists->map(fn ($s) => array_merge($s, ['is_online' => false])); } $this->presenceService->heartbeat($business_id, auth()->id()); $onlineIds = $this->presenceService->onlineUserIds( $business_id, $specialists->pluck('id')->all() ); return $specialists ->map(function ($s) use ($onlineIds) { $s['is_online'] = in_array($s['id'], $onlineIds, true); return $s; }) ->sortByDesc('is_online') ->values(); } private function assertCustomerRoom(int $room_id): ChatRoom { if (! $this->chatEnabled()) { abort(404); } $business_id = $this->businessId(); $contact_id = $this->contactId(); $room = ChatRoom::where('business_id', $business_id)->findOrFail($room_id); $this->chatRoomService->assertRoomMember($room, auth()->user()); $otherMember = $room->members()->where('users.id', '!=', auth()->id())->first(); if ($otherMember) { $this->specialistService->assertSpecialistForContact($business_id, $contact_id, $otherMember->id); } return $room; } }