From 2e89215c9f9a8aec12d3138a6e36686b48dfafc8 Mon Sep 17 00:00:00 2001 From: mohanmd Date: Tue, 23 Jun 2026 23:52:13 +0530 Subject: [PATCH 1/5] dashboard api for employer --- .../Api/EmployerProfileController.php | 31 ++++++- public/swagger.json | 84 +++++++++++++++++++ resources/js/Pages/Admin/Workers/Index.jsx | 72 ++++++++-------- resources/js/Pages/Employer/Workers/Show.jsx | 81 +++++++++--------- tests/Feature/EmployerProfileApiTest.php | 36 +++++++- vite.config.js | 2 +- 6 files changed, 226 insertions(+), 80 deletions(-) diff --git a/app/Http/Controllers/Api/EmployerProfileController.php b/app/Http/Controllers/Api/EmployerProfileController.php index 62ce416..e2c1559 100644 --- a/app/Http/Controllers/Api/EmployerProfileController.php +++ b/app/Http/Controllers/Api/EmployerProfileController.php @@ -54,6 +54,17 @@ public function getProfile(Request $request) 'occupation' => $profile->emirates_id_occupation, 'employer' => $profile->emirates_id_employer, 'issuing_place' => $profile->emirates_id_issue_place, + 'emirates_id' => [ + 'emirates_id_number' => $profile->emirates_id_number, + 'name' => $profile->emirates_id_name, + 'date_of_birth' => $profile->emirates_id_dob, + 'nationality' => $profile->nationality, + 'issue_date' => $profile->emirates_id_issue_date, + 'expiry_date' => $profile->emirates_id_expiry, + 'employer' => $profile->emirates_id_employer, + 'issue_place' => $profile->emirates_id_issue_place, + 'occupation' => $profile->emirates_id_occupation, + ], ]; return response()->json([ @@ -270,6 +281,17 @@ public function updateProfile(Request $request) 'occupation' => $profile->emirates_id_occupation, 'employer' => $profile->emirates_id_employer, 'issuing_place' => $profile->emirates_id_issue_place, + 'emirates_id' => [ + 'emirates_id_number' => $profile->emirates_id_number, + 'name' => $profile->emirates_id_name, + 'date_of_birth' => $profile->emirates_id_dob, + 'nationality' => $profile->nationality, + 'issue_date' => $profile->emirates_id_issue_date, + 'expiry_date' => $profile->emirates_id_expiry, + 'employer' => $profile->emirates_id_employer, + 'issue_place' => $profile->emirates_id_issue_place, + 'occupation' => $profile->emirates_id_occupation, + ], ]; return response()->json([ @@ -309,7 +331,14 @@ public function getDashboard(Request $request) $savedCandidates = \App\Models\Shortlist::where('employer_id', $employer->id)->count(); - $totalWorkers = \App\Models\Worker::where('status', 'active')->count(); + $totalWorkers = \App\Models\Worker::with('documents') + ->where('status', '!=', 'Hired') + ->where('status', '!=', 'hidden') + ->get() + ->filter(function ($w) { + return !str_contains(strtolower($w->passport_status), 'pending'); + }) + ->count(); // Resolve plan $sub = \Illuminate\Support\Facades\DB::table('subscriptions') diff --git a/public/swagger.json b/public/swagger.json index 8b48739..284d8cb 100644 --- a/public/swagger.json +++ b/public/swagger.json @@ -3897,6 +3897,48 @@ "issuing_place": { "type": "string", "example": "Dubai" + }, + "emirates_id": { + "type": "object", + "description": "Consolidated Emirates ID details.", + "properties": { + "emirates_id_number": { + "type": "string", + "example": "784-1988-5310327-2" + }, + "name": { + "type": "string", + "example": "Ahmad Bin Ahmed" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + }, + "nationality": { + "type": "string", + "example": "Emirati" + }, + "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" + } + } } } } @@ -4114,6 +4156,48 @@ "issuing_place": { "type": "string", "example": "Dubai" + }, + "emirates_id": { + "type": "object", + "description": "Consolidated Emirates ID details.", + "properties": { + "emirates_id_number": { + "type": "string", + "example": "784-1988-5310327-2" + }, + "name": { + "type": "string", + "example": "Ahmad Bin Ahmed" + }, + "date_of_birth": { + "type": "string", + "example": "1990-01-01" + }, + "nationality": { + "type": "string", + "example": "Emirati" + }, + "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" + } + } } } } diff --git a/resources/js/Pages/Admin/Workers/Index.jsx b/resources/js/Pages/Admin/Workers/Index.jsx index a34142c..a1ae972 100644 --- a/resources/js/Pages/Admin/Workers/Index.jsx +++ b/resources/js/Pages/Admin/Workers/Index.jsx @@ -805,10 +805,14 @@ export default function WorkerManagement({ workers }) { Document Type {safeRender(passportDoc.ocr_data?.document_type)} -
- Personal Number - {safeRender(passportDoc.ocr_data?.personal_number)} -
+ {passportDoc.ocr_data?.personal_number && + passportDoc.ocr_data?.personal_number !== 'N/A' && + passportDoc.ocr_data?.personal_number !== (passportDoc.ocr_data?.passport_number || passportDoc.number) && ( +
+ Personal Number + {safeRender(passportDoc.ocr_data?.personal_number)} +
+ )} {passportDoc.file_path && (
@@ -840,6 +844,16 @@ export default function WorkerManagement({ workers }) {
); } + + const sponsorObj = visaDoc.ocr_data?.sponsor; + const isSponsorObj = sponsorObj && typeof sponsorObj === 'object' && !Array.isArray(sponsorObj); + const sponsorName = isSponsorObj + ? (sponsorObj.name || sponsorObj.sponsor_name || sponsorObj.full_name) + : (visaDoc.ocr_data?.sponsor_name || (typeof sponsorObj === 'string' ? sponsorObj : null)); + const sponsorMobile = isSponsorObj + ? (sponsorObj.mobile || sponsorObj.mobile_number || sponsorObj.phone) + : null; + return (
@@ -851,22 +865,12 @@ export default function WorkerManagement({ workers }) { UID No {safeRender(visaDoc.ocr_data?.uid_no)}
-
- File Number - {safeRender(visaDoc.ocr_data?.file_number || visaDoc.number)} -
-
- ID Number - {safeRender(visaDoc.ocr_data?.id_number)} -
-
- Full Name - {safeRender(visaDoc.ocr_data?.full_name)} -
-
- Name - {safeRender(visaDoc.ocr_data?.name)} -
+ {visaDoc.ocr_data?.id_number && visaDoc.ocr_data?.id_number !== 'N/A' && ( +
+ ID Number + {safeRender(visaDoc.ocr_data?.id_number)} +
+ )}
Visa Type {safeRender(visaDoc.ocr_data?.visa_type)} @@ -875,10 +879,18 @@ export default function WorkerManagement({ workers }) { Profession {safeRender(visaDoc.ocr_data?.profession)}
-
- Sponsor Name - {safeRender(visaDoc.ocr_data?.sponsor_name)} -
+ {sponsorName && ( +
+ Sponsor Name + {safeRender(sponsorName)} +
+ )} + {sponsorMobile && ( +
+ Sponsor Mobile + {safeRender(sponsorMobile)} +
+ )}
Nationality @@ -888,14 +900,6 @@ export default function WorkerManagement({ workers }) { Country {safeRender(visaDoc.ocr_data?.country)}
-
- Passport No - {safeRender(visaDoc.ocr_data?.passport_no)} -
-
- Passport Number - {safeRender(visaDoc.ocr_data?.passport_number)} -
Date of Birth {safeRender(visaDoc.ocr_data?.date_of_birth)} @@ -917,10 +921,6 @@ export default function WorkerManagement({ workers }) { {safeRender(visaDoc.ocr_data?.expiry_date || visaDoc.expiry_date)}
-
- Accompanied By - {safeRender(visaDoc.ocr_data?.accompanied_by)} -
Document Type {safeRender(visaDoc.ocr_data?.document_type)} diff --git a/resources/js/Pages/Employer/Workers/Show.jsx b/resources/js/Pages/Employer/Workers/Show.jsx index 97772df..01feb3b 100644 --- a/resources/js/Pages/Employer/Workers/Show.jsx +++ b/resources/js/Pages/Employer/Workers/Show.jsx @@ -564,46 +564,49 @@ export default function Show({ worker }) {
- {visaDoc.ocr_data ? ( -
-
-
{t('full_name', 'Full Name')}
-
{safeRender(visaDoc.ocr_data.name)}
+ {visaDoc.ocr_data ? (() => { + const sponsorObj = visaDoc.ocr_data?.sponsor; + const isSponsorObj = sponsorObj && typeof sponsorObj === 'object' && !Array.isArray(sponsorObj); + const sponsorName = isSponsorObj + ? (sponsorObj.name || sponsorObj.sponsor_name || sponsorObj.full_name) + : (visaDoc.ocr_data?.sponsor_name || (typeof sponsorObj === 'string' ? sponsorObj : null)); + const sponsorMobile = isSponsorObj + ? (sponsorObj.mobile || sponsorObj.mobile_number || sponsorObj.phone) + : null; + + return ( +
+
+
{t('profession', 'Profession')}
+
{safeRender(visaDoc.ocr_data.profession || worker.visa_status)}
+
+ {sponsorName && ( +
+
{t('sponsor', 'Sponsor')}
+
{safeRender(sponsorName)}
+
+ )} + {sponsorMobile && ( +
+
{t('sponsor_mobile', 'Sponsor Mobile')}
+
{safeRender(sponsorMobile)}
+
+ )} +
+
{t('place_of_issue', 'Place of Issue')}
+
{safeRender(visaDoc.ocr_data.place_of_issue)}
+
+
+
{t('issue_date', 'Date of Issue')}
+
{safeRender(visaDoc.ocr_data.issue_date || visaDoc.issue_date)}
+
+
+
{t('expiry_date', 'Date of Expiry')}
+
{safeRender(visaDoc.ocr_data.expiry_date || visaDoc.expiry_date)}
+
-
-
{t('file_number', 'File Number')}
-
{safeRender(visaDoc.ocr_data.file_number || visaDoc.number)}
-
-
-
{t('profession', 'Profession')}
-
{safeRender(visaDoc.ocr_data.profession || worker.visa_status)}
-
-
-
{t('sponsor', 'Sponsor')}
-
{safeRender(visaDoc.ocr_data.sponsor || visaDoc.ocr_data.sponsor_name)}
-
-
-
{t('passport_number', 'Passport Number')}
-
{safeRender(visaDoc.ocr_data.passport_number)}
-
-
-
{t('place_of_issue', 'Place of Issue')}
-
{safeRender(visaDoc.ocr_data.place_of_issue)}
-
-
-
{t('issue_date', 'Date of Issue')}
-
{safeRender(visaDoc.ocr_data.issue_date || visaDoc.issue_date)}
-
-
-
{t('expiry_date', 'Date of Expiry')}
-
{safeRender(visaDoc.ocr_data.expiry_date || visaDoc.expiry_date)}
-
-
-
{t('accompanied_by', 'Accompanied By')}
-
{safeRender(visaDoc.ocr_data.accompanied_by)}
-
-
- ) : ( + ); + })() : (
{t('document_id', 'Document ID')}
diff --git a/tests/Feature/EmployerProfileApiTest.php b/tests/Feature/EmployerProfileApiTest.php index a7adec4..6ca7a7b 100644 --- a/tests/Feature/EmployerProfileApiTest.php +++ b/tests/Feature/EmployerProfileApiTest.php @@ -57,6 +57,7 @@ public function test_employer_can_get_profile() 'address', 'notifications', 'verification_status', + 'emirates_id', ] ] ]); @@ -91,6 +92,7 @@ public function test_employer_can_update_profile() 'phone', 'address', 'notifications', + 'emirates_id', ] ] ]); @@ -147,6 +149,17 @@ public function test_employer_can_update_profile_with_emirates_id_fields() 'card_number' => '151023946', 'full_name' => 'Mohammad Jobaier Mohammad Abul Kalam', 'gender' => 'M', + 'emirates_id' => [ + 'emirates_id_number' => '784-1987-5493842-5', + 'name' => 'Mohammad Jobaier Mohammad Abul Kalam', + 'date_of_birth' => '14/04/1987', + 'nationality' => 'Bangladesh', + 'issue_date' => '12/12/2025', + 'expiry_date' => '11/12/2027', + 'employer' => 'Msj International Technical Services L.L.C UAE', + 'issue_place' => 'Dubai', + 'occupation' => 'Electrician', + ] ] ] ]); @@ -248,23 +261,40 @@ public function test_employer_dashboard_returns_total_workers_active() \App\Models\Worker::query()->delete(); // Create 3 active workers - \App\Models\Worker::create([ + $w1 = \App\Models\Worker::create([ 'name' => 'Active 1', 'email' => 'a1@test.com', 'phone' => '+971501110001', 'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian', 'preferred_location' => 'Dubai', 'age' => 25, 'salary' => 1500, 'experience' => '3 Years', 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', + 'verified' => true, ]); - \App\Models\Worker::create([ + \App\Models\WorkerDocument::create([ + 'worker_id' => $w1->id, 'type' => 'passport', 'number' => 'P111', + 'issue_date' => '2020-01-01', 'expiry_date' => '2030-01-01', + ]); + + $w2 = \App\Models\Worker::create([ 'name' => 'Active 2', 'email' => 'a2@test.com', 'phone' => '+971501110002', 'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian', 'preferred_location' => 'Dubai', 'age' => 26, 'salary' => 1500, 'experience' => '3 Years', 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', + 'verified' => true, ]); - \App\Models\Worker::create([ + \App\Models\WorkerDocument::create([ + 'worker_id' => $w2->id, 'type' => 'passport', 'number' => 'P222', + 'issue_date' => '2020-01-01', 'expiry_date' => '2030-01-01', + ]); + + $w3 = \App\Models\Worker::create([ 'name' => 'Active 3', 'email' => 'a3@test.com', 'phone' => '+971501110003', 'password' => bcrypt('password'), 'status' => 'active', 'nationality' => 'Indian', 'preferred_location' => 'Dubai', 'age' => 27, 'salary' => 1500, 'experience' => '3 Years', 'availability' => 'Immediate', 'religion' => 'Christian', 'bio' => 'experienced worker', + 'verified' => true, + ]); + \App\Models\WorkerDocument::create([ + 'worker_id' => $w3->id, 'type' => 'passport', 'number' => 'P333', + 'issue_date' => '2020-01-01', 'expiry_date' => '2030-01-01', ]); // Create 2 inactive/hidden workers diff --git a/vite.config.js b/vite.config.js index 69084ca..fa54835 100644 --- a/vite.config.js +++ b/vite.config.js @@ -23,7 +23,7 @@ export default defineConfig({ port: 5173, cors: true, hmr: { - host: '192.168.0.166', + host: '192.168.29.131', }, watch: { ignored: ['**/storage/framework/views/**'], -- 2.43.0 From a2b299792ae3425129e7b2d55626eb0502e640e9 Mon Sep 17 00:00:00 2001 From: mohanmd Date: Tue, 23 Jun 2026 23:58:20 +0530 Subject: [PATCH 2/5] dashboard api for employer --- resources/js/Pages/Employer/Dashboard.jsx | 4 +--- resources/js/Pages/Employer/SelectedCandidates.jsx | 2 +- resources/js/Pages/Employer/Workers/Index.jsx | 2 +- resources/js/lang/en.json | 3 ++- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/resources/js/Pages/Employer/Dashboard.jsx b/resources/js/Pages/Employer/Dashboard.jsx index fdfc23d..96506db 100644 --- a/resources/js/Pages/Employer/Dashboard.jsx +++ b/resources/js/Pages/Employer/Dashboard.jsx @@ -181,7 +181,6 @@ export default function Dashboard({
{t('total_hired_workers', 'Total Hired Workers')}
{stats.hired_count}
-
{t('direct_contracts', 'Direct contracts initiated')}
{t('track_active_staff', 'Track active staff')} @@ -200,9 +199,8 @@ export default function Dashboard({
-
{t('shortlisted_candidates', 'Shortlisted Candidates')}
+
{t('saved_workers', 'Saved Workers')}
{stats.shortlisted_count}
-
{t('bookmark_list', 'Bookmark list for interviews')}
{t('open_shortlist', 'Open shortlist book')} diff --git a/resources/js/Pages/Employer/SelectedCandidates.jsx b/resources/js/Pages/Employer/SelectedCandidates.jsx index 43ddc4c..db2de5b 100644 --- a/resources/js/Pages/Employer/SelectedCandidates.jsx +++ b/resources/js/Pages/Employer/SelectedCandidates.jsx @@ -22,7 +22,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; -import FilterDrawer, { FilterSection, FilterSelect, FilterInput, FilterChips, FilterCheckboxList } from '../../Components/Employer/FilterDrawer'; +import FilterDrawer, { FilterSection, FilterSelect, FilterInput, FilterChips, FilterCheckboxList } from '../../components/Employer/FilterDrawer'; export default function SelectedCandidates({ selectedWorkers, diff --git a/resources/js/Pages/Employer/Workers/Index.jsx b/resources/js/Pages/Employer/Workers/Index.jsx index 3222068..0b6edb1 100644 --- a/resources/js/Pages/Employer/Workers/Index.jsx +++ b/resources/js/Pages/Employer/Workers/Index.jsx @@ -28,7 +28,7 @@ import { ShieldCheck, User } from 'lucide-react'; -import FilterDrawer, { FilterSection, FilterSelect, FilterInput, FilterChips, FilterCheckboxList } from '../../../Components/Employer/FilterDrawer'; +import FilterDrawer, { FilterSection, FilterSelect, FilterInput, FilterChips, FilterCheckboxList } from '../../../components/Employer/FilterDrawer'; const getLanguageFlag = (lang) => { const flags = { diff --git a/resources/js/lang/en.json b/resources/js/lang/en.json index f81c875..628a553 100644 --- a/resources/js/lang/en.json +++ b/resources/js/lang/en.json @@ -43,7 +43,8 @@ "total_hired_workers": "Total Hired Workers", "direct_contracts": "Direct contracts initiated", "track_active_staff": "Track active staff", - "shortlisted_candidates": "Shortlisted Candidates", + "shortlisted_candidates": "Saved Workers", + "saved_workers": "Saved Workers", "bookmark_list": "Bookmark list for interviews", "open_shortlist": "Open shortlist book", "discover_insights": "Discover Insights & Market Stats", -- 2.43.0 From 8a8f6e86f818c54270fbf3599f6fbd1c3eac7bea Mon Sep 17 00:00:00 2001 From: mohanmd Date: Wed, 24 Jun 2026 00:06:05 +0530 Subject: [PATCH 3/5] add experience, age, and visa details to candidates API --- app/Http/Controllers/Api/EmployerWorkerController.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/Http/Controllers/Api/EmployerWorkerController.php b/app/Http/Controllers/Api/EmployerWorkerController.php index 4db4198..a85497f 100644 --- a/app/Http/Controllers/Api/EmployerWorkerController.php +++ b/app/Http/Controllers/Api/EmployerWorkerController.php @@ -457,6 +457,11 @@ public function getCandidates(Request $request) 'in_country' => (bool)$w->in_country, 'visa_status' => $w->visa_status, 'gender' => $w->gender, + 'age' => $w->age, + 'experience' => $w->experience, + 'visa_expiry_date' => $w->visa_expiry_date, + 'document_expiry_days' => $w->document_expiry_days, + 'document_expiry_status' => $w->document_expiry_status, ]; })->filter()->values()->toArray(); @@ -499,6 +504,11 @@ public function getCandidates(Request $request) 'in_country' => (bool)$w->in_country, 'visa_status' => $w->visa_status, 'gender' => $w->gender, + 'age' => $w->age, + 'experience' => $w->experience, + 'visa_expiry_date' => $w->visa_expiry_date, + 'document_expiry_days' => $w->document_expiry_days, + 'document_expiry_status' => $w->document_expiry_status, ]; })->filter()->values()->toArray(); -- 2.43.0 From b2719888a20972ca3263f1c1cdcee1406f6016f6 Mon Sep 17 00:00:00 2001 From: mohanmd Date: Wed, 24 Jun 2026 00:18:31 +0530 Subject: [PATCH 4/5] format candidates details to match workers API response structure exactly --- .../Api/EmployerWorkerController.php | 68 ++++--------------- 1 file changed, 12 insertions(+), 56 deletions(-) diff --git a/app/Http/Controllers/Api/EmployerWorkerController.php b/app/Http/Controllers/Api/EmployerWorkerController.php index a85497f..398b3a0 100644 --- a/app/Http/Controllers/Api/EmployerWorkerController.php +++ b/app/Http/Controllers/Api/EmployerWorkerController.php @@ -109,6 +109,10 @@ private function formatWorker(Worker $w) 'updated_at' => $doc->updated_at?->toISOString(), ]; })->toArray(), + 'category' => [ + 'id' => 7, + 'name' => 'General Helper', + ], ]; } @@ -396,11 +400,11 @@ public function getCandidates(Request $request) // Fetch Job Applications $jobIds = JobPost::where('employer_id', $employerId)->pluck('id'); $applicationsQuery = JobApplication::whereIn('job_id', $jobIds) - ->with(['worker', 'jobPost']); + ->with(['worker.skills', 'worker.documents', 'jobPost']); // Fetch Direct Hiring Offers $directOffersQuery = JobOffer::where('employer_id', $employerId) - ->with('worker'); + ->with('worker.skills', 'worker.documents'); // Apply search filter if provided if ($request->filled('search')) { @@ -430,39 +434,15 @@ public function getCandidates(Request $request) elseif ($app->status === 'applied') $status = 'Reviewing'; else $status = ucfirst($app->status); - $langs = ($w->id % 2 === 0) ? ['English', 'Arabic'] : (($w->id % 3 === 0) ? ['English', 'Tagalog'] : ['English', 'Hindi', 'Arabic']); - $skillsList = ['cooking', 'driving', 'childcare', 'cleaning', 'elderly care', 'gardening']; - $mappedSkills = [ - $skillsList[$w->id % 6], - $skillsList[($w->id + 2) % 6] - ]; - $jobTypes = ['full-time', 'part-time', 'live-in', 'live-out']; - $preferredJobType = $w->preferred_job_type ?? $jobTypes[$w->id % 4]; + $formattedWorker = $this->formatWorker($w); - return [ + return array_merge($formattedWorker, [ 'id' => $app->id, 'worker_id' => $w->id, - 'name' => $w->name, - 'nationality' => $w->nationality, 'status' => $status, 'applied_at' => $app->created_at->format('Y-m-d H:i:s'), 'type' => 'application', - 'skills' => $mappedSkills, - 'languages' => $langs, - 'availability' => $w->availability ?? 'Immediate', - 'preferred_location' => $w->preferred_location, - 'area' => $w->area, - 'preferred_job_type' => $preferredJobType, - 'live_in_out' => $w->live_in_out, - 'in_country' => (bool)$w->in_country, - 'visa_status' => $w->visa_status, - 'gender' => $w->gender, - 'age' => $w->age, - 'experience' => $w->experience, - 'visa_expiry_date' => $w->visa_expiry_date, - 'document_expiry_days' => $w->document_expiry_days, - 'document_expiry_status' => $w->document_expiry_status, - ]; + ]); })->filter()->values()->toArray(); // Fetch Direct Hiring Offers @@ -477,39 +457,15 @@ public function getCandidates(Request $request) elseif ($offer->status === 'rejected') $status = 'Rejected'; elseif ($offer->status === 'pending') $status = 'Offer Sent'; - $langs = ($w->id % 2 === 0) ? ['English', 'Arabic'] : (($w->id % 3 === 0) ? ['English', 'Tagalog'] : ['English', 'Hindi', 'Arabic']); - $skillsList = ['cooking', 'driving', 'childcare', 'cleaning', 'elderly care', 'gardening']; - $mappedSkills = [ - $skillsList[$w->id % 6], - $skillsList[($w->id + 2) % 6] - ]; - $jobTypes = ['full-time', 'part-time', 'live-in', 'live-out']; - $preferredJobType = $w->preferred_job_type ?? $jobTypes[$w->id % 4]; + $formattedWorker = $this->formatWorker($w); - return [ + return array_merge($formattedWorker, [ 'id' => 'offer_' . $offer->id, 'worker_id' => $w->id, - 'name' => $w->name, - 'nationality' => $w->nationality, 'status' => $status, 'applied_at' => $offer->created_at->format('Y-m-d H:i:s'), 'type' => 'direct_offer', - 'skills' => $mappedSkills, - 'languages' => $langs, - 'availability' => $w->availability ?? 'Immediate', - 'preferred_location' => $w->preferred_location, - 'area' => $w->area, - 'preferred_job_type' => $preferredJobType, - 'live_in_out' => $w->live_in_out, - 'in_country' => (bool)$w->in_country, - 'visa_status' => $w->visa_status, - 'gender' => $w->gender, - 'age' => $w->age, - 'experience' => $w->experience, - 'visa_expiry_date' => $w->visa_expiry_date, - 'document_expiry_days' => $w->document_expiry_days, - 'document_expiry_status' => $w->document_expiry_status, - ]; + ]); })->filter()->values()->toArray(); // Merge and sort -- 2.43.0 From 3d97fb2e29b2bc29cb2d679367ad9e6ca4020ab9 Mon Sep 17 00:00:00 2001 From: mohanmd Date: Wed, 24 Jun 2026 00:42:06 +0530 Subject: [PATCH 5/5] dashboard api for employer --- .../Api/EmployerWorkerController.php | 10 +- .../Controllers/Employer/WorkerController.php | 10 +- resources/js/Pages/Employer/Shortlist.jsx | 18 +- resources/js/Pages/Employer/Workers/Index.jsx | 12 +- resources/js/Pages/Employer/Workers/Show.jsx | 179 ++++++++++++++++-- .../js/components/Employer/FilterDrawer.jsx | 30 +-- 6 files changed, 202 insertions(+), 57 deletions(-) diff --git a/app/Http/Controllers/Api/EmployerWorkerController.php b/app/Http/Controllers/Api/EmployerWorkerController.php index 398b3a0..705859f 100644 --- a/app/Http/Controllers/Api/EmployerWorkerController.php +++ b/app/Http/Controllers/Api/EmployerWorkerController.php @@ -92,7 +92,10 @@ private function formatWorker(Worker $w) 'documents' => $w->documents->map(function ($doc) { $ocrData = $doc->ocr_data; if ($doc->type === 'visa' && is_array($ocrData)) { - unset($ocrData['sponsor']); + unset($ocrData['file_number']); + unset($ocrData['name']); + unset($ocrData['passport_number']); + unset($ocrData['accompanied_by']); unset($ocrData['valid_until']); } return [ @@ -1000,7 +1003,10 @@ public function getWorkerDetail(Request $request, $id) 'documents' => $w->documents->map(function ($doc) { $ocrData = $doc->ocr_data; if ($doc->type === 'visa' && is_array($ocrData)) { - unset($ocrData['sponsor']); + unset($ocrData['file_number']); + unset($ocrData['name']); + unset($ocrData['passport_number']); + unset($ocrData['accompanied_by']); unset($ocrData['valid_until']); } return [ diff --git a/app/Http/Controllers/Employer/WorkerController.php b/app/Http/Controllers/Employer/WorkerController.php index e2c0371..8fe0a9f 100644 --- a/app/Http/Controllers/Employer/WorkerController.php +++ b/app/Http/Controllers/Employer/WorkerController.php @@ -390,6 +390,14 @@ public function show($id) 'in_country' => (bool) $w->in_country, 'preferred_location' => $w->preferred_location, 'documents' => $w->documents->map(function ($doc) { + $ocrData = $doc->ocr_data; + if ($doc->type === 'visa' && is_array($ocrData)) { + unset($ocrData['file_number']); + unset($ocrData['name']); + unset($ocrData['passport_number']); + unset($ocrData['accompanied_by']); + unset($ocrData['valid_until']); + } return [ 'id' => $doc->id, 'type' => $doc->type, @@ -398,7 +406,7 @@ public function show($id) 'expiry_date' => $doc->expiry_date, 'ocr_accuracy' => $doc->ocr_accuracy, 'file_path' => $doc->file_path ? url($doc->file_path) : null, - 'ocr_data' => $doc->ocr_data, + 'ocr_data' => $ocrData, ]; })->toArray(), ]; diff --git a/resources/js/Pages/Employer/Shortlist.jsx b/resources/js/Pages/Employer/Shortlist.jsx index 80ca5d0..8f3e113 100644 --- a/resources/js/Pages/Employer/Shortlist.jsx +++ b/resources/js/Pages/Employer/Shortlist.jsx @@ -123,23 +123,7 @@ export default function Shortlist({ shortlistedWorkers }) { {/* Passport Status Verification Badge & Visa Status */}
- {worker.visa_expiry_date ? ( -
- - Visa Exp: {worker.visa_expiry_date} -
- ) : ( -
- - Visa Expiry Pending -
- )} + {/* Removed visa expiry warning badge for cleaner UI */} {worker.visa_status && (worker.visa_status.toLowerCase().includes('tourist') || worker.visa_status.toLowerCase().includes('visit')) ? (
diff --git a/resources/js/Pages/Employer/Workers/Index.jsx b/resources/js/Pages/Employer/Workers/Index.jsx index 0b6edb1..e9faae7 100644 --- a/resources/js/Pages/Employer/Workers/Index.jsx +++ b/resources/js/Pages/Employer/Workers/Index.jsx @@ -704,17 +704,7 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [], {worker.visa_status}
)} - {worker.document_expiry_status && ( -
- 📅 {worker.document_expiry_status} -
- )} + {/* Removed visa expiry status badge for cleaner UI */}
diff --git a/resources/js/Pages/Employer/Workers/Show.jsx b/resources/js/Pages/Employer/Workers/Show.jsx index 01feb3b..69b3476 100644 --- a/resources/js/Pages/Employer/Workers/Show.jsx +++ b/resources/js/Pages/Employer/Workers/Show.jsx @@ -54,7 +54,7 @@ const getExpiryStatus = (expiryDate) => { if (diffDays < 0) { return { - text: `Expired \${Math.abs(diffDays)} days ago`, + text: 'Expired', color: 'text-rose-700 bg-rose-50 border-rose-200 font-black' }; } else if (diffDays === 0) { @@ -64,7 +64,7 @@ const getExpiryStatus = (expiryDate) => { }; } else { return { - text: `Valid until \${expiryDate} (Expires in \${diffDays} days)`, + text: `Valid until ${expiryDate}`, color: 'text-emerald-700 bg-emerald-50 border-emerald-150 font-black' }; } @@ -83,6 +83,7 @@ const safeRender = (value) => { export default function Show({ worker }) { const { t } = useTranslation(); const passportDoc = worker.documents?.find(d => d.type === 'passport'); + const emiratesIdDoc = worker.documents?.find(d => d.type === 'emirates_id' || d.type === 'eid'); const visaDoc = worker.documents?.find(d => d.type === 'visa'); const [showReportModal, setShowReportModal] = useState(false); @@ -434,8 +435,8 @@ export default function Show({ worker }) { {(() => { const expiryStatus = getExpiryStatus(passportDoc.expiry_date); return ( - - \${expiryStatus.text} + + {expiryStatus.text} ); })()} @@ -474,6 +475,14 @@ export default function Show({ worker }) {
{t('place_of_birth', 'Place of Birth')}
{safeRender(passportDoc.ocr_data.place_of_birth)}
+
+
{t('nationality', 'Nationality')}
+
{safeRender(passportDoc.ocr_data.nationality || worker.nationality)}
+
+
+
{t('issuing_country', 'Issuing Country')}
+
{safeRender(passportDoc.ocr_data.issuing_country)}
+
{t('issue_date', 'Date of Issue')}
{safeRender(passportDoc.ocr_data.date_of_issue || passportDoc.issue_date)}
@@ -483,8 +492,12 @@ export default function Show({ worker }) {
{safeRender(passportDoc.ocr_data.date_of_expiry || passportDoc.expiry_date)}
-
{t('issuing_country', 'Issuing Country')}
-
{safeRender(passportDoc.ocr_data.issuing_country)}
+
{t('authority', 'Authority')}
+
{safeRender(passportDoc.ocr_data.authority)}
+
+
+
{t('document_type', 'Document Type')}
+
{safeRender(passportDoc.ocr_data.document_type || passportDoc.type || 'passport')}
) : ( @@ -537,6 +550,106 @@ export default function Show({ worker }) {
)} + {/* Document Card: Emirates ID */} + {emiratesIdDoc && ( +
+
+
+ {t('emirates_id_details', 'Emirates ID Details')} + {(() => { + const expiryStatus = getExpiryStatus(emiratesIdDoc.expiry_date); + return ( + + {expiryStatus.text} + + ); + })()} +
+ {emiratesIdDoc.ocr_accuracy && ( + + {emiratesIdDoc.ocr_accuracy}% Match + + )} +
+ +
+ {emiratesIdDoc.ocr_data ? ( +
+
+
{t('emirates_id_number', 'Emirates ID Number')}
+
{safeRender(emiratesIdDoc.ocr_data.emirates_id_number || emiratesIdDoc.number)}
+
+
+
{t('full_name', 'Full Name')}
+
{safeRender(emiratesIdDoc.ocr_data.name || emiratesIdDoc.ocr_data.full_name)}
+
+
+
{t('nationality', 'Nationality')}
+
{safeRender(emiratesIdDoc.ocr_data.nationality)}
+
+
+
{t('date_of_birth', 'Date of Birth')}
+
{safeRender(emiratesIdDoc.ocr_data.date_of_birth || emiratesIdDoc.ocr_data.dob)}
+
+
+
{t('gender', 'Gender')}
+
{safeRender(emiratesIdDoc.ocr_data.gender || emiratesIdDoc.ocr_data.sex)}
+
+
+
{t('occupation', 'Occupation')}
+
{safeRender(emiratesIdDoc.ocr_data.occupation || emiratesIdDoc.ocr_data.profession)}
+
+
+
{t('employer', 'Employer')}
+
{safeRender(emiratesIdDoc.ocr_data.employer)}
+
+
+
{t('issue_date', 'Issue Date')}
+
{safeRender(emiratesIdDoc.ocr_data.issue_date || emiratesIdDoc.issue_date)}
+
+
+
{t('expiry_date', 'Expiry Date')}
+
{safeRender(emiratesIdDoc.ocr_data.expiry_date || emiratesIdDoc.expiry_date)}
+
+
+ ) : ( +
+
+
{t('document_id', 'Document ID')}
+
{emiratesIdDoc.number}
+
+
+
{t('issue_date', 'Issue')}
+
{emiratesIdDoc.issue_date || 'N/A'}
+
+
+
{t('expiry_date', 'Expiry')}
+
{emiratesIdDoc.expiry_date || 'N/A'}
+
+
+ )} + +
+ + + UAE PDPL Compliant Scan Storage (Permanently Purged) + + {emiratesIdDoc.file_path && ( + + + View Emirates ID Scan + + )} +
+
+
+ )} + {/* Document Card: Visa */} {visaDoc ? (
@@ -544,10 +657,10 @@ export default function Show({ worker }) {
{t('visa_details', 'Entry Visa Details')} {(() => { - const expiryStatus = getExpiryStatus(passportDoc.expiry_date); + const expiryStatus = getExpiryStatus(visaDoc.expiry_date || worker.visa_expiry_date); return ( - - \${expiryStatus.text} + + {expiryStatus.text} ); })()} @@ -576,13 +689,29 @@ export default function Show({ worker }) { return (
+
+
{t('entry_permit_no', 'Entry Permit No')}
+
{safeRender(visaDoc.ocr_data.entry_permit_no)}
+
+
+
{t('uid_no', 'UID No')}
+
{safeRender(visaDoc.ocr_data.uid_no)}
+
+
+
{t('id_number', 'ID Number')}
+
{safeRender(visaDoc.ocr_data.id_number || visaDoc.ocr_data.emirates_id_number)}
+
+
+
{t('visa_type', 'Visa Type')}
+
{safeRender(visaDoc.ocr_data.visa_type || 'ENTRY PERMIT')}
+
{t('profession', 'Profession')}
{safeRender(visaDoc.ocr_data.profession || worker.visa_status)}
{sponsorName && (
-
{t('sponsor', 'Sponsor')}
+
{t('sponsor_name', 'Sponsor Name')}
{safeRender(sponsorName)}
)} @@ -592,20 +721,44 @@ export default function Show({ worker }) {
{safeRender(sponsorMobile)}
)} +
+
{t('nationality', 'Nationality')}
+
{safeRender(visaDoc.ocr_data.nationality || worker.nationality)}
+
+
+
{t('country', 'Country')}
+
{safeRender(visaDoc.ocr_data.country || 'United Arab Emirates')}
+
+
+
{t('date_of_birth', 'Date of Birth')}
+
{safeRender(visaDoc.ocr_data.date_of_birth || visaDoc.ocr_data.dob)}
+
+
+
{t('place_of_birth', 'Place of Birth')}
+
{safeRender(visaDoc.ocr_data.place_of_birth)}
+
{t('place_of_issue', 'Place of Issue')}
{safeRender(visaDoc.ocr_data.place_of_issue)}
-
{t('issue_date', 'Date of Issue')}
+
{t('issue_date', 'Issue Date')}
{safeRender(visaDoc.ocr_data.issue_date || visaDoc.issue_date)}
-
{t('expiry_date', 'Date of Expiry')}
+
{t('expiry_date', 'Expiry Date')}
{safeRender(visaDoc.ocr_data.expiry_date || visaDoc.expiry_date)}
+
+
{t('document_type', 'Document Type')}
+
{safeRender(visaDoc.ocr_data.document_type || visaDoc.type || 'uae_visa')}
+
+
+
{t('ocr_accuracy', 'OCR Accuracy')}
+
{(visaDoc.ocr_accuracy || visaDoc.ocr_data?.ocr_accuracy || 99)}%
+
- ); + ); })() : (
diff --git a/resources/js/components/Employer/FilterDrawer.jsx b/resources/js/components/Employer/FilterDrawer.jsx index 4428b1b..26e7fab 100644 --- a/resources/js/components/Employer/FilterDrawer.jsx +++ b/resources/js/components/Employer/FilterDrawer.jsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import * as React from 'react'; import { X, RotateCcw, SlidersHorizontal } from 'lucide-react'; /** @@ -14,7 +14,7 @@ import { X, RotateCcw, SlidersHorizontal } from 'lucide-react'; */ export default function FilterDrawer({ open, onClose, onReset, activeCount = 0, title = 'Filters', children }) { // Lock body scroll when drawer is open - useEffect(() => { + React.useEffect(() => { if (open) { document.body.style.overflow = 'hidden'; } else { @@ -24,8 +24,8 @@ export default function FilterDrawer({ open, onClose, onReset, activeCount = 0, }, [open]); // Close on Escape key - useEffect(() => { - const handler = (e) => { if (e.key === 'Escape') onClose(); }; + React.useEffect(() => { + const handler = (e) => { if (e.key === 'Escape' && typeof onClose === 'function') onClose(); }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); }, [onClose]); @@ -164,14 +164,15 @@ export function FilterInput({ id, value, onChange, placeholder }) { * FilterChips — a row of selectable chip buttons for single-select options. * options: [{ value, label }] */ -export function FilterChips({ options, selected, onChange }) { +export function FilterChips({ options = [], selected, onChange }) { + const opts = options || []; return (
- {options.map(opt => ( + {opts.map(opt => (