id % 2 === 0) ? ['English', 'Arabic'] : (($w->id % 3 === 0) ? ['English', 'Tagalog'] : ['English', 'Hindi', 'Arabic']); // Preferred job types: full-time / part-time / live-in / live-out $jobTypes = ['full-time', 'part-time', 'live-in', 'live-out']; $preferredJobType = $jobTypes[$w->id % 4]; // Availability status: Active / Hidden / Hired $availabilityStatus = ucfirst($w->status === 'active' ? 'Active' : ($w->status === 'hidden' ? 'Hidden' : ($w->status === 'Hired' ? 'Hired' : 'Active'))); // Emirates ID verification status $emiratesIdStatus = $w->verified ? 'Emirates ID Verified' : 'EID Vetting Pending'; // Exact Skills mapping: cooking, driving, childcare, cleaning, elderly care, gardening $skillsList = ['cooking', 'driving', 'childcare', 'cleaning', 'elderly care', 'gardening']; $mappedSkills = [ $skillsList[$w->id % 6], $skillsList[($w->id + 2) % 6] ]; // Visa status $visaStatusesList = ['Residence Visa', 'Tourist Visa', 'Employment Visa', 'Cancelled Visa', 'Own Visa']; $visaStatus = $visaStatusesList[$w->id % 5]; // Optional profile photos $photos = [ 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=200', 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&q=80&w=200', 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&q=80&w=200', null ]; $photo = $photos[$w->id % 4]; $rating = 4.0 + (($w->id * 3) % 10) / 10.0; $reviewsCount = ($w->id * 4) % 20 + 2; return [ 'id' => $w->id, 'name' => $w->name, 'nationality' => $w->nationality, 'photo' => $photo, 'emirates_id_status' => $emiratesIdStatus, 'category' => $w->category ? $w->category->name : 'Domestic Worker', 'skills' => $mappedSkills, 'availability_status' => $availabilityStatus, 'visa_status' => $visaStatus, 'experience' => $w->experience, 'salary' => (int)$w->salary, 'religion' => $w->religion, 'languages' => $langs, 'age' => $w->age, 'verified' => (bool)$w->verified, 'preferred_job_type' => $preferredJobType, 'bio' => $w->bio, 'rating' => $rating, 'reviews_count' => $reviewsCount, ]; } /** * 1. GET /api/employers/workers * List of workers available for hiring. */ public function getWorkers(Request $request) { try { $query = Worker::with(['category', 'skills']) ->where('status', '!=', 'Hired') ->where('status', '!=', 'hidden'); // Apply search filter if provided if ($request->filled('search')) { $search = $request->search; $query->where(function($q) use ($search) { $q->where('name', 'like', "%{$search}%") ->orWhere('nationality', 'like', "%{$search}%") ->orWhere('religion', 'like', "%{$search}%"); }); } // Apply category filter if provided if ($request->filled('category_id')) { $query->where('category_id', $request->category_id); } // Apply nationality filter if provided if ($request->filled('nationality')) { $query->where('nationality', $request->nationality); } // Apply salary filters if ($request->filled('min_salary')) { $query->where('salary', '>=', $request->min_salary); } if ($request->filled('max_salary')) { $query->where('salary', '<=', $request->max_salary); } $dbWorkers = $query->latest()->get(); $formattedWorkers = $dbWorkers->map(function ($w) { return $this->formatWorker($w); }); return response()->json([ 'success' => true, 'data' => [ 'workers' => $formattedWorkers ] ], 200); } catch (\Exception $e) { logger()->error('Mobile API Employer Get Workers Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred while fetching the worker list.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * 2. GET /api/employers/candidates * List of candidates representing standard job applications and direct hiring offers. */ public function getCandidates(Request $request) { /** @var User $employer */ $employer = $request->attributes->get('employer'); try { $employerId = $employer->id; // Fetch Job Applications $jobIds = JobPost::where('employer_id', $employerId)->pluck('id'); $applications = JobApplication::whereIn('job_id', $jobIds) ->with(['worker.category', 'jobPost']) ->get(); $selectedWorkers = $applications->map(function ($app) { $w = $app->worker; if (!$w) return null; $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, 'worker_id' => $w->id, 'name' => $w->name, 'nationality' => $w->nationality, 'category' => $w->category ? $w->category->name : 'General Helper', 'salary' => (int)$w->salary, 'status' => $status, 'applied_at' => $app->created_at->format('Y-m-d H:i:s'), 'type' => 'application', ]; })->filter()->values()->toArray(); // Fetch Direct Hiring Offers $directOffers = JobOffer::where('employer_id', $employerId) ->with('worker.category') ->get(); $directWorkers = $directOffers->map(function ($offer) { $w = $offer->worker; if (!$w) return null; $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, 'worker_id' => $w->id, 'name' => $w->name, 'nationality' => $w->nationality, 'category' => $w->category ? $w->category->name : 'General Helper', 'salary' => (int)$offer->salary, 'status' => $status, 'applied_at' => $offer->created_at->format('Y-m-d H:i:s'), 'type' => 'direct_offer', ]; })->filter()->values()->toArray(); // Merge and sort $mergedCandidates = array_merge($selectedWorkers, $directWorkers); // Only display Hired candidates in the candidates pipeline (exclude active states) $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) { return $c && $c['status'] === 'Hired'; })); return response()->json([ 'success' => true, 'data' => [ 'candidates' => $mergedCandidates ] ], 200); } catch (\Exception $e) { logger()->error('Mobile API Employer Get Candidates Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred while fetching the candidate list.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * 3. POST /api/employers/candidates/hire or POST /api/employers/candidates/{id}/hire * Update status from active (or reviewing/pending) to hired. */ public function hireCandidate(Request $request, $id = null) { /** @var User $employer */ $employer = $request->attributes->get('employer'); // Let the client pass either route parameter id OR body parameter identifier $targetId = $id ?: $request->input('id') ?: $request->input('worker_id') ?: $request->input('application_id') ?: $request->input('offer_id'); if (!$targetId) { return response()->json([ 'success' => false, 'message' => 'Candidate or Worker identifier is required.' ], 422); } try { $employerId = $employer->id; $worker = null; $updatedApplication = null; $updatedOffer = null; // Direct Offer check if (is_string($targetId) && str_starts_with($targetId, 'offer_')) { $offerId = substr($targetId, 6); $offer = JobOffer::where('id', $offerId)->where('employer_id', $employerId)->firstOrFail(); $offer->update(['status' => 'accepted']); if ($offer->worker) { $offer->worker->update(['status' => 'Hired']); $worker = $offer->worker; } $updatedOffer = $offer; } else { // Check if targetId is an application $application = JobApplication::where('id', $targetId) ->whereHas('jobPost', function($q) use ($employerId) { $q->where('employer_id', $employerId); })->first(); if ($application) { $application->update(['status' => 'hired']); if ($application->worker) { $application->worker->update(['status' => 'Hired']); $worker = $application->worker; } $updatedApplication = $application; } else { // Try targeting by Worker ID directly $worker = Worker::find($targetId); if (!$worker) { return response()->json([ 'success' => false, 'message' => 'Worker, application, or job offer not found with the provided identifier.' ], 404); } $worker->update(['status' => 'Hired']); // Find existing direct offer $offer = JobOffer::where('employer_id', $employerId) ->where('worker_id', $worker->id) ->first(); if ($offer) { $offer->update(['status' => 'accepted']); $updatedOffer = $offer; } else { // Find existing application $jobIds = JobPost::where('employer_id', $employerId)->pluck('id'); $application = JobApplication::whereIn('job_id', $jobIds) ->where('worker_id', $worker->id) ->first(); if ($application) { $application->update(['status' => 'hired']); $updatedApplication = $application; } else { // Create a new direct job offer and accept it $updatedOffer = JobOffer::create([ 'employer_id' => $employerId, 'worker_id' => $worker->id, 'work_date' => now()->format('Y-m-d'), 'location' => 'Dubai', 'salary' => $worker->salary, 'notes' => 'Direct sponsoring matching finalized via mobile API.', 'status' => 'accepted', ]); } } } } return response()->json([ 'success' => true, 'message' => 'Candidate successfully marked as Hired!', 'data' => [ 'worker_id' => $worker ? $worker->id : null, 'worker_status' => $worker ? $worker->status : 'Hired', 'application' => $updatedApplication, 'offer' => $updatedOffer, ] ], 200); } catch (\Exception $e) { logger()->error('Mobile API Employer Hire Candidate Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred while marking the candidate as hired.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } /** * 4. GET /api/employers/workers/{id} * Get detailed worker profile, mirroring the web detail view. */ public function getWorkerDetail(Request $request, $id) { /** @var User $employer */ $employer = $request->attributes->get('employer'); try { $w = Worker::with(['category', 'skills', 'documents'])->find($id); if (!$w) { return response()->json([ 'success' => false, 'message' => 'Worker profile not found.' ], 404); } // Map languages $langs = ($w->id % 2 === 0) ? ['English', 'Arabic'] : (($w->id % 3 === 0) ? ['English', 'Tagalog'] : ['English', 'Hindi', 'Arabic']); // Preferred job types $jobTypes = ['full-time', 'part-time', 'live-in', 'live-out']; $preferredJobType = $jobTypes[$w->id % 4]; // Availability status $availabilityStatus = ucfirst($w->status === 'active' ? 'Active' : ($w->status === 'hidden' ? 'Hidden' : ($w->status === 'Hired' ? 'Hired' : 'Active'))); // Emirates ID status $emiratesIdStatus = $w->verified ? 'Emirates ID Verified' : 'EID Vetting Pending'; // Skills mapping $skillsList = ['cooking', 'driving', 'childcare', 'cleaning', 'elderly care', 'gardening']; $mappedSkills = [ $skillsList[$w->id % 6], $skillsList[($w->id + 2) % 6] ]; // Visa status $visaStatusesList = ['Residence Visa', 'Tourist Visa', 'Employment Visa', 'Cancelled Visa', 'Own Visa']; $visaStatus = $visaStatusesList[$w->id % 5]; // Profile photo $photos = [ 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=200', 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&q=80&w=200', 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&q=80&w=200', null ]; $photo = $photos[$w->id % 4]; $rating = 4.0 + (($w->id * 3) % 10) / 10.0; $reviewsCount = ($w->id * 4) % 20 + 2; // Hardcoded premium reviews matching web detail view $reviews = [ [ 'id' => 1, 'employer_name' => 'Fatima Al Mansoori', 'rating' => 5, 'date' => 'May 10, 2026', 'comment' => 'Extremely reliable and respectful. Professional work and great communication.', ], [ 'id' => 2, 'employer_name' => 'Michael Harrison', 'rating' => 4, 'date' => 'Mar 24, 2026', 'comment' => 'Very punctual, did exactly what was expected. Highly recommend.', ] ]; // Similar workers matching web view $simDb = Worker::with('category') ->where('id', '!=', $w->id) ->where('status', '!=', 'Hired') ->where('status', '!=', 'hidden') ->limit(3) ->get(); $similarWorkers = $simDb->map(function($sw) { $availabilityStatus = ucfirst($sw->status === 'active' ? 'Active' : ($sw->status === 'hidden' ? 'Hidden' : ($sw->status === 'Hired' ? 'Hired' : 'Active'))); return [ 'id' => $sw->id, 'name' => $sw->name, 'nationality' => $sw->nationality, 'category' => $sw->category ? $sw->category->name : 'Helper', 'salary' => (int)$sw->salary, 'rating' => 4.7, 'verified' => (bool)$sw->verified, 'availability_status' => $availabilityStatus, ]; })->toArray(); // Check if there is an existing conversation between this employer and this worker $conversation = \App\Models\Conversation::where('employer_id', $employer->id) ->where('worker_id', $w->id) ->first(); $workerProfile = [ 'id' => $w->id, 'name' => $w->name, 'nationality' => $w->nationality, 'photo' => $photo, 'emirates_id_status' => $emiratesIdStatus, 'category' => $w->category ? $w->category->name : 'Domestic Worker', 'skills' => $mappedSkills, 'availability_status' => $availabilityStatus, 'visa_status' => $visaStatus, 'experience' => $w->experience, 'experience_years' => 5, 'salary' => (int)$w->salary, 'religion' => $w->religion, 'languages' => $langs, 'age' => $w->age, 'verified' => (bool)$w->verified, 'preferred_job_type' => $preferredJobType, 'bio' => $w->bio, 'rating' => $rating, 'reviews_count' => $reviewsCount, 'reviews' => $reviews, 'similar_workers' => $similarWorkers, 'conversation_id' => $conversation ? $conversation->id : null, ]; return response()->json([ 'success' => true, 'data' => [ 'worker' => $workerProfile ] ], 200); } catch (\Exception $e) { logger()->error('Mobile API Employer Get Worker Detail Failure: ' . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'An error occurred while fetching the worker profile.', 'error' => app()->environment('local') ? $e->getMessage() : 'Internal Server Error' ], 500); } } }