id) ->where('sender_type', 'employer') ->latest() ->first(); if ($lastEmployerMessage) { $questionText = strtolower($lastEmployerMessage->text); $isLookingJobQuestion = str_contains($questionText, 'looking job') || str_contains($questionText, 'looking for a job') || str_contains($questionText, 'looking for job') || str_contains($questionText, 'are you looking'); if ($isLookingJobQuestion) { if ($replyText === 'yes') { // S6 Outcome: Update status as Hired $worker->update([ 'status' => 'Hired', 'availability' => 'Hired' ]); // Accept existing direct offer or application $offer = JobOffer::where('employer_id', $conv->employer_id) ->where('worker_id', $worker->id) ->first(); if ($offer) { $offer->update(['status' => 'accepted']); } else { JobOffer::create([ 'employer_id' => $conv->employer_id, 'worker_id' => $worker->id, 'work_date' => now()->format('Y-m-d'), 'location' => 'Dubai', 'salary' => $worker->salary ?: 2000, 'notes' => 'Hired via chat agreement.', 'status' => 'accepted', ]); } } else if ($replyText === 'no') { // S5 Outcome: Update status as Hidden (Auto-Hidden from search) $worker->update([ 'status' => 'hidden', 'availability' => 'Not Available' ]); } } } } } /** * Get all conversations for the authorized employer. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse */ public function getConversations(Request $request) { /** @var User $employer */ $employer = $request->attributes->get('employer'); try { $dbConversations = Conversation::where('employer_id', $employer->id) ->with(['worker.category', 'messages']) ->latest('updated_at') ->get(); $conversations = $dbConversations->map(function ($conv) { $lastMsg = $conv->messages->last(); // Calculate unread count (messages sent by worker that are not read by employer) $unreadCount = $conv->messages->filter(function ($msg) { return $msg->sender_type === 'worker' && is_null($msg->read_at); })->count(); return [ 'id' => $conv->id, 'worker_id' => $conv->worker_id, 'worker_name' => $conv->worker->name ?? 'Candidate', 'category' => $conv->worker->category->name ?? 'General Helper', 'nationality' => $conv->worker->nationality ?? 'Unknown', 'salary' => ($conv->worker->salary ?? $conv->worker->expected_salary ?? 2000) . ' AED', 'last_message' => $lastMsg->text ?? 'No messages yet.', 'unread_count' => $unreadCount, 'sent_at' => $lastMsg ? $lastMsg->created_at->diffForHumans() : 'Just now', 'updated_at' => $conv->updated_at->toISOString(), ]; }); return response()->json([ 'success' => true, 'data' => [ 'conversations' => $conversations ] ], 200); } catch (\Exception $e) { logger()->error('Mobile Employer Get Conversations Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred while fetching conversations.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * Get messages in a single conversation and mark unread incoming messages as read. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\JsonResponse */ public function getMessages(Request $request, $id) { /** @var User $employer */ $employer = $request->attributes->get('employer'); try { $conv = Conversation::where('employer_id', $employer->id) ->where('id', $id) ->with(['worker.category', 'messages']) ->first(); if (!$conv) { return response()->json([ 'success' => false, 'message' => 'Conversation not found.' ], 404); } // Mark incoming worker messages in this conversation as read Message::where('conversation_id', $conv->id) ->where('sender_type', 'worker') ->whereNull('read_at') ->update(['read_at' => now()]); $messages = $conv->messages->map(function ($msg) { return [ 'id' => $msg->id, 'sender_type' => $msg->sender_type, // employer or worker 'text' => $msg->text, 'time' => $msg->created_at->format('g:i A') . ($msg->created_at->isToday() ? ' Today' : ' ' . $msg->created_at->format('M d')), 'read_at' => $msg->read_at ? $msg->read_at->toISOString() : null, 'created_at' => $msg->created_at->toISOString(), 'attachment_url' => $msg->attachment_path ? asset('storage/' . $msg->attachment_path) : null, 'attachment_type' => $msg->attachment_type, ]; }); $conversationDetail = [ 'id' => $conv->id, 'worker_name' => $conv->worker->name ?? 'Candidate', 'category' => $conv->worker->category->name ?? 'General Helper', 'nationality' => $conv->worker->nationality ?? 'Unknown', 'salary' => ($conv->worker->salary ?? $conv->worker->expected_salary ?? 2000) . ' AED', ]; return response()->json([ 'success' => true, 'data' => [ 'conversation' => $conversationDetail, 'messages' => $messages ] ], 200); } catch (\Exception $e) { logger()->error('Mobile Employer Get Messages Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred while fetching messages.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * Send a reply message from the employer to the worker. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\JsonResponse */ public function sendMessage(Request $request, $id) { /** @var User $employer */ $employer = $request->attributes->get('employer'); $validator = Validator::make($request->all(), [ 'text' => 'required_without:file|string|max:1000|nullable', 'file' => 'nullable|file|mimes:jpg,jpeg,png,pdf,mp3,wav,m4a,ogg,webm,mp4,aac,3gp,amr|max:10240', // 10MB limit ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } try { $conv = Conversation::where('employer_id', $employer->id) ->where('id', $id) ->first(); if (!$conv) { return response()->json([ 'success' => false, 'message' => 'Conversation not found.' ], 404); } $attachmentPath = null; $attachmentType = null; if ($request->hasFile('file')) { $file = $request->file('file'); $attachmentPath = $file->store('chat_attachments', 'public'); $mime = $file->getMimeType(); if (str_starts_with($mime, 'image/')) { $attachmentType = 'image'; } elseif (str_starts_with($mime, 'audio/')) { $attachmentType = 'voice'; } else { $attachmentType = 'document'; } } $message = null; DB::transaction(function () use ($conv, $employer, $request, $attachmentPath, $attachmentType, &$message) { $message = Message::create([ 'conversation_id' => $conv->id, 'sender_type' => 'employer', 'sender_id' => $employer->id, 'text' => $request->text, 'attachment_path' => $attachmentPath, 'attachment_type' => $attachmentType, ]); // Touch conversation updated_at for sorting $conv->touch(); // Check if employer sent predefined message "are you looking job?" $textLower = strtolower($request->text); if ( str_contains($textLower, 'looking job') || str_contains($textLower, 'looking for a job') || str_contains($textLower, 'looking for job') || str_contains($textLower, 'are you looking') ) { // Automatically simulate worker response 'Yes' (triggers the S6 hired flow) $workerResponseText = 'Yes'; // Create the message in database Message::create([ 'conversation_id' => $conv->id, 'sender_type' => 'worker', 'sender_id' => $conv->worker_id, 'text' => $workerResponseText, ]); // Process the worker response to update status to Hired! $worker = Worker::find($conv->worker_id); if ($worker) { self::processWorkerResponse($conv, $worker, $workerResponseText); } } }); return response()->json([ 'success' => true, 'message' => 'Message sent successfully.', 'data' => [ 'message' => [ 'id' => $message->id, 'sender_type' => $message->sender_type, 'text' => $message->text, 'time' => $message->created_at->format('g:i A') . ' Today', 'created_at' => $message->created_at->toISOString(), 'attachment_url' => $message->attachment_path ? asset('storage/' . $message->attachment_path) : null, 'attachment_type' => $message->attachment_type, ] ] ], 201); } catch (\Exception $e) { logger()->error('Mobile Employer Send Message Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred while sending the message.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * Start a conversation with a specific worker. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse */ public function startConversation(Request $request) { /** @var User $employer */ $employer = $request->attributes->get('employer'); $validator = Validator::make($request->all(), [ 'worker_id' => 'required|exists:workers,id', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation error.', 'errors' => $validator->errors() ], 422); } try { // Find or create conversation $conv = Conversation::firstOrCreate([ 'employer_id' => $employer->id, 'worker_id' => $request->worker_id, ]); return response()->json([ 'success' => true, 'message' => 'Conversation started successfully.', 'data' => [ 'conversation_id' => $conv->id ] ], 200); } catch (\Exception $e) { logger()->error('Mobile Employer Start Conversation Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred while starting the conversation.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } }