From 51193f318861ce9e76aa5a785e33c1f2ee26f83f Mon Sep 17 00:00:00 2001 From: mohanmd Date: Thu, 2 Jul 2026 15:12:33 +0530 Subject: [PATCH] job , worker --- .../Api/EmployerWorkerController.php | 15 + .../Controllers/Api/WorkerAuthController.php | 4 + .../Controllers/Employer/WorkerController.php | 9 + app/Models/Worker.php | 1 + ...1_add_main_profession_to_workers_table.php | 28 ++ public/swagger.json | 388 ++++++++++++++---- resources/js/Layouts/EmployerLayout.jsx | 6 +- resources/js/Pages/Employer/Dashboard.jsx | 13 +- resources/js/Pages/Employer/Shortlist.jsx | 314 +++++++------- resources/js/Pages/Employer/Workers/Index.jsx | 164 +++++--- resources/js/Pages/Employer/Workers/Show.jsx | 16 +- tests/Feature/EmployerProfileApiTest.php | 10 + tests/Feature/WorkerJourneyApiTest.php | 4 + 13 files changed, 669 insertions(+), 303 deletions(-) create mode 100644 database/migrations/2026_07_02_114951_add_main_profession_to_workers_table.php diff --git a/app/Http/Controllers/Api/EmployerWorkerController.php b/app/Http/Controllers/Api/EmployerWorkerController.php index d5b7faf..a603573 100644 --- a/app/Http/Controllers/Api/EmployerWorkerController.php +++ b/app/Http/Controllers/Api/EmployerWorkerController.php @@ -57,6 +57,7 @@ private function formatWorker(Worker $w) 'deleted_at' => $w->deleted_at?->toISOString(), 'language' => $w->language, 'languages' => $langs, + 'main_profession' => $w->main_profession, 'preferred_location' => $w->preferred_location, 'country' => $w->country, 'city' => $w->city, @@ -154,6 +155,13 @@ public function getWorkers(Request $request) $workersArray = $formattedWorkers->toArray(); // Apply filters: gender, skills, language/languages, nationality, preferred_location, job_type, live_in_out, in_country, visa_status + if ($request->filled('main_profession')) { + $mainProfession = strtolower($request->main_profession); + $workersArray = array_values(array_filter($workersArray, function ($c) use ($mainProfession) { + return isset($c['main_profession']) && strtolower($c['main_profession']) === $mainProfession; + })); + } + if ($request->filled('gender')) { $gender = strtolower($request->gender); $workersArray = array_values(array_filter($workersArray, function ($c) use ($gender) { @@ -481,6 +489,13 @@ public function getCandidates(Request $request) })); // Apply filters: gender, skills, languages, nationality, availability, preferred_location, job_type, live_in_out, in_country, visa_status + if ($request->filled('main_profession')) { + $mainProfession = strtolower($request->main_profession); + $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($mainProfession) { + return isset($c['main_profession']) && strtolower($c['main_profession']) === $mainProfession; + })); + } + if ($request->filled('gender')) { $gender = strtolower($request->gender); $mergedCandidates = array_values(array_filter($mergedCandidates, function ($c) use ($gender) { diff --git a/app/Http/Controllers/Api/WorkerAuthController.php b/app/Http/Controllers/Api/WorkerAuthController.php index ab9d800..1c545e7 100644 --- a/app/Http/Controllers/Api/WorkerAuthController.php +++ b/app/Http/Controllers/Api/WorkerAuthController.php @@ -181,6 +181,7 @@ public function setupProfile(Request $request) 'gender' => 'nullable|string|in:male,female,other', 'live_in_out' => 'nullable|string|in:live_in,live_out', 'preferred_location' => 'nullable|string|max:255', + 'main_profession' => 'nullable|string|max:100', 'skills' => 'nullable|array', 'skills.*' => 'integer|exists:skills,id', 'fcm_token' => 'nullable|string|max:255', @@ -241,6 +242,7 @@ public function setupProfile(Request $request) 'gender' => $request->gender, 'live_in_out' => $request->live_in_out, 'preferred_location' => $request->preferred_location, + 'main_profession' => $request->main_profession, 'age' => 25, // Default — updated later in full profile 'salary' => 1500, // Default AED — updated later 'availability' => 'Immediate', @@ -318,6 +320,7 @@ public function register(Request $request) 'in_country' => 'nullable', 'visa_status' => 'nullable|string|max:100', 'preferred_job_type' => 'nullable|string|max:100', + 'main_profession' => 'nullable|string|max:100', 'gender' => 'nullable|string|in:male,female,other', 'live_in_out' => 'nullable|string|in:live_in,live_out', 'preferred_location' => 'nullable|string|max:255', @@ -453,6 +456,7 @@ public function register(Request $request) 'gender' => $request->gender, 'live_in_out' => $request->live_in_out, 'preferred_location' => $request->preferred_location, + 'main_profession' => $request->main_profession, 'age' => $age, 'salary' => $request->salary, 'availability' => 'Immediate', diff --git a/app/Http/Controllers/Employer/WorkerController.php b/app/Http/Controllers/Employer/WorkerController.php index 659cc6a..c1d429f 100644 --- a/app/Http/Controllers/Employer/WorkerController.php +++ b/app/Http/Controllers/Employer/WorkerController.php @@ -91,6 +91,7 @@ public function index(Request $request) 'emirates_id_status' => $emiratesIdStatus, 'passport_status' => $w->passport_status, 'category' => 'Domestic Worker', + 'main_profession' => $w->main_profession, 'skills' => $mappedSkills, 'visa_status' => $visaStatus, 'experience' => $w->experience, @@ -192,6 +193,12 @@ public function index(Request $request) return false; })); } + if ($request->filled('main_profession')) { + $mainProfession = strtolower($request->main_profession); + $workers = array_values(array_filter($workers, function ($c) use ($mainProfession) { + return isset($c['main_profession']) && strtolower($c['main_profession']) === $mainProfession; + })); + } if ($request->filled('gender')) { $gender = strtolower($request->gender); $workers = array_values(array_filter($workers, function ($c) use ($gender) { @@ -268,6 +275,7 @@ public function index(Request $request) $filtersMetadata = [ 'nationalities' => array_merge(['All Nationalities'], $dbNationalities), + 'professions' => ['All Professions', 'Housemaid', 'Nanny', 'Cook', 'Driver', 'Caregiver'], 'experienceLevels' => ['All Experience', '1-2 Years', '3-5 Years', '5+ Years'], 'religions' => ['All Religions', 'Christian', 'Muslim', 'Hindu', 'Buddhist'], 'languages' => ['All Languages', 'English', 'Arabic', 'Hindi', 'Tagalog'], @@ -367,6 +375,7 @@ public function show($id) 'emirates_id_status' => $emiratesIdStatus, 'passport_status' => $w->passport_status, 'category' => 'Domestic Worker', + 'main_profession' => $w->main_profession, 'skills' => $mappedSkills, 'visa_status' => $visaStatus, 'experience' => $w->experience, diff --git a/app/Models/Worker.php b/app/Models/Worker.php index b15a5d7..35b8ec6 100644 --- a/app/Models/Worker.php +++ b/app/Models/Worker.php @@ -35,6 +35,7 @@ class Worker extends Model 'in_country', 'visa_status', 'preferred_job_type', + 'main_profession', 'fcm_token', ]; diff --git a/database/migrations/2026_07_02_114951_add_main_profession_to_workers_table.php b/database/migrations/2026_07_02_114951_add_main_profession_to_workers_table.php new file mode 100644 index 0000000..8932c99 --- /dev/null +++ b/database/migrations/2026_07_02_114951_add_main_profession_to_workers_table.php @@ -0,0 +1,28 @@ +string('main_profession')->nullable()->after('preferred_job_type'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('workers', function (Blueprint $table) { + $table->dropColumn('main_profession'); + }); + } +}; diff --git a/public/swagger.json b/public/swagger.json index eae0d4a..cabb43f 100644 --- a/public/swagger.json +++ b/public/swagger.json @@ -63,28 +63,79 @@ "type": "object", "description": "Optional JSON object containing extracted organization/trade license details.", "properties": { - "document_type": { "type": "string", "example": "dubai_commercial_license" }, - "country": { "type": "string", "example": "United Arab Emirates" }, - "authority": { "type": "string", "example": "Dubai Economy and Tourism" }, - "license_no": { "type": "string", "example": "1426961" }, - "company_name": { "type": "string", "example": "NEST CLIMATE TECHNICAL SERVICES L.L.C" }, - "business_name": { "type": "string", "example": "NEST CLIMATE TECHNICAL SERVICES L.L.C" }, - "license_category": { "type": "string", "example": "Dep. of Economic Development" }, - "legal_type": { "type": "string", "example": "Limited Liability Company(LLC)" }, - "issue_date": { "type": "string", "example": "22/10/2024" }, - "expiry_date": { "type": "string", "example": "21/10/2026" }, - "main_license_no": { "type": "string", "example": "1426961" }, - "register_no": { "type": "string", "example": "2436760" }, - "dcci_no": { "type": "string", "example": "570232" }, + "document_type": { + "type": "string", + "example": "dubai_commercial_license" + }, + "country": { + "type": "string", + "example": "United Arab Emirates" + }, + "authority": { + "type": "string", + "example": "Dubai Economy and Tourism" + }, + "license_no": { + "type": "string", + "example": "1426961" + }, + "company_name": { + "type": "string", + "example": "NEST CLIMATE TECHNICAL SERVICES L.L.C" + }, + "business_name": { + "type": "string", + "example": "NEST CLIMATE TECHNICAL SERVICES L.L.C" + }, + "license_category": { + "type": "string", + "example": "Dep. of Economic Development" + }, + "legal_type": { + "type": "string", + "example": "Limited Liability Company(LLC)" + }, + "issue_date": { + "type": "string", + "example": "22/10/2024" + }, + "expiry_date": { + "type": "string", + "example": "21/10/2026" + }, + "main_license_no": { + "type": "string", + "example": "1426961" + }, + "register_no": { + "type": "string", + "example": "2436760" + }, + "dcci_no": { + "type": "string", + "example": "570232" + }, "members": { "type": "array", "items": { "type": "object", "properties": { - "person_no": { "type": "string", "example": "1709285" }, - "name": { "type": "string", "example": "SEYED ABDUL RAHMAN SHERIF NISAR" }, - "nationality": { "type": "string", "example": "India" }, - "role": { "type": "string", "example": "Manager" } + "person_no": { + "type": "string", + "example": "1709285" + }, + "name": { + "type": "string", + "example": "SEYED ABDUL RAHMAN SHERIF NISAR" + }, + "nationality": { + "type": "string", + "example": "India" + }, + "role": { + "type": "string", + "example": "Manager" + } } } } @@ -138,7 +189,6 @@ "type": "string", "example": "151023946" } - } }, "organization_name": { @@ -976,6 +1026,18 @@ "example": "full-time", "description": "Preferred job type." }, + "main_profession": { + "type": "string", + "example": "Housemaid", + "description": "Main profession of the worker selected from the master dropdown.", + "enum": [ + "Housemaid", + "Nanny", + "Cook", + "Driver", + "Caregiver" + ] + }, "live_in_out": { "type": "string", "enum": [ @@ -2333,6 +2395,18 @@ "example": "Dubai Marina", "description": "Preferred work location/area." }, + "main_profession": { + "type": "string", + "example": "Nanny", + "description": "Main profession of the worker selected from the master dropdown.", + "enum": [ + "Housemaid", + "Nanny", + "Cook", + "Driver", + "Caregiver" + ] + }, "fcm_token": { "type": "string", "example": "fcm_token_example", @@ -5054,6 +5128,15 @@ "type": "string" } }, + { + "name": "main_profession", + "in": "query", + "required": false, + "description": "Filter by main profession (e.g. Housemaid, Nanny, Cook, Driver, Caregiver).", + "schema": { + "type": "string" + } + }, { "name": "preferred_location", "in": "query", @@ -6245,14 +6328,44 @@ "description" ], "properties": { - "title": { "type": "string", "example": "Housekeeper Needed" }, - "workers_needed": { "type": "integer", "example": 2 }, - "location": { "type": "string", "example": "Dubai Marina" }, - "salary": { "type": "number", "example": 1800 }, - "job_type": { "type": "string", "enum": ["Full Time", "Part Time", "Contract"], "example": "Full Time" }, - "start_date": { "type": "string", "format": "date", "example": "2026-07-15" }, - "description": { "type": "string", "example": "Looking for an experienced housekeeper for a family in Dubai Marina." }, - "requirements": { "type": "string", "example": "Must speak fluent English. Minimum 3 years experience." } + "title": { + "type": "string", + "example": "Housekeeper Needed" + }, + "workers_needed": { + "type": "integer", + "example": 2 + }, + "location": { + "type": "string", + "example": "Dubai Marina" + }, + "salary": { + "type": "number", + "example": 1800 + }, + "job_type": { + "type": "string", + "enum": [ + "Full Time", + "Part Time", + "Contract" + ], + "example": "Full Time" + }, + "start_date": { + "type": "string", + "format": "date", + "example": "2026-07-15" + }, + "description": { + "type": "string", + "example": "Looking for an experienced housekeeper for a family in Dubai Marina." + }, + "requirements": { + "type": "string", + "example": "Must speak fluent English. Minimum 3 years experience." + } } } } @@ -6288,7 +6401,9 @@ "name": "id", "in": "path", "required": true, - "schema": { "type": "integer" } + "schema": { + "type": "integer" + } } ], "responses": { @@ -6319,7 +6434,9 @@ "name": "id", "in": "path", "required": true, - "schema": { "type": "integer" } + "schema": { + "type": "integer" + } } ], "responses": { @@ -6352,7 +6469,9 @@ "name": "id", "in": "path", "required": true, - "schema": { "type": "integer" } + "schema": { + "type": "integer" + } } ], "requestBody": { @@ -6372,15 +6491,53 @@ "status" ], "properties": { - "title": { "type": "string", "example": "Housekeeper Needed" }, - "workers_needed": { "type": "integer", "example": 2 }, - "location": { "type": "string", "example": "Dubai Marina" }, - "salary": { "type": "number", "example": 1800 }, - "job_type": { "type": "string", "enum": ["Full Time", "Part Time", "Contract"], "example": "Full Time" }, - "start_date": { "type": "string", "format": "date", "example": "2026-07-15" }, - "description": { "type": "string", "example": "Looking for an experienced housekeeper for a family in Dubai Marina." }, - "requirements": { "type": "string", "example": "Must speak fluent English. Minimum 3 years experience." }, - "status": { "type": "string", "enum": ["active", "closed", "draft"], "example": "active" } + "title": { + "type": "string", + "example": "Housekeeper Needed" + }, + "workers_needed": { + "type": "integer", + "example": 2 + }, + "location": { + "type": "string", + "example": "Dubai Marina" + }, + "salary": { + "type": "number", + "example": 1800 + }, + "job_type": { + "type": "string", + "enum": [ + "Full Time", + "Part Time", + "Contract" + ], + "example": "Full Time" + }, + "start_date": { + "type": "string", + "format": "date", + "example": "2026-07-15" + }, + "description": { + "type": "string", + "example": "Looking for an experienced housekeeper for a family in Dubai Marina." + }, + "requirements": { + "type": "string", + "example": "Must speak fluent English. Minimum 3 years experience." + }, + "status": { + "type": "string", + "enum": [ + "active", + "closed", + "draft" + ], + "example": "active" + } } } } @@ -6459,8 +6616,8 @@ "type": "object", "properties": { "application_id": { - "type": "integer", - "example": 10 + "type": "integer", + "example": 10 }, "status": { "type": "string", @@ -6512,7 +6669,7 @@ } } } - } + } } }, "401": { @@ -7480,23 +7637,39 @@ "schema": { "type": "object", "properties": { - "success": { "type": "boolean", "example": true }, + "success": { + "type": "boolean", + "example": true + }, "data": { "type": "object", "properties": { "application": { "type": "object", "properties": { - "id": { "type": "integer", "example": 12 }, - "status": { "type": "string", "example": "shortlisted" }, + "id": { + "type": "integer", + "example": 12 + }, + "status": { + "type": "string", + "example": "shortlisted" + }, "status_history": { "type": "array", "items": { "type": "object", "properties": { - "status": { "type": "string" }, - "timestamp": { "type": "string" }, - "notes": { "type": "string", "nullable": true } + "status": { + "type": "string" + }, + "timestamp": { + "type": "string" + }, + "notes": { + "type": "string", + "nullable": true + } } } } @@ -7625,34 +7798,103 @@ "license": { "type": "object", "properties": { - "document_type": { "type": "string", "example": "dubai_commercial_license" }, - "country": { "type": "string", "example": "United Arab Emirates" }, - "authority": { "type": "string", "example": "Dubai Economy and Tourism" }, - "license_no": { "type": "string", "example": "1426961" }, - "company_name": { "type": "string", "example": "NEST CLIMATE TECHNICAL SERVICES L.L.C" }, - "business_name": { "type": "string", "example": "NEST CLIMATE TECHNICAL SERVICES L.L.C" }, - "license_category": { "type": "string", "example": "Dep. of Economic Development" }, - "legal_type": { "type": "string", "example": "Limited Liability Company(LLC)" }, - "issue_date": { "type": "string", "example": "22/10/2024" }, - "expiry_date": { "type": "string", "example": "21/10/2026" }, - "main_license_no": { "type": "string", "example": "1426961" }, - "register_no": { "type": "string", "example": "2436760" }, - "dcci_no": { "type": "string", "example": "570232" } + "document_type": { + "type": "string", + "example": "dubai_commercial_license" + }, + "country": { + "type": "string", + "example": "United Arab Emirates" + }, + "authority": { + "type": "string", + "example": "Dubai Economy and Tourism" + }, + "license_no": { + "type": "string", + "example": "1426961" + }, + "company_name": { + "type": "string", + "example": "NEST CLIMATE TECHNICAL SERVICES L.L.C" + }, + "business_name": { + "type": "string", + "example": "NEST CLIMATE TECHNICAL SERVICES L.L.C" + }, + "license_category": { + "type": "string", + "example": "Dep. of Economic Development" + }, + "legal_type": { + "type": "string", + "example": "Limited Liability Company(LLC)" + }, + "issue_date": { + "type": "string", + "example": "22/10/2024" + }, + "expiry_date": { + "type": "string", + "example": "21/10/2026" + }, + "main_license_no": { + "type": "string", + "example": "1426961" + }, + "register_no": { + "type": "string", + "example": "2436760" + }, + "dcci_no": { + "type": "string", + "example": "570232" + } } }, "emirates_id": { "type": "object", "properties": { - "emirates_id_number": { "type": "string", "example": "784-1988-5310327-2" }, - "name": { "type": "string", "example": "KRISHNA PRASAD" }, - "date_of_birth": { "type": "string", "example": "1988-03-22" }, - "issue_date": { "type": "string", "example": "2023-04-11" }, - "expiry_date": { "type": "string", "example": "2028-04-11" }, - "employer": { "type": "string", "example": "Federal Authority" }, - "issue_place": { "type": "string", "example": "Abu Dhabi" }, - "occupation": { "type": "string", "example": "Manager" }, - "nationality": { "type": "string", "example": "NPL" }, - "gender": { "type": "string", "example": "Male" } + "emirates_id_number": { + "type": "string", + "example": "784-1988-5310327-2" + }, + "name": { + "type": "string", + "example": "KRISHNA PRASAD" + }, + "date_of_birth": { + "type": "string", + "example": "1988-03-22" + }, + "issue_date": { + "type": "string", + "example": "2023-04-11" + }, + "expiry_date": { + "type": "string", + "example": "2028-04-11" + }, + "employer": { + "type": "string", + "example": "Federal Authority" + }, + "issue_place": { + "type": "string", + "example": "Abu Dhabi" + }, + "occupation": { + "type": "string", + "example": "Manager" + }, + "nationality": { + "type": "string", + "example": "NPL" + }, + "gender": { + "type": "string", + "example": "Male" + } } }, "created_at": { @@ -7834,6 +8076,12 @@ } } }, + "main_profession": { + "type": "string", + "nullable": true, + "example": "Nanny", + "description": "The main profession of the worker" + }, "skills": { "type": "array", "items": { diff --git a/resources/js/Layouts/EmployerLayout.jsx b/resources/js/Layouts/EmployerLayout.jsx index 66c8892..19ea9f0 100644 --- a/resources/js/Layouts/EmployerLayout.jsx +++ b/resources/js/Layouts/EmployerLayout.jsx @@ -373,9 +373,9 @@ export default function EmployerLayout({ children, title, fullPage = false }) { - - + {/* Top Right Actions & Salary */} +
+ +
+
+ {worker.salary} AED +
+
/mo
+
+
+ - {/* Card Body */} -
-
- - {/* Ratings & Cost */} -
-
- - {worker.salary} AED - /mo -
-
+ {/* Card Body */} +
+
+ {/* Details Table */} +
+
+ + Exp: {worker.experience} +
+
+ + {worker.preferred_job_type} +
+
+ + {worker.preferred_location || 'Not Specified'} +
+
+ + {worker.rating || '4.5'} ({worker.reviews_count || '12'}) +
+
+ {/* Core exact platform Skills List */} +
+
{t('platform_skills', 'Platform Skills')}
+
+ {worker.skills?.slice(0, 3).map(skill => ( + + {skill} + + ))} + {worker.skills?.length > 3 && ( + + +{worker.skills.length - 3} More + + )} +
+
- {/* Details Table */} -
-
- - Exp: {worker.experience} -
-
- - {worker.preferred_job_type} -
-
- - {worker.preferred_location || 'Not Specified'} -
-
- - {worker.rating || '4.5'} ({worker.reviews_count || '12'}) -
-
+ {/* Languages Badges with Visual Flags */} +
+
{t('languages_spoken', 'Languages Spoken')}
+
+ {worker.languages?.map(lang => ( + + {getLanguageFlag(lang)} + {lang} + + ))} +
+
+
- {/* Core exact platform Skills List */} -
-
{t('platform_skills', 'Platform Skills')}
-
- {worker.skills?.map(skill => ( - - {skill} - - ))} -
-
+ {/* Actions block */} +
+
+ - {/* Languages Badges with Visual Flags */} -
-
{t('languages_spoken', 'Languages Spoken')}
-
- {worker.languages?.map(lang => ( - - {getLanguageFlag(lang)} - {lang} - - ))} -
-
-
+ +
- {/* Actions block */} -
-
- - - -
- -
- - {t('open_profile', 'Open Profile')} - - - - {t('message', 'Message')} - +
+ + {t('open_profile', 'Open Profile')} + + + + {t('message', 'Message')} +
diff --git a/resources/js/Pages/Employer/Workers/Index.jsx b/resources/js/Pages/Employer/Workers/Index.jsx index e9faae7..167df3d 100644 --- a/resources/js/Pages/Employer/Workers/Index.jsx +++ b/resources/js/Pages/Employer/Workers/Index.jsx @@ -47,6 +47,7 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [], const { t } = useTranslation(); // Basic Filters const [searchQuery, setSearchQuery] = useState(''); + const [selectedProfession, setSelectedProfession] = useState('All Professions'); const [selectedNationalities, setSelectedNationalities] = useState([]); const [selectedGender, setSelectedGender] = useState('All Genders'); const [selectedExperience, setSelectedExperience] = useState('All Experience'); @@ -108,9 +109,13 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [], const outCountryVal = params.get('out_country'); const visa = params.get('visa_status') || params.get('next_visa_type') || params.get('visa_type'); const gender = params.get('gender'); + const prof = params.get('main_profession') || params.get('profession'); let hasAdvanced = false; + if (prof) { + setSelectedProfession(prof); + } if (nat) { setSelectedNationalities(nat.split(',').map(x => x.trim()).filter(Boolean)); } @@ -196,6 +201,7 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [], const resetFilters = () => { setSearchQuery(''); + setSelectedProfession('All Professions'); setSelectedNationalities([]); setSelectedGender('All Genders'); setSelectedExperience('All Experience'); @@ -221,13 +227,14 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [], if (filterAccommodation !== 'All') count++; if (selectedNationalities.length > 0) count++; if (selectedGender !== 'All Genders') count++; + if (selectedProfession !== 'All Professions') count++; if (filterInCountry !== 'All') count++; if (filterInCountry === 'In Country' && filterVisaType !== 'All') count++; if (selectedSkills.length > 0) count++; if (selectedLanguages.length > 0) count++; if (maxSalary < 5000) count++; return count; - }, [filterLocation, filterJobType, filterAccommodation, selectedNationalities, selectedGender, filterInCountry, filterVisaType, selectedSkills, selectedLanguages, maxSalary]); + }, [filterLocation, filterJobType, filterAccommodation, selectedNationalities, selectedGender, selectedProfession, filterInCountry, filterVisaType, selectedSkills, selectedLanguages, maxSalary]); const activeFilterTags = useMemo(() => { const tags = []; @@ -236,13 +243,14 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [], if (filterAccommodation !== 'All') tags.push({ key: 'acc', label: `🏠 ${filterAccommodation.replace('_', '-')}`, clear: () => setFilterAccommodation('All') }); if (selectedNationalities.length > 0) tags.push({ key: 'nat', label: `🌍 ${selectedNationalities.length} Nat`, clear: () => setSelectedNationalities([]) }); if (selectedGender !== 'All Genders') tags.push({ key: 'gender', label: `🚻 ${selectedGender}`, clear: () => setSelectedGender('All Genders') }); + if (selectedProfession !== 'All Professions') tags.push({ key: 'profession', label: `🧑‍🔧 ${selectedProfession}`, clear: () => setSelectedProfession('All Professions') }); if (filterInCountry !== 'All') tags.push({ key: 'country', label: `✈️ ${filterInCountry}`, clear: () => { setFilterInCountry('All'); setFilterVisaType('All'); } }); if (filterInCountry === 'In Country' && filterVisaType !== 'All') tags.push({ key: 'visa', label: `🪪 ${filterVisaType}`, clear: () => setFilterVisaType('All') }); if (selectedSkills.length > 0) tags.push({ key: 'skills', label: `🛠 ${selectedSkills.length} skill${selectedSkills.length > 1 ? 's' : ''}`, clear: () => setSelectedSkills([]) }); if (selectedLanguages.length > 0) tags.push({ key: 'lang', label: `🗣 ${selectedLanguages.length} lang`, clear: () => setSelectedLanguages([]) }); if (maxSalary < 5000) tags.push({ key: 'salary', label: `💰 ≤${maxSalary} AED`, clear: () => setMaxSalary(5000) }); return tags; - }, [filterLocation, filterJobType, filterAccommodation, selectedNationalities, selectedGender, filterInCountry, filterVisaType, selectedSkills, selectedLanguages, maxSalary]); + }, [filterLocation, filterJobType, filterAccommodation, selectedNationalities, selectedGender, selectedProfession, filterInCountry, filterVisaType, selectedSkills, selectedLanguages, maxSalary]); // Filter and Sort Worker List const filteredWorkers = useMemo(() => { @@ -257,6 +265,7 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [], } // Dropdown filters + if (selectedProfession !== 'All Professions' && (!worker.main_profession || worker.main_profession.toLowerCase() !== selectedProfession.toLowerCase())) return false; if (selectedNationalities.length > 0 && (!worker.nationality || !selectedNationalities.map(n => n.toLowerCase()).includes(worker.nationality.toLowerCase()))) return false; if (selectedGender !== 'All Genders' && worker.gender && worker.gender.toLowerCase() !== selectedGender.toLowerCase()) return false; if (selectedExperience !== 'All Experience' && worker.experience !== selectedExperience) return false; @@ -332,6 +341,7 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [], }, [ initialWorkers, searchQuery, + selectedProfession, selectedNationalities, selectedGender, selectedExperience, @@ -400,6 +410,23 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [], activeCount={activeFilterCount} title="Filter Workers" > + {/* Profession */} + {filtersMetadata.professions && ( + + setSelectedProfession(e.target.value)} + > + {filtersMetadata.professions.map(prof => ( + + ))} + + + )} + {/* Preferred Location */} {/* Card Header with Photo Option and Availability indicator */} -
-
+
+
{/* Photo (optional) container */} -
+
{worker.photo ? ( {worker.name} ) : ( - + )}
-
-
+
+
{worker.name} - {false && ( - - - {t('verified')} + {worker.main_profession && ( + + {worker.main_profession} )}
{/* Passport Status Verification Badge & Visa Status */} -
- {worker.visa_expiry_date ? ( -
+ {worker.visa_expiry_date ? ( +
- + Visa Exp: {worker.visa_expiry_date}
) : ( -
- +
+ Visa Expiry Pending
)} {worker.visa_status && (worker.visa_status.toLowerCase().includes('tourist') || worker.visa_status.toLowerCase().includes('visit')) ? ( -
- - {worker.visa_status} ({t('warning', 'Warning')}) +
+ + {worker.visa_status}
) : worker.visa_status && worker.visa_status.toLowerCase().includes('cancelled') ? ( -
- - {worker.visa_status} ({t('warning', 'Warning')}) +
+ + {worker.visa_status}
) : ( -
+
{worker.visa_status}
)} - {/* Removed visa expiry status badge for cleaner UI */}
-
- - {worker.nationality} +
+ + {worker.nationality} {worker.age} {t('yrs', 'yrs')}
- {/* Top Right Availability status and bookmark */} -
+ {/* Top Right Actions & Salary */} +
+
+
+ {worker.salary} {t('aed', 'AED')} +
+
/{t('mo', 'mo')}
+
{/* Card Body */} -
-
- - {/* Ratings & Cost */} -
-
- - {worker.salary} {t('aed', 'AED')} - /{t('mo', 'mo')} -
-
- - +
+
{/* Details Table */} -
+
- + {t('experience', 'Exp')}: {worker.experience}
- - {worker.preferred_job_type} + + {worker.preferred_job_type}
- + {worker.preferred_location || 'Not Specified'}
- + {worker.rating} ({worker.reviews_count})
{/* Core exact platform Skills List */} -
-
{t('platform_skills', 'Platform Skills')}
+
+
{t('platform_skills', 'Platform Skills')}
- {worker.skills?.map(skill => ( - + {worker.skills?.slice(0, 3).map(skill => ( + {skill} ))} + {worker.skills?.length > 3 && ( + + +{worker.skills.length - 3} More + + )}
{/* Languages Badges with Visual Flags */} -
-
{t('languages_spoken', 'Languages Spoken')}
-
+
+
{t('languages_spoken', 'Languages Spoken')}
+
{worker.languages?.map(lang => ( - + {getLanguageFlag(lang)} {lang} @@ -793,12 +818,12 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [],
{/* Actions block */} -
+
View Profile -
@@ -947,8 +971,12 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [],

{previewWorker.name}

-
+ {previewWorker.main_profession && ( +
+ {previewWorker.main_profession} +
+ )} {previewWorker.visa_expiry_date ? (

{worker.name}

-
+ {worker.main_profession && ( +
+ {worker.main_profession} +
+ )} {/* Passport verification status bar & Visa Status */}
@@ -314,6 +318,16 @@ export default function Show({ worker }) { {worker.name}
+ {worker.main_profession && ( +
+
+ + {t('main_profession', 'Main Profession')} +
+ {worker.main_profession} +
+ )} +
diff --git a/tests/Feature/EmployerProfileApiTest.php b/tests/Feature/EmployerProfileApiTest.php index 159054d..edd043a 100644 --- a/tests/Feature/EmployerProfileApiTest.php +++ b/tests/Feature/EmployerProfileApiTest.php @@ -561,6 +561,16 @@ public function test_employer_can_get_plans_and_pay_with_numeric_id() $this->assertIsInt($plan['id']); } + // Create sponsor record for payment verification + \App\Models\Sponsor::create([ + 'email' => $this->employer->email, + 'full_name' => $this->employer->name, + 'mobile' => '+971501112222', + 'password' => $this->employer->password, + 'is_verified' => true, + 'status' => 'active', + ]); + // 2. Submit payment with a numeric plan ID (e.g. 3) $responsePayment = $this->postJson('/api/employers/payment', [ 'email' => $this->employer->email, diff --git a/tests/Feature/WorkerJourneyApiTest.php b/tests/Feature/WorkerJourneyApiTest.php index 47e9aa0..3cf25d2 100644 --- a/tests/Feature/WorkerJourneyApiTest.php +++ b/tests/Feature/WorkerJourneyApiTest.php @@ -154,6 +154,7 @@ public function test_s1_complete_basic_profile_setup() 'nationality' => 'Indian', 'language' => 'HI', 'gender' => 'male', + 'main_profession' => 'Nanny', 'skills' => [], ]); @@ -180,6 +181,7 @@ public function test_s1_complete_basic_profile_setup() 'phone' => $phone, 'language' => 'HI', 'gender' => 'male', + 'main_profession' => 'Nanny', 'verified' => false, 'status' => 'active', ]); @@ -386,6 +388,7 @@ public function test_register_with_new_api_fields() 'in_country' => 'true', 'visa_status' => 'Tourist Visa', 'preferred_job_type' => 'full-time', + 'main_profession' => 'Housemaid', 'gender' => 'male', 'live_in_out' => 'live_out', 'preferred_location' => 'Dubai Marina', @@ -399,6 +402,7 @@ public function test_register_with_new_api_fields() 'in_country' => true, 'visa_status' => 'Tourist Visa', 'preferred_job_type' => 'full-time', + 'main_profession' => 'Housemaid', 'gender' => 'male', 'live_in_out' => 'live_out', 'preferred_location' => 'Dubai Marina',