Compare commits

..

No commits in common. "a2b299792ae3425129e7b2d55626eb0502e640e9" and "b95d76f00b78c5cdde8ee98b52e66f48c7d49be7" have entirely different histories.

10 changed files with 86 additions and 231 deletions

View File

@ -54,17 +54,6 @@ 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([
@ -281,17 +270,6 @@ 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([
@ -331,14 +309,7 @@ public function getDashboard(Request $request)
$savedCandidates = \App\Models\Shortlist::where('employer_id', $employer->id)->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();
$totalWorkers = \App\Models\Worker::where('status', 'active')->count();
// Resolve plan
$sub = \Illuminate\Support\Facades\DB::table('subscriptions')

View File

@ -3897,48 +3897,6 @@
"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"
}
}
}
}
}
@ -4156,48 +4114,6 @@
"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"
}
}
}
}
}

View File

@ -805,14 +805,10 @@ export default function WorkerManagement({ workers }) {
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Document Type</span>
<span className="font-bold text-slate-800">{safeRender(passportDoc.ocr_data?.document_type)}</span>
</div>
{passportDoc.ocr_data?.personal_number &&
passportDoc.ocr_data?.personal_number !== 'N/A' &&
passportDoc.ocr_data?.personal_number !== (passportDoc.ocr_data?.passport_number || passportDoc.number) && (
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Personal Number</span>
<span className="font-mono font-bold text-slate-800">{safeRender(passportDoc.ocr_data?.personal_number)}</span>
</div>
)}
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Personal Number</span>
<span className="font-mono font-bold text-slate-800">{safeRender(passportDoc.ocr_data?.personal_number)}</span>
</div>
</div>
{passportDoc.file_path && (
<div className="pt-2 border-t border-slate-200/60 flex justify-end">
@ -844,16 +840,6 @@ export default function WorkerManagement({ workers }) {
</div>
);
}
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 (
<div className="p-4 bg-slate-50 rounded-xl border border-slate-100 space-y-3">
<div className="grid grid-cols-2 gap-3 text-xs">
@ -865,12 +851,22 @@ export default function WorkerManagement({ workers }) {
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">UID No</span>
<span className="font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.uid_no)}</span>
</div>
{visaDoc.ocr_data?.id_number && visaDoc.ocr_data?.id_number !== 'N/A' && (
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">ID Number</span>
<span className="font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.id_number)}</span>
</div>
)}
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">File Number</span>
<span className="font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.file_number || visaDoc.number)}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">ID Number</span>
<span className="font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.id_number)}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Full Name</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.full_name)}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Name</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.name)}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Visa Type</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.visa_type)}</span>
@ -879,18 +875,10 @@ export default function WorkerManagement({ workers }) {
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Profession</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.profession)}</span>
</div>
{sponsorName && (
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Sponsor Name</span>
<span className="font-bold text-slate-800">{safeRender(sponsorName)}</span>
</div>
)}
{sponsorMobile && (
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Sponsor Mobile</span>
<span className="font-mono font-bold text-slate-800">{safeRender(sponsorMobile)}</span>
</div>
)}
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Sponsor Name</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.sponsor_name)}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Nationality</span>
@ -900,6 +888,14 @@ export default function WorkerManagement({ workers }) {
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Country</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.country)}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Passport No</span>
<span className="font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.passport_no)}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Passport Number</span>
<span className="font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.passport_number)}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Date of Birth</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.date_of_birth)}</span>
@ -921,6 +917,10 @@ export default function WorkerManagement({ workers }) {
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.expiry_date || visaDoc.expiry_date)}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Accompanied By</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.accompanied_by)}</span>
</div>
<div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-wider block">Document Type</span>
<span className="font-bold text-slate-800">{safeRender(visaDoc.ocr_data?.document_type)}</span>

View File

@ -181,6 +181,7 @@ export default function Dashboard({
<div>
<div className="text-xs font-semibold text-slate-400 uppercase tracking-widest">{t('total_hired_workers', 'Total Hired Workers')}</div>
<div className="text-3xl font-black text-slate-800 mt-1">{stats.hired_count}</div>
<div className="text-[11px] text-slate-500 font-medium mt-1">{t('direct_contracts', 'Direct contracts initiated')}</div>
</div>
<Link href="/employer/candidates" className="text-xs font-bold text-[#185FA5] hover:underline flex items-center space-x-1 pt-2 border-t border-slate-100">
<span>{t('track_active_staff', 'Track active staff')}</span>
@ -199,8 +200,9 @@ export default function Dashboard({
</span>
</div>
<div>
<div className="text-xs font-semibold text-slate-400 uppercase tracking-widest">{t('saved_workers', 'Saved Workers')}</div>
<div className="text-xs font-semibold text-slate-400 uppercase tracking-widest">{t('shortlisted_candidates', 'Shortlisted Candidates')}</div>
<div className="text-3xl font-black text-slate-800 mt-1">{stats.shortlisted_count}</div>
<div className="text-[11px] text-slate-500 font-medium mt-1">{t('bookmark_list', 'Bookmark list for interviews')}</div>
</div>
<Link href="/employer/shortlist" className="text-xs font-bold text-[#185FA5] hover:underline flex items-center space-x-1 pt-2 border-t border-slate-100">
<span>{t('open_shortlist', 'Open shortlist book')}</span>

View File

@ -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,

View File

@ -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 = {

View File

@ -564,49 +564,46 @@ export default function Show({ worker }) {
</div>
<div className="p-6">
{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 (
<div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6">
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('profession', 'Profession')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.profession || worker.visa_status)}</div>
</div>
{sponsorName && (
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor', 'Sponsor')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(sponsorName)}</div>
</div>
)}
{sponsorMobile && (
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor_mobile', 'Sponsor Mobile')}</div>
<div className="text-xs font-mono font-bold text-slate-800">{safeRender(sponsorMobile)}</div>
</div>
)}
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('place_of_issue', 'Place of Issue')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.place_of_issue)}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Date of Issue')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.issue_date || visaDoc.issue_date)}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Date of Expiry')}</div>
<div className="text-xs font-bold text-emerald-600">{safeRender(visaDoc.ocr_data.expiry_date || visaDoc.expiry_date)}</div>
</div>
{visaDoc.ocr_data ? (
<div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6">
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('full_name', 'Full Name')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.name)}</div>
</div>
);
})() : (
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('file_number', 'File Number')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.file_number || visaDoc.number)}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('profession', 'Profession')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.profession || worker.visa_status)}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor', 'Sponsor')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.sponsor || visaDoc.ocr_data.sponsor_name)}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('passport_number', 'Passport Number')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.passport_number)}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('place_of_issue', 'Place of Issue')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.place_of_issue)}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Date of Issue')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.issue_date || visaDoc.issue_date)}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Date of Expiry')}</div>
<div className="text-xs font-bold text-emerald-600">{safeRender(visaDoc.ocr_data.expiry_date || visaDoc.expiry_date)}</div>
</div>
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('accompanied_by', 'Accompanied By')}</div>
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.accompanied_by)}</div>
</div>
</div>
) : (
<div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6">
<div>
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_id', 'Document ID')}</div>

View File

@ -43,8 +43,7 @@
"total_hired_workers": "Total Hired Workers",
"direct_contracts": "Direct contracts initiated",
"track_active_staff": "Track active staff",
"shortlisted_candidates": "Saved Workers",
"saved_workers": "Saved Workers",
"shortlisted_candidates": "Shortlisted Candidates",
"bookmark_list": "Bookmark list for interviews",
"open_shortlist": "Open shortlist book",
"discover_insights": "Discover Insights & Market Stats",

View File

@ -57,7 +57,6 @@ public function test_employer_can_get_profile()
'address',
'notifications',
'verification_status',
'emirates_id',
]
]
]);
@ -92,7 +91,6 @@ public function test_employer_can_update_profile()
'phone',
'address',
'notifications',
'emirates_id',
]
]
]);
@ -149,17 +147,6 @@ 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',
]
]
]
]);
@ -261,40 +248,23 @@ public function test_employer_dashboard_returns_total_workers_active()
\App\Models\Worker::query()->delete();
// Create 3 active workers
$w1 = \App\Models\Worker::create([
\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\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([
\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\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([
\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

View File

@ -23,7 +23,7 @@ export default defineConfig({
port: 5173,
cors: true,
hmr: {
host: '192.168.29.131',
host: '192.168.0.166',
},
watch: {
ignored: ['**/storage/framework/views/**'],