id ?? null); if (!$sessId) { $user = User::where('role', 'employer')->first(); if ($user) { session(['user' => (object)[ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, 'role' => 'employer', 'subscription_status' => $user->subscription_status ?? 'active', ]]); return $user; } } else { return User::find($sessId); } return null; } private function getWorkerStatus($employerId, $workerId, $defaultStatus) { $pendingOfferOrApp = JobOffer::where('employer_id', $employerId) ->where('worker_id', $workerId) ->where('status', 'pending') ->exists() || \App\Models\JobApplication::where('worker_id', $workerId) ->whereHas('jobPost', function($q) use ($employerId) { $q->where('employer_id', $employerId); })->where('status', 'hire_requested') ->exists(); return $pendingOfferOrApp ? 'pending_confirmation' : strtolower($defaultStatus ?? 'active'); } public static function processWorkerResponse($conv, $worker, $text) { $replyText = strtolower(trim($text)); if ($replyText === 'yes' || $replyText === 'no') { // Find the last message sent by the employer in this conversation (excluding the current worker message) $lastEmployerMessage = Message::where('conversation_id', $conv->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' ]); } } } } } public function index(Request $request) { $user = $this->resolveCurrentUser(); if (!$user) { return redirect()->route('employer.login'); } $dbConversations = Conversation::where('employer_id', $user->id) ->with(['worker', 'messages']) ->latest('updated_at') ->get(); $conversations = $dbConversations->map(function ($conv) use ($user) { $lastMsg = $conv->messages->last(); return [ 'id' => $conv->id, 'worker_id' => $conv->worker_id, 'worker_name' => $conv->worker->name ?? 'Candidate', 'worker_status' => $this->getWorkerStatus($user->id, $conv->worker_id, $conv->worker->status), 'last_message' => $lastMsg->text ?? 'No messages yet.', 'unread' => $lastMsg ? ($lastMsg->sender_type === 'worker' && is_null($lastMsg->read_at)) : false, 'online' => true, 'sent_at' => $lastMsg ? $lastMsg->created_at->diffForHumans() : 'Just now', ]; })->toArray(); return Inertia::render('Employer/Messages/Index', [ 'conversations' => $conversations, ]); } public function show($id) { $user = $this->resolveCurrentUser(); if (!$user) { return redirect()->route('employer.login'); } $dbConversations = Conversation::where('employer_id', $user->id) ->with(['worker', 'messages']) ->latest('updated_at') ->get(); $conversations = $dbConversations->map(function ($conv) use ($user) { $lastMsg = $conv->messages->last(); return [ 'id' => $conv->id, 'worker_id' => $conv->worker_id, 'worker_name' => $conv->worker->name ?? 'Candidate', 'worker_status' => $this->getWorkerStatus($user->id, $conv->worker_id, $conv->worker->status), 'last_message' => $lastMsg->text ?? 'No messages yet.', 'unread' => $lastMsg ? ($lastMsg->sender_type === 'worker' && is_null($lastMsg->read_at)) : false, 'online' => true, 'sent_at' => $lastMsg ? $lastMsg->created_at->diffForHumans() : 'Just now', ]; })->toArray(); $activeConv = Conversation::where('employer_id', $user->id) ->where('id', $id) ->with(['worker', 'messages']) ->firstOrFail(); // Mark incoming messages as read Message::where('conversation_id', $activeConv->id) ->where('sender_type', 'worker') ->whereNull('read_at') ->update(['read_at' => now()]); $conversationData = [ 'id' => $activeConv->id, 'worker_id' => $activeConv->worker_id, 'worker_name' => $activeConv->worker->name ?? 'Candidate', 'worker_status' => $this->getWorkerStatus($user->id, $activeConv->worker_id, $activeConv->worker->status), 'online' => true, 'salary' => ($activeConv->worker->salary ?? 2000) . ' AED', 'nationality' => $activeConv->worker->nationality ?? 'Unknown', ]; $initialMessages = $activeConv->messages->map(function ($msg) { return [ 'id' => $msg->id, 'sender' => $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')), 'attachment_url' => $msg->attachment_path ? asset('storage/' . $msg->attachment_path) : null, 'attachment_type' => $msg->attachment_type, ]; })->toArray(); $application = \App\Models\JobApplication::where('worker_id', $activeConv->worker_id) ->whereHas('jobPost', function($q) use ($user) { $q->where('employer_id', $user->id); }) ->latest('id') ->first(); if (!$application) { $jobPost = \App\Models\JobPost::where('employer_id', $user->id)->latest('id')->first(); if (!$jobPost) { $jobPost = \App\Models\JobPost::create([ 'employer_id' => $user->id, 'title' => 'General Helper', 'location' => 'Dubai', 'salary' => 2000, 'job_type' => 'full-time', 'status' => 'open', 'start_date' => now()->format('Y-m-d'), 'description' => 'General domestic helper position.', 'requirements' => 'General helper requirements.', ]); } $application = \App\Models\JobApplication::create([ 'job_id' => $jobPost->id, 'worker_id' => $activeConv->worker_id, 'status' => 'applied', 'employer_status' => 'review', 'status_history' => [ [ 'status' => 'applied', 'timestamp' => now()->toIso8601String(), 'notes' => 'Application created via chat conversation.', ] ], ]); } $applicationData = null; if ($application) { $applicationData = [ 'application_id' => $application->id, 'status' => $application->status, 'notes' => $application->notes, 'status_history' => $application->status_history ?: [], 'name' => $activeConv->worker->name ?? 'Candidate', ]; } $hasJobAccess = $this->checkJobAccess($user); return Inertia::render('Employer/Messages/Show', [ 'conversations' => $conversations, 'conversation' => $conversationData, 'initialMessages' => $initialMessages, 'application' => $applicationData, 'hasJobAccess' => $hasJobAccess, ]); } private function checkJobAccess($user) { if (!$user) { return false; } $sub = \Illuminate\Support\Facades\DB::table('subscriptions') ->where('user_id', $user->id) ->where('status', 'active') ->latest('id') ->first(); $planId = $sub ? $sub->plan_id : 'basic'; return $planId !== 'basic'; } public function send(Request $request, $id) { $user = $this->resolveCurrentUser(); if (!$user) { return back()->withErrors(['general' => 'User session not found.']); } $request->validate([ '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 ]); $conv = Conversation::where('employer_id', $user->id)->where('id', $id)->firstOrFail(); $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 = Message::create([ 'conversation_id' => $conv->id, 'sender_type' => 'employer', 'sender_id' => $user->id, 'text' => $request->text, 'attachment_path' => $attachmentPath, 'attachment_type' => $attachmentType, ]); // Touch conversation updated_at for sorting $conv->touch(); // Dispatch push notification to worker $worker = $conv->worker; if ($worker && $worker->fcm_token) { \App\Services\FCMService::sendPushNotification( $worker->fcm_token, "New Message from " . ($user->name ?? "Employer"), $request->text ?: "Sent an attachment", [ 'conversation_id' => $conv->id, 'sender_type' => 'employer', 'sender_id' => $user->id, ] ); } return back(); } /** * Start or load a conversation with a specific worker. * * @param int $workerId * @return \Illuminate\Http\RedirectResponse */ public function startConversation($workerId) { $user = $this->resolveCurrentUser(); if (!$user) { return redirect()->route('employer.login'); } if (!User::canContactWorker($user->id, $workerId)) { $activeSub = \App\Models\Subscription::where('user_id', $user->id)->where('status', 'active')->first(); $planId = $activeSub ? $activeSub->plan_id : 'basic'; $plan = \App\Models\Plan::find($planId); $maxContacts = $plan ? $plan->max_contact_people : 10; return redirect()->route('employer.subscription') ->with('error', 'You have reached your contacted workers limit of ' . $maxContacts . ' under your active plan. Please upgrade or renew your subscription to contact more workers.'); } // Find or create conversation $conv = Conversation::firstOrCreate([ 'employer_id' => $user->id, 'worker_id' => $workerId, ]); return redirect()->route('employer.messages.show', ['id' => $conv->id]); } }