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; } public function index(Request $request) { $user = $this->resolveCurrentUser(); $employerId = $user ? $user->id : 2; // 1. Get job posts created by this employer $jobIds = JobPost::where('employer_id', $employerId)->pluck('id'); // Fetch applications for those jobs $applications = JobApplication::whereIn('job_id', $jobIds) ->with(['worker', 'jobPost']) ->get(); $selectedWorkers = $applications->map(function ($app) { $w = $app->worker; if (!$w) return null; // Map DB status to UI friendly status $status = 'Reviewing'; if ($app->status === 'hired') $status = 'Hired'; elseif ($app->status === 'rejected') $status = 'Rejected'; elseif ($app->status === 'shortlisted' || $app->status === 'offer_sent') $status = 'Offer Sent'; elseif ($app->status === 'applied') $status = 'Reviewing'; else $status = ucfirst($app->status); return [ 'id' => $app->id, // standard application id 'worker_id' => $w->id, 'name' => $w->name, 'nationality' => $w->nationality, 'salary' => (int)$w->salary, 'status' => $status, 'applied_at' => $app->created_at->format('M d, Y'), 'preferred_location' => $w->preferred_location, 'preferred_job_type' => $w->preferred_job_type, 'live_in_out' => $w->live_in_out, 'in_country' => (bool)$w->in_country, 'visa_status' => $w->visa_status, ]; })->filter()->values()->toArray(); // 2. Fetch direct hiring offers sent by this employer $directOffers = JobOffer::where('employer_id', $employerId) ->with('worker') ->get(); $directWorkers = $directOffers->map(function ($offer) { $w = $offer->worker; if (!$w) return null; // Map DB offer status to UI friendly status $status = 'Offer Sent'; if ($offer->status === 'accepted') $status = 'Hired'; elseif ($offer->status === 'rejected') $status = 'Rejected'; elseif ($offer->status === 'pending') $status = 'Offer Sent'; return [ 'id' => 'offer_' . $offer->id, // Unique string key prefix to prevent clashes 'worker_id' => $w->id, 'name' => $w->name, 'nationality' => $w->nationality, 'salary' => (int)$offer->salary, 'status' => $status, 'applied_at' => $offer->created_at->format('M d, Y'), 'preferred_location' => $w->preferred_location, 'preferred_job_type' => $w->preferred_job_type, 'live_in_out' => $w->live_in_out, 'in_country' => (bool)$w->in_country, 'visa_status' => $w->visa_status, ]; })->filter()->values()->toArray(); // Merge standard job applications with direct hiring offers for unified tracking $mergedCandidates = array_merge($selectedWorkers, $directWorkers); // Only display Hired candidates in the candidates pipeline $mergedCandidates = array_values(array_filter($mergedCandidates, function ($w) { return $w && $w['status'] === 'Hired'; })); // Apply filters: preferred_location, job_type, live_in_out, nationality, in_country, visa_status if ($request->filled('preferred_location')) { $prefLoc = $request->preferred_location; $locsArray = is_array($prefLoc) ? $prefLoc : array_filter(array_map('trim', explode(',', $prefLoc))); $locsArray = array_map('strtolower', $locsArray); $locationMapping = [ 'dubai' => ['dubai', 'marina', 'barsha', 'nahda', 'jumeirah', 'deira', 'downtown', 'silicon', 'sports', 'motor', 'jlt', 'jbr', 'meydan', 'ranches', 'bay', 'mirdif', 'quoz'], 'abu dhabi' => ['abu dhabi', 'yas', 'khalifa', 'reem', 'saadiyat', 'raha', 'mussafah', 'zahiyah', 'karamah'], 'oman' => ['oman', 'muscat', 'salalah', 'sohar', 'nizwa', 'sur', 'ibri', 'rustaq'] ]; $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($locsArray, $locationMapping) { if (!isset($c['preferred_location'])) return false; $wLoc = strtolower($c['preferred_location']); foreach ($locsArray as $l) { if ($l === 'all') return true; $allowedKeywords = $locationMapping[$l] ?? [$l]; foreach ($allowedKeywords as $keyword) { if (str_contains($wLoc, $keyword)) { return true; } } } return false; })); } $jobTypeParam = $request->input('job_type') ?? $request->input('preferred_job_type'); if ($jobTypeParam) { $jobTypesArray = is_array($jobTypeParam) ? $jobTypeParam : array_filter(array_map('trim', explode(',', $jobTypeParam))); $jobTypesArray = array_map('strtolower', $jobTypesArray); $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($jobTypesArray) { if (!isset($c['preferred_job_type'])) return false; $wJobType = strtolower($c['preferred_job_type']); foreach ($jobTypesArray as $jt) { if (str_contains($wJobType, $jt)) { return true; } } return false; })); } $accParam = $request->input('live_in_out') ?? $request->input('accommodation_type') ?? $request->input('accomadation_type'); if ($accParam) { $accsArray = is_array($accParam) ? $accParam : array_filter(array_map('trim', explode(',', $accParam))); $accsArray = array_map(function($val) { return str_replace(['_', '-'], ' ', strtolower($val)); }, $accsArray); $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($accsArray) { if (!isset($c['live_in_out'])) return false; $wAcc = str_replace(['_', '-'], ' ', strtolower($c['live_in_out'])); foreach ($accsArray as $a) { if (str_contains($wAcc, $a)) { return true; } } return false; })); } if ($request->filled('nationality')) { $natInput = $request->nationality; $natsArray = is_array($natInput) ? $natInput : array_filter(array_map('trim', explode(',', $natInput))); $natsArray = array_map('strtolower', $natsArray); $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($natsArray) { if (!isset($c['nationality'])) return false; $wNat = strtolower($c['nationality']); foreach ($natsArray as $n) { if (str_contains($wNat, $n) || $wNat === $n) { return true; } } return false; })); } if ($request->filled('gender')) { $gender = strtolower($request->gender); $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($gender) { return isset($c['gender']) && strtolower($c['gender']) === $gender; })); } if ($request->has('in_country')) { $inCountryVal = $request->input('in_country'); if ($inCountryVal !== null && $inCountryVal !== '') { $isInCountry = filter_var($inCountryVal, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($isInCountry === null) { $strVal = strtolower(trim($inCountryVal)); if ($strVal === 'in' || $strVal === 'in_country' || $strVal === 'yes') { $isInCountry = true; } elseif ($strVal === 'out' || $strVal === 'out_country' || $strVal === 'no') { $isInCountry = false; } } if ($isInCountry !== null) { $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($isInCountry) { return isset($c['in_country']) && (bool)$c['in_country'] === $isInCountry; })); } } } if ($request->has('out_country')) { $outCountryVal = $request->input('out_country'); if ($outCountryVal !== null && $outCountryVal !== '') { $isOutCountry = filter_var($outCountryVal, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($isOutCountry === null) { $strVal = strtolower(trim($outCountryVal)); if ($strVal === 'out' || $strVal === 'out_country' || $strVal === 'yes') { $isOutCountry = true; } elseif ($strVal === 'in' || $strVal === 'in_country' || $strVal === 'no') { $isOutCountry = false; } } if ($isOutCountry !== null) { $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($isOutCountry) { return isset($c['in_country']) && (bool)$c['in_country'] !== $isOutCountry; })); } } } $visaParam = $request->input('visa_status') ?? $request->input('next_visa_type') ?? $request->input('visa_type'); if ($visaParam) { $visasArray = is_array($visaParam) ? $visaParam : array_filter(array_map('trim', explode(',', $visaParam))); $visasArray = array_map('strtolower', $visasArray); $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($visasArray) { if (!isset($c['visa_status'])) return false; $isInCountry = isset($c['in_country']) && (bool)$c['in_country']; if (!$isInCountry) { return false; } $wVisa = strtolower($c['visa_status']); foreach ($visasArray as $v) { if (str_contains($wVisa, $v)) { return true; } } return false; })); } $nationalitiesResponse = app(\App\Http\Controllers\Api\WorkerAuthController::class)->nationalities(new \Illuminate\Http\Request(['per_page' => 500])); $nationalitiesData = json_decode($nationalitiesResponse->getContent(), true); $allNationalities = collect($nationalitiesData['data']['nationalities'] ?? [])->pluck('name')->filter()->toArray(); return Inertia::render('Employer/SelectedCandidates', [ 'selectedWorkers' => $mergedCandidates, 'allNationalities' => $allNationalities, ]); } 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 updateStatus(Request $request, $id) { $request->validate([ 'status' => 'required|string|in:Applied,Reviewing,Shortlisted,Contacted,Interview Scheduled,Selected,Rejected,Hired,Offer Sent,applied,shortlisted,contacted,interview scheduled,interview_scheduled,selected,rejected,hired,offer sent,offer_sent', 'notes' => 'nullable|string', ]); // If this is a direct hiring offer response if (is_string($id) && str_starts_with($id, 'offer_')) { $offerId = substr($id, 6); $offer = JobOffer::findOrFail($offerId); $dbStatus = 'pending'; if (in_array(strtolower($request->status), ['hired', 'accepted'])) { $dbStatus = 'accepted'; $offer->worker->update(['status' => 'Hired']); } elseif (in_array(strtolower($request->status), ['rejected'])) { $dbStatus = 'rejected'; $offer->worker->update(['status' => 'active']); } elseif (in_array(strtolower($request->status), ['offer sent', 'offer_sent', 'pending'])) { $dbStatus = 'pending'; } $offer->update(['status' => $dbStatus]); return back()->with('success', 'Direct candidate hiring offer status updated successfully to ' . $request->status); } // Standard job application status update $app = JobApplication::findOrFail($id); $user = $this->resolveCurrentUser(); if (!$user) { return redirect()->route('employer.login'); } if (!$this->checkJobAccess($user)) { return redirect()->route('employer.subscription') ->with('error', 'Your current plan does not support the Job Apply module. Please upgrade to a Premium plan to access this feature.'); } $dbStatus = 'applied'; $statusLower = strtolower($request->status); if ($statusLower === 'hired') { $dbStatus = 'hired'; } elseif ($statusLower === 'rejected') { $dbStatus = 'rejected'; } elseif ($statusLower === 'shortlisted' || $statusLower === 'offer sent' || $statusLower === 'offer_sent') { $dbStatus = 'shortlisted'; } elseif ($statusLower === 'contacted') { $dbStatus = 'contacted'; } elseif ($statusLower === 'interview scheduled' || $statusLower === 'interview_scheduled') { $dbStatus = 'interview_scheduled'; } elseif ($statusLower === 'selected') { $dbStatus = 'selected'; } elseif ($statusLower === 'reviewing' || $statusLower === 'applied') { $dbStatus = 'applied'; } // Contact Limit check: if moving to 'shortlisted', 'contacted', 'interview_scheduled', 'selected', 'hired' $contactStatuses = ['shortlisted', 'contacted', 'interview_scheduled', 'selected', 'hired']; if (in_array($dbStatus, $contactStatuses)) { if (!User::canContactWorker($user->id, $app->worker_id)) { $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; // Web: contact limit block redirect 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.'); } } // Update application status & history $history = $app->status_history ?: []; $history[] = [ 'status' => $dbStatus, 'timestamp' => now()->toIso8601String(), 'notes' => $request->input('notes'), ]; $updateData = [ 'status' => $dbStatus, 'status_history' => $history, ]; if ($request->filled('notes')) { $updateData['notes'] = $request->notes; } $app->update($updateData); // If shortlisted, automatically add to Saved Workers (Shortlist table) if not already saved if ($dbStatus === 'shortlisted') { $exists = \App\Models\Shortlist::where('employer_id', $user->id) ->where('worker_id', $app->worker_id) ->exists(); if (!$exists) { \App\Models\Shortlist::create([ 'employer_id' => $user->id, 'worker_id' => $app->worker_id, ]); } } // If hired, also update worker status if ($dbStatus === 'hired') { if ($app->worker) { $app->worker->update(['status' => 'Hired']); } } elseif ($dbStatus === 'rejected') { if ($app->worker) { $app->worker->update(['status' => 'active']); } } // Trigger notifications $this->triggerStatusNotification($app, $dbStatus); return back()->with('success', 'Candidate application status updated successfully to ' . $request->status); } private function triggerStatusNotification($application, $status) { $worker = $application->worker; $job = $application->jobPost; $employer = $job ? $job->employer : null; // When a new application is submitted: notify employer if ($status === 'applied') { if ($employer && $employer->fcm_token) { \App\Services\FCMService::sendPushNotification( $employer->fcm_token, "New Job Application", "A candidate has applied for your job: " . ($job->title ?? 'Job Post'), [ 'type' => 'new_job_application', 'job_id' => $job->id, 'application_id' => $application->id, ] ); } return; } // When status is updated: notify worker if ($worker && $worker->fcm_token) { $title = "Job Application Update"; $body = ""; switch (strtolower($status)) { case 'shortlisted': $body = "You have been shortlisted for the job: " . ($job->title ?? 'Job Post'); break; case 'contacted': $body = "An employer wants to contact you regarding the job: " . ($job->title ?? 'Job Post'); break; case 'interview scheduled': case 'interview_scheduled': $body = "An interview has been scheduled for the job: " . ($job->title ?? 'Job Post'); break; case 'selected': $body = "Congratulations! You have been selected for the job: " . ($job->title ?? 'Job Post'); break; case 'rejected': $body = "Your application for the job: " . ($job->title ?? 'Job Post') . " has been reviewed and rejected."; break; case 'hired': $body = "Congratulations! You have been hired for the job: " . ($job->title ?? 'Job Post'); break; default: $body = "Your application status for the job: " . ($job->title ?? 'Job Post') . " has changed to " . ucfirst($status); break; } \App\Services\FCMService::sendPushNotification( $worker->fcm_token, $title, $body, [ 'type' => 'job_application_status_update', 'job_id' => $job->id ?? '', 'application_id' => $application->id, 'status' => $status, ] ); } } }