Compare commits
No commits in common. "1bf07aa0fd7bc252a2f25dd47e6dd8ad147b6fe1" and "765292e6251d85e0df2994e64ffc8a9def304f27" have entirely different histories.
1bf07aa0fd
...
765292e625
@ -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')
|
||||
|
||||
@ -92,10 +92,7 @@ private function formatWorker(Worker $w)
|
||||
'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['sponsor']);
|
||||
unset($ocrData['valid_until']);
|
||||
}
|
||||
return [
|
||||
@ -112,10 +109,6 @@ private function formatWorker(Worker $w)
|
||||
'updated_at' => $doc->updated_at?->toISOString(),
|
||||
];
|
||||
})->toArray(),
|
||||
'category' => [
|
||||
'id' => 7,
|
||||
'name' => 'General Helper',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -403,11 +396,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.skills', 'worker.documents', 'jobPost']);
|
||||
->with(['worker', 'jobPost']);
|
||||
|
||||
// Fetch Direct Hiring Offers
|
||||
$directOffersQuery = JobOffer::where('employer_id', $employerId)
|
||||
->with('worker.skills', 'worker.documents');
|
||||
->with('worker');
|
||||
|
||||
// Apply search filter if provided
|
||||
if ($request->filled('search')) {
|
||||
@ -437,15 +430,34 @@ public function getCandidates(Request $request)
|
||||
elseif ($app->status === 'applied') $status = 'Reviewing';
|
||||
else $status = ucfirst($app->status);
|
||||
|
||||
$formattedWorker = $this->formatWorker($w);
|
||||
$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];
|
||||
|
||||
return array_merge($formattedWorker, [
|
||||
return [
|
||||
'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,
|
||||
];
|
||||
})->filter()->values()->toArray();
|
||||
|
||||
// Fetch Direct Hiring Offers
|
||||
@ -460,15 +472,34 @@ public function getCandidates(Request $request)
|
||||
elseif ($offer->status === 'rejected') $status = 'Rejected';
|
||||
elseif ($offer->status === 'pending') $status = 'Offer Sent';
|
||||
|
||||
$formattedWorker = $this->formatWorker($w);
|
||||
$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];
|
||||
|
||||
return array_merge($formattedWorker, [
|
||||
return [
|
||||
'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,
|
||||
];
|
||||
})->filter()->values()->toArray();
|
||||
|
||||
// Merge and sort
|
||||
@ -1003,10 +1034,7 @@ 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['file_number']);
|
||||
unset($ocrData['name']);
|
||||
unset($ocrData['passport_number']);
|
||||
unset($ocrData['accompanied_by']);
|
||||
unset($ocrData['sponsor']);
|
||||
unset($ocrData['valid_until']);
|
||||
}
|
||||
return [
|
||||
|
||||
@ -390,14 +390,6 @@ 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,
|
||||
@ -406,7 +398,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' => $ocrData,
|
||||
'ocr_data' => $doc->ocr_data,
|
||||
];
|
||||
})->toArray(),
|
||||
];
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -123,7 +123,23 @@ export default function Shortlist({ shortlistedWorkers }) {
|
||||
|
||||
{/* Passport Status Verification Badge & Visa Status */}
|
||||
<div className="flex flex-wrap gap-1 mt-0.5">
|
||||
{/* Removed visa expiry warning badge for cleaner UI */}
|
||||
{worker.visa_expiry_date ? (
|
||||
<div className={`flex items-center space-x-1 text-[9px] font-black uppercase px-1.5 py-0.5 rounded border ${
|
||||
worker.document_expiry_days !== null && worker.document_expiry_days <= 30
|
||||
? 'text-rose-700 bg-rose-50 border-rose-200 animate-pulse'
|
||||
: worker.document_expiry_days !== null && worker.document_expiry_days <= 90
|
||||
? 'text-amber-700 bg-amber-50 border-amber-200'
|
||||
: 'text-[#185FA5] bg-blue-50 border-blue-100'
|
||||
}`}>
|
||||
<Calendar className="w-3 h-3 flex-shrink-0" />
|
||||
<span>Visa Exp: {worker.visa_expiry_date}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center space-x-1 text-[9px] font-black uppercase text-amber-700 bg-amber-50 px-1.5 py-0.5 rounded border border-amber-200">
|
||||
<Calendar className="w-3 h-3 flex-shrink-0 text-amber-600" />
|
||||
<span>Visa Expiry Pending</span>
|
||||
</div>
|
||||
)}
|
||||
{worker.visa_status && (worker.visa_status.toLowerCase().includes('tourist') || worker.visa_status.toLowerCase().includes('visit')) ? (
|
||||
<div className="flex items-center space-x-1 text-[9px] font-black uppercase text-amber-700 bg-amber-50 px-1.5 py-0.5 rounded border border-amber-200">
|
||||
<AlertTriangle className="w-3 h-3 text-amber-600 flex-shrink-0 animate-bounce" />
|
||||
|
||||
@ -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 = {
|
||||
@ -704,7 +704,17 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [],
|
||||
<span>{worker.visa_status}</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Removed visa expiry status badge for cleaner UI */}
|
||||
{worker.document_expiry_status && (
|
||||
<div className={`flex items-center space-x-1 text-[9px] font-black uppercase px-1.5 py-0.5 rounded border ${
|
||||
worker.document_expiry_days !== null && worker.document_expiry_days <= 30
|
||||
? 'text-rose-700 bg-rose-50 border-rose-200 animate-pulse'
|
||||
: worker.document_expiry_days !== null && worker.document_expiry_days <= 90
|
||||
? 'text-amber-700 bg-amber-50 border-amber-200'
|
||||
: 'text-[#185FA5] bg-blue-50 border-blue-100'
|
||||
}`}>
|
||||
<span>📅 {worker.document_expiry_status}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-1.5 text-xs text-slate-500 mt-1">
|
||||
|
||||
@ -54,7 +54,7 @@ const getExpiryStatus = (expiryDate) => {
|
||||
|
||||
if (diffDays < 0) {
|
||||
return {
|
||||
text: 'Expired',
|
||||
text: `Expired \${Math.abs(diffDays)} days ago`,
|
||||
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}`,
|
||||
text: `Valid until \${expiryDate} (Expires in \${diffDays} days)`,
|
||||
color: 'text-emerald-700 bg-emerald-50 border-emerald-150 font-black'
|
||||
};
|
||||
}
|
||||
@ -83,7 +83,6 @@ 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);
|
||||
@ -435,8 +434,8 @@ export default function Show({ worker }) {
|
||||
{(() => {
|
||||
const expiryStatus = getExpiryStatus(passportDoc.expiry_date);
|
||||
return (
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded text-[8px] font-black border uppercase tracking-wider ${expiryStatus.color}`}>
|
||||
{expiryStatus.text}
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded text-[8px] font-black border uppercase tracking-wider \${expiryStatus.color}`}>
|
||||
\${expiryStatus.text}
|
||||
</span>
|
||||
);
|
||||
})()}
|
||||
@ -475,14 +474,6 @@ export default function Show({ worker }) {
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('place_of_birth', 'Place of Birth')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.place_of_birth)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('nationality', 'Nationality')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.nationality || worker.nationality)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issuing_country', 'Issuing Country')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.issuing_country)}</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(passportDoc.ocr_data.date_of_issue || passportDoc.issue_date)}</div>
|
||||
@ -492,12 +483,8 @@ export default function Show({ worker }) {
|
||||
<div className="text-xs font-bold text-emerald-600">{safeRender(passportDoc.ocr_data.date_of_expiry || passportDoc.expiry_date)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('authority', 'Authority')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.authority)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_type', 'Document Type')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.document_type || passportDoc.type || 'passport')}</div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issuing_country', 'Issuing Country')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(passportDoc.ocr_data.issuing_country)}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@ -550,106 +537,6 @@ export default function Show({ worker }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Document Card: Emirates ID */}
|
||||
{emiratesIdDoc && (
|
||||
<div className="bg-white border border-slate-200 rounded-2xl overflow-hidden shadow-xs hover:shadow-sm lg:col-span-2">
|
||||
<div className="bg-slate-50 px-5 py-3.5 border-b border-slate-100 flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-[11px] font-black text-slate-800 uppercase tracking-wider">{t('emirates_id_details', 'Emirates ID Details')}</span>
|
||||
{(() => {
|
||||
const expiryStatus = getExpiryStatus(emiratesIdDoc.expiry_date);
|
||||
return (
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded text-[8px] font-black border uppercase tracking-wider ${expiryStatus.color}`}>
|
||||
{expiryStatus.text}
|
||||
</span>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
{emiratesIdDoc.ocr_accuracy && (
|
||||
<span className="text-[9px] font-bold text-emerald-600 bg-emerald-50/50 border border-emerald-100 px-2 py-0.5 rounded-full">
|
||||
{emiratesIdDoc.ocr_accuracy}% Match
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
{emiratesIdDoc.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('emirates_id_number', 'Emirates ID Number')}</div>
|
||||
<div className="text-xs font-mono font-bold text-slate-800">{safeRender(emiratesIdDoc.ocr_data.emirates_id_number || emiratesIdDoc.number)}</div>
|
||||
</div>
|
||||
<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(emiratesIdDoc.ocr_data.name || emiratesIdDoc.ocr_data.full_name)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('nationality', 'Nationality')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(emiratesIdDoc.ocr_data.nationality)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('date_of_birth', 'Date of Birth')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(emiratesIdDoc.ocr_data.date_of_birth || emiratesIdDoc.ocr_data.dob)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('gender', 'Gender')}</div>
|
||||
<div className="text-xs font-bold text-slate-800 uppercase">{safeRender(emiratesIdDoc.ocr_data.gender || emiratesIdDoc.ocr_data.sex)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('occupation', 'Occupation')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(emiratesIdDoc.ocr_data.occupation || emiratesIdDoc.ocr_data.profession)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('employer', 'Employer')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(emiratesIdDoc.ocr_data.employer)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Issue Date')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(emiratesIdDoc.ocr_data.issue_date || emiratesIdDoc.issue_date)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Expiry Date')}</div>
|
||||
<div className="text-xs font-bold text-emerald-600">{safeRender(emiratesIdDoc.ocr_data.expiry_date || emiratesIdDoc.expiry_date)}</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>
|
||||
<div className="text-xs font-bold text-slate-800">{emiratesIdDoc.number}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Issue')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{emiratesIdDoc.issue_date || 'N/A'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Expiry')}</div>
|
||||
<div className="text-xs font-bold text-emerald-600">{emiratesIdDoc.expiry_date || 'N/A'}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-6 pt-4 border-t border-slate-100 flex items-center justify-between text-xs text-slate-500 font-medium">
|
||||
<span className="flex items-center space-x-1.5">
|
||||
<ShieldCheck className="w-4 h-4 text-emerald-600" />
|
||||
<span>UAE PDPL Compliant Scan Storage (Permanently Purged)</span>
|
||||
</span>
|
||||
{emiratesIdDoc.file_path && (
|
||||
<a
|
||||
href={emiratesIdDoc.file_path}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center space-x-1 text-[9px] font-black text-blue-600 hover:text-blue-700 bg-blue-50 px-2.5 py-1.5 rounded-lg border border-blue-155 transition-colors uppercase tracking-wider"
|
||||
>
|
||||
<FileText className="w-3.5 h-3.5" />
|
||||
<span>View Emirates ID Scan</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Document Card: Visa */}
|
||||
{visaDoc ? (
|
||||
<div className="bg-white border border-slate-200 rounded-2xl overflow-hidden shadow-xs hover:shadow-sm lg:col-span-2">
|
||||
@ -657,10 +544,10 @@ export default function Show({ worker }) {
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-[11px] font-black text-slate-800 uppercase tracking-wider">{t('visa_details', 'Entry Visa Details')}</span>
|
||||
{(() => {
|
||||
const expiryStatus = getExpiryStatus(visaDoc.expiry_date || worker.visa_expiry_date);
|
||||
const expiryStatus = getExpiryStatus(passportDoc.expiry_date);
|
||||
return (
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded text-[8px] font-black border uppercase tracking-wider ${expiryStatus.color}`}>
|
||||
{expiryStatus.text}
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded text-[8px] font-black border uppercase tracking-wider \${expiryStatus.color}`}>
|
||||
\${expiryStatus.text}
|
||||
</span>
|
||||
);
|
||||
})()}
|
||||
@ -677,89 +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('entry_permit_no', 'Entry Permit No')}</div>
|
||||
<div className="text-xs font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data.entry_permit_no)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('uid_no', 'UID No')}</div>
|
||||
<div className="text-xs font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data.uid_no)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('id_number', 'ID Number')}</div>
|
||||
<div className="text-xs font-mono font-bold text-slate-800">{safeRender(visaDoc.ocr_data.id_number || visaDoc.ocr_data.emirates_id_number)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('visa_type', 'Visa Type')}</div>
|
||||
<div className="text-xs font-bold text-slate-800 uppercase">{safeRender(visaDoc.ocr_data.visa_type || 'ENTRY PERMIT')}</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>
|
||||
{sponsorName && (
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sponsor_name', 'Sponsor Name')}</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('nationality', 'Nationality')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.nationality || worker.nationality)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('country', 'Country')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.country || 'United Arab Emirates')}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('date_of_birth', 'Date of Birth')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.date_of_birth || visaDoc.ocr_data.dob)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('place_of_birth', 'Place of Birth')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.place_of_birth)}</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', 'Issue Date')}</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', 'Expiry Date')}</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('document_type', 'Document Type')}</div>
|
||||
<div className="text-xs font-bold text-slate-800">{safeRender(visaDoc.ocr_data.document_type || visaDoc.type || 'uae_visa')}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('ocr_accuracy', 'OCR Accuracy')}</div>
|
||||
<div className="text-xs font-bold text-emerald-600">{(visaDoc.ocr_accuracy || visaDoc.ocr_data?.ocr_accuracy || 99)}%</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>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import React, { useEffect } 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
|
||||
React.useEffect(() => {
|
||||
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
|
||||
React.useEffect(() => {
|
||||
const handler = (e) => { if (e.key === 'Escape' && typeof onClose === 'function') onClose(); };
|
||||
useEffect(() => {
|
||||
const handler = (e) => { if (e.key === 'Escape') onClose(); };
|
||||
window.addEventListener('keydown', handler);
|
||||
return () => window.removeEventListener('keydown', handler);
|
||||
}, [onClose]);
|
||||
@ -164,15 +164,14 @@ 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 }) {
|
||||
const opts = options || [];
|
||||
export function FilterChips({ options, selected, onChange }) {
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{opts.map(opt => (
|
||||
{options.map(opt => (
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
onClick={() => onChange && onChange(opt.value)}
|
||||
onClick={() => onChange(opt.value)}
|
||||
className={`px-3 py-1.5 rounded-full text-xs font-bold transition-all border ${
|
||||
selected === opt.value
|
||||
? 'bg-[#185FA5] text-white border-[#185FA5] shadow-sm'
|
||||
@ -186,19 +185,16 @@ export function FilterChips({ options = [], selected, onChange }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function FilterCheckboxList({ items = [], selected = [], onToggle, placeholder = 'Search...' }) {
|
||||
export function FilterCheckboxList({ items, selected, onToggle, placeholder = 'Search...' }) {
|
||||
const [search, setSearch] = React.useState('');
|
||||
|
||||
const itemsList = items || [];
|
||||
const selectedList = selected || [];
|
||||
|
||||
const filteredItems = itemsList.filter(item =>
|
||||
item && item.toLowerCase().includes((search || '').toLowerCase())
|
||||
const filteredItems = items.filter(item =>
|
||||
item.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{itemsList.length > 5 && (
|
||||
{items.length > 5 && (
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
@ -220,12 +216,12 @@ export function FilterCheckboxList({ items = [], selected = [], onToggle, placeh
|
||||
)}
|
||||
<div className="space-y-2 max-h-48 overflow-y-auto pr-1">
|
||||
{filteredItems.map(item => {
|
||||
const isChecked = selectedList.includes(item);
|
||||
const isChecked = selected.includes(item);
|
||||
return (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
onClick={() => onToggle && onToggle(item)}
|
||||
onClick={() => onToggle(item)}
|
||||
className="w-full flex items-center space-x-3 text-left focus:outline-none group cursor-pointer"
|
||||
>
|
||||
<div className={`w-4 h-4 rounded border-2 flex items-center justify-center flex-shrink-0 transition-all ${
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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/**'],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user