employer login issues fixed
This commit is contained in:
parent
56d8fc49aa
commit
0a497e56ed
@ -42,14 +42,7 @@ private function formatWorker(Worker $w)
|
|||||||
// Visa status
|
// Visa status
|
||||||
$visaStatus = $w->visa_status;
|
$visaStatus = $w->visa_status;
|
||||||
|
|
||||||
// Optional profile photos
|
$photo = null;
|
||||||
$photos = [
|
|
||||||
'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=200',
|
|
||||||
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&q=80&w=200',
|
|
||||||
'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&q=80&w=200',
|
|
||||||
null
|
|
||||||
];
|
|
||||||
$photo = $photos[$w->id % 4];
|
|
||||||
|
|
||||||
$dbReviews = \App\Models\Review::where('worker_id', $w->id)->get();
|
$dbReviews = \App\Models\Review::where('worker_id', $w->id)->get();
|
||||||
$reviewsCount = $dbReviews->count();
|
$reviewsCount = $dbReviews->count();
|
||||||
@ -887,14 +880,7 @@ public function getWorkerDetail(Request $request, $id)
|
|||||||
$visaStatusesList = ['Residence Visa', 'Tourist Visa', 'Employment Visa', 'Cancelled Visa', 'Own Visa'];
|
$visaStatusesList = ['Residence Visa', 'Tourist Visa', 'Employment Visa', 'Cancelled Visa', 'Own Visa'];
|
||||||
$visaStatus = $visaStatusesList[$w->id % 5];
|
$visaStatus = $visaStatusesList[$w->id % 5];
|
||||||
|
|
||||||
// Profile photo
|
$photo = null;
|
||||||
$photos = [
|
|
||||||
'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=200',
|
|
||||||
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&q=80&w=200',
|
|
||||||
'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&q=80&w=200',
|
|
||||||
null
|
|
||||||
];
|
|
||||||
$photo = $photos[$w->id % 4];
|
|
||||||
|
|
||||||
// Fetch dynamic database reviews (Requirement 1)
|
// Fetch dynamic database reviews (Requirement 1)
|
||||||
$dbReviews = Review::where('worker_id', $w->id)->with('employer')->latest()->get();
|
$dbReviews = Review::where('worker_id', $w->id)->with('employer')->latest()->get();
|
||||||
|
|||||||
@ -298,6 +298,21 @@ public function setupProfile(Request $request)
|
|||||||
*/
|
*/
|
||||||
public function register(Request $request)
|
public function register(Request $request)
|
||||||
{
|
{
|
||||||
|
$data = $request->all();
|
||||||
|
if (isset($data['passport']) && is_string($data['passport'])) {
|
||||||
|
$decoded = json_decode($data['passport'], true);
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
|
||||||
|
$data['passport'] = $decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($data['visa']) && is_string($data['visa'])) {
|
||||||
|
$decoded = json_decode($data['visa'], true);
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
|
||||||
|
$data['visa'] = $decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$request->merge($data);
|
||||||
|
|
||||||
$validator = Validator::make($request->all(), [
|
$validator = Validator::make($request->all(), [
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'phone' => 'required|string|max:50|unique:workers,phone',
|
'phone' => 'required|string|max:50|unique:workers,phone',
|
||||||
|
|||||||
@ -77,13 +77,7 @@ public function index(Request $request)
|
|||||||
$visaStatus = $w->visa_status;
|
$visaStatus = $w->visa_status;
|
||||||
|
|
||||||
// Optional profile photos
|
// Optional profile photos
|
||||||
$photos = [
|
$photo = null;
|
||||||
'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=200',
|
|
||||||
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&q=80&w=200',
|
|
||||||
'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&q=80&w=200',
|
|
||||||
null
|
|
||||||
];
|
|
||||||
$photo = $photos[$w->id % 4];
|
|
||||||
|
|
||||||
$dbReviews = \App\Models\Review::where('worker_id', $w->id)->get();
|
$dbReviews = \App\Models\Review::where('worker_id', $w->id)->get();
|
||||||
$reviewsCount = $dbReviews->count();
|
$reviewsCount = $dbReviews->count();
|
||||||
@ -109,6 +103,11 @@ public function index(Request $request)
|
|||||||
'bio' => $w->bio,
|
'bio' => $w->bio,
|
||||||
'rating' => $rating,
|
'rating' => $rating,
|
||||||
'reviews_count' => $reviewsCount,
|
'reviews_count' => $reviewsCount,
|
||||||
|
'document_expiry_status' => $w->document_expiry_status,
|
||||||
|
'document_expiry_days' => $w->document_expiry_days,
|
||||||
|
'visa_expiry_date' => $w->visa_expiry_date,
|
||||||
|
'in_country' => (bool) $w->in_country,
|
||||||
|
'preferred_location' => $w->preferred_location,
|
||||||
];
|
];
|
||||||
})->filter()->values()->toArray();
|
})->filter()->values()->toArray();
|
||||||
|
|
||||||
|
|||||||
@ -75,14 +75,7 @@ public function index(Request $request)
|
|||||||
// Visa status
|
// Visa status
|
||||||
$visaStatus = $w->visa_status;
|
$visaStatus = $w->visa_status;
|
||||||
|
|
||||||
// Optional profile photos
|
$photo = null;
|
||||||
$photos = [
|
|
||||||
'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=200',
|
|
||||||
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&q=80&w=200',
|
|
||||||
'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&q=80&w=200',
|
|
||||||
null // Test optional photo placeholder
|
|
||||||
];
|
|
||||||
$photo = $photos[$w->id % 4];
|
|
||||||
|
|
||||||
$dbReviews = \App\Models\Review::where('worker_id', $w->id)->get();
|
$dbReviews = \App\Models\Review::where('worker_id', $w->id)->get();
|
||||||
$reviewsCount = $dbReviews->count();
|
$reviewsCount = $dbReviews->count();
|
||||||
@ -113,6 +106,9 @@ public function index(Request $request)
|
|||||||
'reviews_count' => $reviewsCount,
|
'reviews_count' => $reviewsCount,
|
||||||
'preferred_location' => $w->preferred_location,
|
'preferred_location' => $w->preferred_location,
|
||||||
'in_country' => (bool) $w->in_country,
|
'in_country' => (bool) $w->in_country,
|
||||||
|
'document_expiry_status' => $w->document_expiry_status,
|
||||||
|
'document_expiry_days' => $w->document_expiry_days,
|
||||||
|
'visa_expiry_date' => $w->visa_expiry_date,
|
||||||
];
|
];
|
||||||
})->filter()->values()->toArray();
|
})->filter()->values()->toArray();
|
||||||
|
|
||||||
@ -319,13 +315,7 @@ public function show($id)
|
|||||||
$mappedSkills = ['cooking', 'cleaning'];
|
$mappedSkills = ['cooking', 'cleaning'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$photos = [
|
$photo = null;
|
||||||
'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=200',
|
|
||||||
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&q=80&w=200',
|
|
||||||
'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&q=80&w=200',
|
|
||||||
null
|
|
||||||
];
|
|
||||||
$photo = $photos[$w->id % 4];
|
|
||||||
|
|
||||||
// Fetch dynamic database reviews (Requirement 1)
|
// Fetch dynamic database reviews (Requirement 1)
|
||||||
$dbReviews = Review::where('worker_id', $w->id)->with('employer')->latest()->get();
|
$dbReviews = Review::where('worker_id', $w->id)->with('employer')->latest()->get();
|
||||||
@ -395,6 +385,23 @@ public function show($id)
|
|||||||
'similar_workers' => $similarWorkers,
|
'similar_workers' => $similarWorkers,
|
||||||
'status' => $w->status,
|
'status' => $w->status,
|
||||||
'availability_status' => $w->status,
|
'availability_status' => $w->status,
|
||||||
|
'document_expiry_status' => $w->document_expiry_status,
|
||||||
|
'document_expiry_days' => $w->document_expiry_days,
|
||||||
|
'visa_expiry_date' => $w->visa_expiry_date,
|
||||||
|
'in_country' => (bool) $w->in_country,
|
||||||
|
'preferred_location' => $w->preferred_location,
|
||||||
|
'documents' => $w->documents->map(function ($doc) {
|
||||||
|
return [
|
||||||
|
'id' => $doc->id,
|
||||||
|
'type' => $doc->type,
|
||||||
|
'number' => $doc->number,
|
||||||
|
'issue_date' => $doc->issue_date,
|
||||||
|
'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,
|
||||||
|
];
|
||||||
|
})->toArray(),
|
||||||
];
|
];
|
||||||
|
|
||||||
return Inertia::render('Employer/Workers/Show', [
|
return Inertia::render('Employer/Workers/Show', [
|
||||||
|
|||||||
@ -56,6 +56,7 @@ class Worker extends Model
|
|||||||
'emirates_id_status',
|
'emirates_id_status',
|
||||||
'document_expiry_days',
|
'document_expiry_days',
|
||||||
'document_expiry_status',
|
'document_expiry_status',
|
||||||
|
'visa_expiry_date',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function getPassportStatusAttribute()
|
public function getPassportStatusAttribute()
|
||||||
@ -87,6 +88,12 @@ public function getEmiratesIdStatusAttribute()
|
|||||||
return $this->passport_status;
|
return $this->passport_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getVisaExpiryDateAttribute()
|
||||||
|
{
|
||||||
|
$visa = $this->documents->where('type', 'visa')->first();
|
||||||
|
return $visa ? $visa->expiry_date : null;
|
||||||
|
}
|
||||||
|
|
||||||
public function getDocumentExpiryDaysAttribute()
|
public function getDocumentExpiryDaysAttribute()
|
||||||
{
|
{
|
||||||
$visa = $this->documents->where('type', 'visa')->first();
|
$visa = $this->documents->where('type', 'visa')->first();
|
||||||
|
|||||||
@ -29,71 +29,23 @@ public function run(): void
|
|||||||
// Conversation 1: with Worker 1 (John Doe)
|
// Conversation 1: with Worker 1 (John Doe)
|
||||||
$w1 = $workers->get(0);
|
$w1 = $workers->get(0);
|
||||||
if ($w1) {
|
if ($w1) {
|
||||||
$conv1 = Conversation::create([
|
Conversation::create([
|
||||||
'employer_id' => $employer->id,
|
'employer_id' => $employer->id,
|
||||||
'worker_id' => $w1->id,
|
'worker_id' => $w1->id,
|
||||||
'created_at' => now()->subHours(2),
|
'created_at' => now()->subHours(2),
|
||||||
'updated_at' => now()->subHours(2),
|
'updated_at' => now()->subHours(2),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Message::create([
|
|
||||||
'conversation_id' => $conv1->id,
|
|
||||||
'sender_type' => 'employer',
|
|
||||||
'sender_id' => $employer->id,
|
|
||||||
'text' => 'Hello John, I saw your profile and I am interested in hiring you as an electrician.',
|
|
||||||
'created_at' => now()->subMinutes(110),
|
|
||||||
]);
|
|
||||||
|
|
||||||
Message::create([
|
|
||||||
'conversation_id' => $conv1->id,
|
|
||||||
'sender_type' => 'worker',
|
|
||||||
'sender_id' => $w1->id,
|
|
||||||
'text' => 'Hello, thank you for reaching out! Yes, I am available.',
|
|
||||||
'created_at' => now()->subMinutes(100),
|
|
||||||
]);
|
|
||||||
|
|
||||||
Message::create([
|
|
||||||
'conversation_id' => $conv1->id,
|
|
||||||
'sender_type' => 'employer',
|
|
||||||
'sender_id' => $employer->id,
|
|
||||||
'text' => 'Great, what is your salary expectation?',
|
|
||||||
'created_at' => now()->subMinutes(90),
|
|
||||||
]);
|
|
||||||
|
|
||||||
Message::create([
|
|
||||||
'conversation_id' => $conv1->id,
|
|
||||||
'sender_type' => 'worker',
|
|
||||||
'sender_id' => $w1->id,
|
|
||||||
'text' => 'My expectation is around 2500 AED per month.',
|
|
||||||
'created_at' => now()->subMinutes(80),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversation 2: with Worker 2 (Ali Hassan)
|
// Conversation 2: with Worker 2 (Ali Hassan)
|
||||||
$w2 = $workers->get(1);
|
$w2 = $workers->get(1);
|
||||||
if ($w2) {
|
if ($w2) {
|
||||||
$conv2 = Conversation::create([
|
Conversation::create([
|
||||||
'employer_id' => $employer->id,
|
'employer_id' => $employer->id,
|
||||||
'worker_id' => $w2->id,
|
'worker_id' => $w2->id,
|
||||||
'created_at' => now()->subHour(),
|
'created_at' => now()->subHour(),
|
||||||
'updated_at' => now()->subHour(),
|
'updated_at' => now()->subHour(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Message::create([
|
|
||||||
'conversation_id' => $conv2->id,
|
|
||||||
'sender_type' => 'employer',
|
|
||||||
'sender_id' => $employer->id,
|
|
||||||
'text' => 'Hi Ali, are you looking for job?',
|
|
||||||
'created_at' => now()->subMinutes(50),
|
|
||||||
]);
|
|
||||||
|
|
||||||
Message::create([
|
|
||||||
'conversation_id' => $conv2->id,
|
|
||||||
'sender_type' => 'worker',
|
|
||||||
'sender_id' => $w2->id,
|
|
||||||
'text' => 'Yes, I am available.',
|
|
||||||
'created_at' => now()->subMinutes(45),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -111,7 +111,7 @@ export default function Announcements({ initialAnnouncements }) {
|
|||||||
setIsDialogOpen(false);
|
setIsDialogOpen(false);
|
||||||
showToast(
|
showToast(
|
||||||
t('community_charity_event_posted', 'COMMUNITY CHARITY EVENT POSTED!'),
|
t('community_charity_event_posted', 'COMMUNITY CHARITY EVENT POSTED!'),
|
||||||
t('community_charity_event_posted_desc', 'Instant Push Notification sent to all workers in Dubai! Morning-of reminder notification scheduled successfully.')
|
t('community_charity_event_posted_desc', 'Instant Push Notification sent to all workers! Morning-of reminder notification scheduled successfully.')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -128,8 +128,8 @@ export default function Announcements({ initialAnnouncements }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EmployerLayout title={t('charity_events_support_drives', 'Charity Events & Support Drives')}>
|
<EmployerLayout title="Charity Events">
|
||||||
<Head title={`${t('community_charity_events_sponsor_hub', 'Community Charity Events - Sponsor Hub')}`} />
|
<Head title={t('charity_events', 'Charity Events')} />
|
||||||
|
|
||||||
{toastMessage && (
|
{toastMessage && (
|
||||||
<div className="fixed bottom-8 right-8 bg-slate-900 text-white px-6 py-5 rounded-[24px] shadow-2xl border border-slate-800 flex items-start space-x-3.5 z-[100] max-w-md animate-in slide-in-from-bottom-5 duration-350">
|
<div className="fixed bottom-8 right-8 bg-slate-900 text-white px-6 py-5 rounded-[24px] shadow-2xl border border-slate-800 flex items-start space-x-3.5 z-[100] max-w-md animate-in slide-in-from-bottom-5 duration-350">
|
||||||
@ -150,7 +150,7 @@ export default function Announcements({ initialAnnouncements }) {
|
|||||||
<div className="space-y-2 max-w-2xl">
|
<div className="space-y-2 max-w-2xl">
|
||||||
<div className="inline-flex items-center space-x-2 px-3 py-1 bg-[#185FA5]/5 border border-[#185FA5]/10 rounded-full text-[10px] font-black uppercase tracking-wider text-[#185FA5]">
|
<div className="inline-flex items-center space-x-2 px-3 py-1 bg-[#185FA5]/5 border border-[#185FA5]/10 rounded-full text-[10px] font-black uppercase tracking-wider text-[#185FA5]">
|
||||||
<Heart className="w-3.5 h-3.5 fill-[#185FA5] text-[#185FA5] animate-pulse" />
|
<Heart className="w-3.5 h-3.5 fill-[#185FA5] text-[#185FA5] animate-pulse" />
|
||||||
<span>{t('dubai_community_support_drives', 'Dubai Community Support Drives')}</span>
|
<span>{t('dubai_community_support_drives', 'Community Support Drives')}</span>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-xl font-black text-slate-900 tracking-tight">{t('free_medical_checks_title', 'Free Medical Checks, Food Drives & Support Services')}</h2>
|
<h2 className="text-xl font-black text-slate-900 tracking-tight">{t('free_medical_checks_title', 'Free Medical Checks, Food Drives & Support Services')}</h2>
|
||||||
<p className="text-xs text-slate-500 font-bold leading-relaxed">
|
<p className="text-xs text-slate-500 font-bold leading-relaxed">
|
||||||
@ -177,10 +177,6 @@ export default function Announcements({ initialAnnouncements }) {
|
|||||||
<form onSubmit={handleCreate} className="p-8 space-y-5 bg-white max-h-[80vh] overflow-y-auto">
|
<form onSubmit={handleCreate} className="p-8 space-y-5 bg-white max-h-[80vh] overflow-y-auto">
|
||||||
|
|
||||||
<div className="space-y-4 p-4 bg-[#185FA5]/5 rounded-2xl border border-[#185FA5]/10 animate-in fade-in duration-200">
|
<div className="space-y-4 p-4 bg-[#185FA5]/5 rounded-2xl border border-[#185FA5]/10 animate-in fade-in duration-200">
|
||||||
<div className="text-[10px] font-black text-[#185FA5] uppercase tracking-widest flex items-center gap-1.5">
|
|
||||||
<Heart className="w-3.5 h-3.5 fill-[#185FA5] text-[#185FA5]" />
|
|
||||||
<span>{t('charity_drive_metadata', 'Charity Drive Metadata (Dubai Support)')}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@ -356,7 +352,7 @@ export default function Announcements({ initialAnnouncements }) {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
placeholder={t('location_details_placeholder', 'e.g. Al Quoz Community Center, Dubai')}
|
placeholder={t('location_details_placeholder', 'e.g. Al Quoz Community Center')}
|
||||||
value={newAnnouncement.location_details}
|
value={newAnnouncement.location_details}
|
||||||
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, location_details: e.target.value })}
|
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, location_details: e.target.value })}
|
||||||
className="w-full px-3.5 py-2.5 bg-white border border-slate-200 rounded-xl text-xs font-bold focus:ring-2 focus:ring-[#185FA5]/20 outline-none mb-2"
|
className="w-full px-3.5 py-2.5 bg-white border border-slate-200 rounded-xl text-xs font-bold focus:ring-2 focus:ring-[#185FA5]/20 outline-none mb-2"
|
||||||
@ -449,10 +445,10 @@ export default function Announcements({ initialAnnouncements }) {
|
|||||||
{filteredAnnouncements.length === 0 ? (
|
{filteredAnnouncements.length === 0 ? (
|
||||||
<div className="text-center py-24 bg-white rounded-[40px] border border-slate-200 shadow-sm text-slate-500 border-dashed md:col-span-2">
|
<div className="text-center py-24 bg-white rounded-[40px] border border-slate-200 shadow-sm text-slate-500 border-dashed md:col-span-2">
|
||||||
<div className="w-20 h-20 bg-slate-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
<div className="w-20 h-20 bg-slate-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||||
<Megaphone className="w-10 h-10 text-slate-200" />
|
<Heart className="w-10 h-10 text-slate-200 fill-slate-50" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-black text-slate-900 text-lg uppercase tracking-tight">{t('no_announcements_title', 'No Announcements')}</h3>
|
<h3 className="font-black text-slate-900 text-lg uppercase tracking-tight">{t('no_charity_events_title', 'No Charity Events')}</h3>
|
||||||
<p className="font-bold text-xs text-slate-400 mt-1 uppercase tracking-widest">{t('no_announcements_desc', "You haven't posted any announcements yet")}</p>
|
<p className="font-bold text-xs text-slate-400 mt-1 uppercase tracking-widest">{t('no_charity_events_desc', "You haven't posted any charity events yet")}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
filteredAnnouncements.map((ann) => {
|
filteredAnnouncements.map((ann) => {
|
||||||
|
|||||||
@ -339,7 +339,7 @@ export default function Dashboard({
|
|||||||
<div className="truncate">
|
<div className="truncate">
|
||||||
<div className="font-bold text-sm text-slate-800 truncate flex items-center space-x-1">
|
<div className="font-bold text-sm text-slate-800 truncate flex items-center space-x-1">
|
||||||
<span>{worker.name}</span>
|
<span>{worker.name}</span>
|
||||||
{worker.verified && <CheckCircle2 className="w-3.5 h-3.5 text-emerald-600 flex-shrink-0" />}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-slate-500 truncate font-semibold">{worker.nationality}</div>
|
<div className="text-[10px] text-slate-500 truncate font-semibold">{worker.nationality}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export default function Show({ conversation, initialMessages, conversations = []
|
|||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const [isTyping, setIsTyping] = useState(false);
|
const [isTyping, setIsTyping] = useState(false);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [showProfile, setShowProfile] = useState(true);
|
const [showHireConfirmModal, setShowHireConfirmModal] = useState(false);
|
||||||
const [showAttachmentModal, setShowAttachmentModal] = useState(false);
|
const [showAttachmentModal, setShowAttachmentModal] = useState(false);
|
||||||
const [uploadedFiles, setUploadedFiles] = useState([]);
|
const [uploadedFiles, setUploadedFiles] = useState([]);
|
||||||
const [isRecording, setIsRecording] = useState(false);
|
const [isRecording, setIsRecording] = useState(false);
|
||||||
@ -188,6 +188,16 @@ export default function Show({ conversation, initialMessages, conversations = []
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleMarkHired = () => {
|
||||||
|
router.post(`/employer/workers/${conversation.worker_id}/mark-hired`, {}, {
|
||||||
|
preserveScroll: true,
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t('marked_hired', 'Worker successfully marked as Hired!'));
|
||||||
|
router.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const stopRecording = () => {
|
const stopRecording = () => {
|
||||||
if (mediaRecorderRef.current && isRecording) {
|
if (mediaRecorderRef.current && isRecording) {
|
||||||
mediaRecorderRef.current.stop();
|
mediaRecorderRef.current.stop();
|
||||||
@ -196,8 +206,6 @@ export default function Show({ conversation, initialMessages, conversations = []
|
|||||||
};
|
};
|
||||||
|
|
||||||
const chips = [
|
const chips = [
|
||||||
t('chip_looking_job', "Are you looking for a job?"),
|
|
||||||
t('chip_interview', "Are you available for a video interview today?"),
|
|
||||||
t('chip_salary', "What is your final expected salary?"),
|
t('chip_salary', "What is your final expected salary?"),
|
||||||
t('chip_visa', "Can you transfer your visa immediately?")
|
t('chip_visa', "Can you transfer your visa immediately?")
|
||||||
];
|
];
|
||||||
@ -281,7 +289,7 @@ export default function Show({ conversation, initialMessages, conversations = []
|
|||||||
<ArrowLeft className="w-5 h-5" />
|
<ArrowLeft className="w-5 h-5" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="relative group cursor-pointer" onClick={() => setShowProfile(!showProfile)}>
|
<div className="relative group">
|
||||||
<div className="w-11 h-11 rounded-2xl bg-gradient-to-br from-[#185FA5] to-blue-600 text-white flex items-center justify-center font-black text-base shadow-md">
|
<div className="w-11 h-11 rounded-2xl bg-gradient-to-br from-[#185FA5] to-blue-600 text-white flex items-center justify-center font-black text-base shadow-md">
|
||||||
{conversation.worker_name.charAt(0)}
|
{conversation.worker_name.charAt(0)}
|
||||||
</div>
|
</div>
|
||||||
@ -303,29 +311,22 @@ export default function Show({ conversation, initialMessages, conversations = []
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* WhatsApp Bridge Widget & Dossier Button */}
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<a
|
{conversation.worker_status !== 'hired' && conversation.worker_status !== 'hidden' ? (
|
||||||
href={`https://wa.me/971501112222?text=Hi, I am reviewing candidate ${conversation.worker_name} (${conversation.nationality}) on the platform. Please assist with hiring workflow details.`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="px-3.5 py-2 bg-emerald-50 text-emerald-800 border border-emerald-200 hover:bg-emerald-100 rounded-xl transition-all shadow-xs flex items-center space-x-1.5 text-xs font-bold"
|
|
||||||
>
|
|
||||||
<MessageSquare className="w-4 h-4 text-emerald-600 fill-emerald-600" />
|
|
||||||
<span className="hidden sm:inline">{t('whatsapp_bridge', 'WhatsApp Bridge')}</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowProfile(!showProfile)}
|
type="button"
|
||||||
className={`p-2.5 rounded-xl transition-all shadow-xs border ${
|
onClick={() => setShowHireConfirmModal(true)}
|
||||||
showProfile
|
className="px-3.5 py-2 bg-emerald-600 text-white hover:bg-emerald-700 rounded-xl transition-all shadow-md flex items-center space-x-1.5 text-xs font-black uppercase tracking-wider border border-emerald-700 cursor-pointer"
|
||||||
? 'bg-blue-50 border-blue-100 text-[#185FA5]'
|
|
||||||
: 'bg-slate-50 border-slate-200 text-slate-400 hover:text-[#185FA5]'
|
|
||||||
}`}
|
|
||||||
title="Toggle Context Sidebar"
|
|
||||||
>
|
>
|
||||||
<Info className="w-4.5 h-4.5" />
|
<CheckCircle2 className="w-4 h-4 text-white" />
|
||||||
|
<span>{t('mark_hired_action', 'Mark Hired')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
) : conversation.worker_status === 'hired' ? (
|
||||||
|
<span className="px-3.5 py-2 bg-slate-100 text-slate-500 rounded-xl text-xs font-black uppercase tracking-wider border border-slate-200 flex items-center space-x-1.5">
|
||||||
|
<CheckCircle2 className="w-4 h-4 text-emerald-600" />
|
||||||
|
<span>{t('hired', 'Hired')}</span>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -485,85 +486,7 @@ export default function Show({ conversation, initialMessages, conversations = []
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Candidate Context Sidebar (Right) */}
|
|
||||||
{showProfile && (
|
|
||||||
<aside className="hidden lg:flex w-72 2xl:w-80 border-l border-slate-200 flex-col flex-shrink-0 bg-white z-20 animate-in slide-in-from-right-10 duration-350 overflow-y-auto">
|
|
||||||
<div className="p-6 space-y-6">
|
|
||||||
<div className="text-center space-y-3 pb-4 border-b border-slate-100">
|
|
||||||
<div className="w-20 h-20 rounded-[30px] bg-gradient-to-br from-[#185FA5] to-blue-600 text-white flex items-center justify-center font-black text-2xl mx-auto shadow-md relative">
|
|
||||||
{conversation.worker_name.charAt(0)}
|
|
||||||
<span className="absolute -bottom-1 -right-1 bg-emerald-500 p-1 rounded-lg border-2 border-white shadow">
|
|
||||||
<ShieldCheck className="w-4 h-4 text-white" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="font-extrabold text-base text-slate-900 tracking-tight">{conversation.worker_name}</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-2 text-center text-xs font-bold">
|
|
||||||
<div className="bg-slate-50/70 p-3 rounded-xl border border-slate-100">
|
|
||||||
<DollarSign className="w-3.5 h-3.5 text-emerald-600 mx-auto mb-1" />
|
|
||||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-widest">{t('expected', 'Expected')}</div>
|
|
||||||
<div className="text-slate-800 mt-0.5 truncate">{conversation.salary}</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-slate-50/70 p-3 rounded-xl border border-slate-100">
|
|
||||||
<MapPin className="w-3.5 h-3.5 text-blue-600 mx-auto mb-1" />
|
|
||||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-widest">{t('region', 'Region')}</div>
|
|
||||||
<div className="text-slate-800 mt-0.5 truncate">{conversation.nationality}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
|
||||||
<h5 className="text-[9px] font-black text-slate-400 uppercase tracking-widest pl-1">{t('compliance_profile', 'Compliance Profile')}</h5>
|
|
||||||
<div className="space-y-2">
|
|
||||||
{[
|
|
||||||
{ label: t('visa_transfer', 'Visa Transfer'), value: t('transferable', 'Transferable'), icon: Briefcase, color: 'text-blue-500' },
|
|
||||||
{ label: t('medical_status', 'Medical Status'), value: t('cleared', 'Cleared'), icon: ShieldCheck, color: 'text-emerald-500' },
|
|
||||||
{ label: t('passport_ocr', 'Passport OCR'), value: t('verified', 'Verified'), icon: FileText, color: 'text-slate-500' }
|
|
||||||
].map((item, i) => (
|
|
||||||
<div key={i} className="flex items-center justify-between p-3 bg-slate-50/40 border border-slate-100 rounded-xl text-[10px] font-bold text-slate-600">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<item.icon className={`w-3.5 h-3.5 ${item.color}`} />
|
|
||||||
<span className="text-slate-500">{item.label}</span>
|
|
||||||
</div>
|
|
||||||
<span className="text-slate-900 font-extrabold">{item.value}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="pt-2 space-y-2">
|
|
||||||
<Link
|
|
||||||
href={`/employer/workers/${conversation.worker_id || conversation.id}`}
|
|
||||||
className="w-full bg-[#185FA5] hover:bg-[#144f8a] text-white py-3 rounded-xl text-xs font-black uppercase tracking-widest flex items-center justify-center space-x-1.5 shadow-xs"
|
|
||||||
>
|
|
||||||
<Eye className="w-4 h-4" />
|
|
||||||
<span>{t('open_worker_profile', 'Open Worker Profile')}</span>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{conversation.worker_status !== 'hired' && conversation.worker_status !== 'hidden' && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
router.post(`/employer/workers/${conversation.worker_id}/mark-hired`, {}, {
|
|
||||||
preserveScroll: true,
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success(t('marked_hired', 'Worker successfully marked as Hired!'));
|
|
||||||
router.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="w-full bg-emerald-650 bg-emerald-600 hover:bg-emerald-700 text-white py-3 rounded-xl text-xs font-black uppercase tracking-widest flex items-center justify-center space-x-1.5 shadow-md border border-emerald-700"
|
|
||||||
>
|
|
||||||
<CheckCircle2 className="w-4.5 h-4.5 text-white" />
|
|
||||||
<span>{t('mark_hired_action', 'Mark Hired')}</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Interactive File Attachment Modal Placeholder */}
|
{/* Interactive File Attachment Modal Placeholder */}
|
||||||
@ -606,6 +529,44 @@ export default function Show({ conversation, initialMessages, conversations = []
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Hired Confirmation Modal */}
|
||||||
|
{showHireConfirmModal && (
|
||||||
|
<div className="fixed inset-0 bg-slate-900/60 backdrop-blur-xs flex items-center justify-center p-4 z-50 animate-fade-in">
|
||||||
|
<div className="bg-white rounded-3xl w-full max-w-md p-6 relative animate-zoom-in text-center space-y-4 border border-slate-200 shadow-2xl">
|
||||||
|
<div className="mx-auto w-16 h-16 rounded-full bg-blue-50 border border-blue-100 flex items-center justify-center text-blue-600">
|
||||||
|
<CheckCircle2 className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="font-extrabold text-lg text-slate-900">{t('confirm_hire_title', 'Confirm Hiring')}</h4>
|
||||||
|
<p className="text-xs text-slate-600 leading-normal px-2">
|
||||||
|
{t('confirm_hire_desc', 'Verify this worker with your needs and make sure to hire this worker. If hired, you can view this worker in the Hired Workers page.')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex space-x-3 pt-2 text-xs font-bold">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowHireConfirmModal(false)}
|
||||||
|
className="flex-1 py-3 border border-slate-200 hover:bg-slate-50 rounded-xl transition-colors"
|
||||||
|
>
|
||||||
|
{t('cancel', 'Cancel')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setShowHireConfirmModal(false);
|
||||||
|
handleMarkHired();
|
||||||
|
}}
|
||||||
|
className="flex-1 bg-emerald-600 hover:bg-emerald-700 text-white py-3 rounded-xl shadow-sm transition-colors"
|
||||||
|
>
|
||||||
|
{t('confirm_hire_action', 'Confirm Hire')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</EmployerLayout>
|
</EmployerLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,8 @@ import {
|
|||||||
X,
|
X,
|
||||||
HeartHandshake,
|
HeartHandshake,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
MapPin
|
MapPin,
|
||||||
|
Calendar
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const getLanguageFlag = (lang) => {
|
const getLanguageFlag = (lang) => {
|
||||||
@ -99,7 +100,7 @@ export default function Shortlist({ shortlistedWorkers }) {
|
|||||||
|
|
||||||
{/* Card Header with Photo Option and Availability indicator */}
|
{/* Card Header with Photo Option and Availability indicator */}
|
||||||
<div className="bg-slate-50/50 p-6 border-b border-slate-100 flex items-start justify-between relative">
|
<div className="bg-slate-50/50 p-6 border-b border-slate-100 flex items-start justify-between relative">
|
||||||
<div className="flex items-center space-x-3.5">
|
<div className="flex items-center space-x-3.5 min-w-0 flex-1">
|
||||||
{/* Photo (optional) container */}
|
{/* Photo (optional) container */}
|
||||||
<div className="w-14 h-14 rounded-2xl bg-blue-100 text-[#185FA5] flex items-center justify-center font-bold text-xl border border-blue-200 shadow-inner flex-shrink-0 overflow-hidden relative">
|
<div className="w-14 h-14 rounded-2xl bg-blue-100 text-[#185FA5] flex items-center justify-center font-bold text-xl border border-blue-200 shadow-inner flex-shrink-0 overflow-hidden relative">
|
||||||
{worker.photo ? (
|
{worker.photo ? (
|
||||||
@ -109,10 +110,10 @@ export default function Shortlist({ shortlistedWorkers }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="truncate">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="font-extrabold text-base text-slate-900 group-hover:text-[#185FA5] transition-colors truncate flex items-center space-x-1.5">
|
<div className="font-extrabold text-base text-slate-900 group-hover:text-[#185FA5] transition-colors truncate flex items-center space-x-1.5">
|
||||||
<span>{worker.name}</span>
|
<span>{worker.name}</span>
|
||||||
{worker.verified && (
|
{false && (
|
||||||
<span className="inline-flex items-center px-1.5 py-0.5 rounded-full text-[8px] font-black uppercase tracking-wider bg-emerald-500 text-white shadow-xs gap-0.5" title="OCR Verified">
|
<span className="inline-flex items-center px-1.5 py-0.5 rounded-full text-[8px] font-black uppercase tracking-wider bg-emerald-500 text-white shadow-xs gap-0.5" title="OCR Verified">
|
||||||
<CheckCircle2 className="w-3 h-3" />
|
<CheckCircle2 className="w-3 h-3" />
|
||||||
<span>{t('verified', 'Verified')}</span>
|
<span>{t('verified', 'Verified')}</span>
|
||||||
@ -122,18 +123,23 @@ export default function Shortlist({ shortlistedWorkers }) {
|
|||||||
|
|
||||||
{/* Passport Status Verification Badge & Visa Status */}
|
{/* Passport Status Verification Badge & Visa Status */}
|
||||||
<div className="flex flex-wrap gap-1 mt-0.5">
|
<div className="flex flex-wrap gap-1 mt-0.5">
|
||||||
|
{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 ${
|
<div className={`flex items-center space-x-1 text-[9px] font-black uppercase px-1.5 py-0.5 rounded border ${
|
||||||
worker.passport_status.toLowerCase().includes('pending')
|
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-amber-700 bg-amber-50 border-amber-200'
|
||||||
: 'text-emerald-700 bg-emerald-50 border-emerald-100'
|
: 'text-[#185FA5] bg-blue-50 border-blue-100'
|
||||||
}`}>
|
}`}>
|
||||||
<ShieldCheck className={`w-3 h-3 flex-shrink-0 ${
|
<Calendar className="w-3 h-3 flex-shrink-0" />
|
||||||
worker.passport_status.toLowerCase().includes('pending')
|
<span>Visa Exp: {worker.visa_expiry_date}</span>
|
||||||
? 'text-amber-600'
|
|
||||||
: 'text-emerald-600'
|
|
||||||
}`} />
|
|
||||||
<span>{worker.passport_status}</span>
|
|
||||||
</div>
|
</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')) ? (
|
{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">
|
<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" />
|
<AlertTriangle className="w-3 h-3 text-amber-600 flex-shrink-0 animate-bounce" />
|
||||||
@ -382,21 +388,26 @@ export default function Shortlist({ shortlistedWorkers }) {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<h4 className="font-extrabold text-lg text-slate-900">{previewWorker.name}</h4>
|
<h4 className="font-extrabold text-lg text-slate-900">{previewWorker.name}</h4>
|
||||||
{previewWorker.verified && <CheckCircle2 className="w-4 h-4 text-emerald-600" />}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`flex items-center space-x-1 font-black text-[9px] uppercase tracking-wider ${
|
{previewWorker.visa_expiry_date ? (
|
||||||
previewWorker.passport_status?.toLowerCase().includes('pending')
|
<div className={`flex items-center space-x-1 font-black text-[9px] uppercase tracking-wider border px-1.5 py-0.5 rounded w-fit ${
|
||||||
? 'text-amber-700 bg-amber-50 border border-amber-200 px-1.5 py-0.5 rounded'
|
previewWorker.document_expiry_days !== null && previewWorker.document_expiry_days <= 30
|
||||||
: 'text-emerald-700 bg-emerald-50 border border-emerald-100 px-1.5 py-0.5 rounded'
|
? 'text-rose-700 bg-rose-50 border-rose-200 animate-pulse'
|
||||||
|
: previewWorker.document_expiry_days !== null && previewWorker.document_expiry_days <= 90
|
||||||
|
? 'text-amber-700 bg-amber-50 border border-amber-200'
|
||||||
|
: 'text-[#185FA5] bg-blue-50 border-blue-100'
|
||||||
}`}>
|
}`}>
|
||||||
<ShieldCheck className={`w-3.5 h-3.5 flex-shrink-0 ${
|
<Calendar className="w-3.5 h-3.5 flex-shrink-0" />
|
||||||
previewWorker.passport_status?.toLowerCase().includes('pending')
|
<span>Visa Exp: {previewWorker.visa_expiry_date}</span>
|
||||||
? 'text-amber-600'
|
|
||||||
: 'text-emerald-600'
|
|
||||||
}`} />
|
|
||||||
<span>{previewWorker.passport_status}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center space-x-1 font-black text-[9px] uppercase tracking-wider text-amber-700 bg-amber-50 border border-amber-200 px-1.5 py-0.5 rounded w-fit">
|
||||||
|
<Calendar className="w-3.5 h-3.5 flex-shrink-0 text-amber-600" />
|
||||||
|
<span>Visa Expiry Pending</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="text-xs text-slate-500 font-bold">{previewWorker.nationality}</div>
|
<div className="text-xs text-slate-500 font-bold">{previewWorker.nationality}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -649,7 +649,7 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [],
|
|||||||
|
|
||||||
{/* Card Header with Photo Option and Availability indicator */}
|
{/* Card Header with Photo Option and Availability indicator */}
|
||||||
<div className="bg-slate-50/50 p-6 border-b border-slate-100 flex items-start justify-between relative">
|
<div className="bg-slate-50/50 p-6 border-b border-slate-100 flex items-start justify-between relative">
|
||||||
<div className="flex items-center space-x-3.5">
|
<div className="flex items-center space-x-3.5 min-w-0 flex-1">
|
||||||
{/* Photo (optional) container */}
|
{/* Photo (optional) container */}
|
||||||
<div className="w-14 h-14 rounded-2xl bg-blue-100 text-[#185FA5] flex items-center justify-center font-bold text-xl border border-blue-200 shadow-inner flex-shrink-0 overflow-hidden relative">
|
<div className="w-14 h-14 rounded-2xl bg-blue-100 text-[#185FA5] flex items-center justify-center font-bold text-xl border border-blue-200 shadow-inner flex-shrink-0 overflow-hidden relative">
|
||||||
{worker.photo ? (
|
{worker.photo ? (
|
||||||
@ -659,10 +659,10 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [],
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="truncate">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="font-extrabold text-base text-slate-900 group-hover:text-[#185FA5] transition-colors truncate flex items-center space-x-1.5">
|
<div className="font-extrabold text-base text-slate-900 group-hover:text-[#185FA5] transition-colors truncate flex items-center space-x-1.5">
|
||||||
<span>{worker.name}</span>
|
<span>{worker.name}</span>
|
||||||
{worker.verified && (
|
{false && (
|
||||||
<span className="inline-flex items-center px-1.5 py-0.5 rounded-full text-[8px] font-black uppercase tracking-wider bg-emerald-500 text-white shadow-xs gap-0.5 animate-pulse" title="OCR Verified">
|
<span className="inline-flex items-center px-1.5 py-0.5 rounded-full text-[8px] font-black uppercase tracking-wider bg-emerald-500 text-white shadow-xs gap-0.5 animate-pulse" title="OCR Verified">
|
||||||
<CheckCircle2 className="w-3 h-3" />
|
<CheckCircle2 className="w-3 h-3" />
|
||||||
<span>{t('verified')}</span>
|
<span>{t('verified')}</span>
|
||||||
@ -672,18 +672,23 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [],
|
|||||||
|
|
||||||
{/* Passport Status Verification Badge & Visa Status */}
|
{/* Passport Status Verification Badge & Visa Status */}
|
||||||
<div className="flex flex-wrap gap-1 mt-0.5">
|
<div className="flex flex-wrap gap-1 mt-0.5">
|
||||||
|
{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 ${
|
<div className={`flex items-center space-x-1 text-[9px] font-black uppercase px-1.5 py-0.5 rounded border ${
|
||||||
worker.passport_status.toLowerCase().includes('pending')
|
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-amber-700 bg-amber-50 border-amber-200'
|
||||||
: 'text-emerald-700 bg-emerald-50 border-emerald-100'
|
: 'text-[#185FA5] bg-blue-50 border-blue-100'
|
||||||
}`}>
|
}`}>
|
||||||
<ShieldCheck className={`w-3 h-3 flex-shrink-0 ${
|
<Calendar className="w-3 h-3 flex-shrink-0" />
|
||||||
worker.passport_status.toLowerCase().includes('pending')
|
<span>Visa Exp: {worker.visa_expiry_date}</span>
|
||||||
? 'text-amber-600'
|
|
||||||
: 'text-emerald-600'
|
|
||||||
}`} />
|
|
||||||
<span>{worker.passport_status}</span>
|
|
||||||
</div>
|
</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')) ? (
|
{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">
|
<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" />
|
<AlertTriangle className="w-3 h-3 text-amber-600 flex-shrink-0 animate-bounce" />
|
||||||
@ -722,7 +727,7 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [],
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Top Right Availability status and bookmark */}
|
{/* Top Right Availability status and bookmark */}
|
||||||
<div className="flex flex-col items-end space-y-2">
|
<div className="flex flex-col items-end space-y-2 flex-shrink-0 ml-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => toggleShortlist(worker.id)}
|
onClick={() => toggleShortlist(worker.id)}
|
||||||
@ -952,21 +957,26 @@ export default function Index({ initialWorkers = [], initialShortlistedIds = [],
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<h4 className="font-extrabold text-lg text-slate-900">{previewWorker.name}</h4>
|
<h4 className="font-extrabold text-lg text-slate-900">{previewWorker.name}</h4>
|
||||||
{previewWorker.verified && <CheckCircle2 className="w-4 h-4 text-emerald-600" />}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`flex items-center space-x-1 font-black text-[9px] uppercase tracking-wider ${
|
{previewWorker.visa_expiry_date ? (
|
||||||
previewWorker.passport_status?.toLowerCase().includes('pending')
|
<div className={`flex items-center space-x-1 font-black text-[9px] uppercase tracking-wider border px-1.5 py-0.5 rounded w-fit ${
|
||||||
? 'text-amber-700 bg-amber-50 border border-amber-200 px-1.5 py-0.5 rounded'
|
previewWorker.document_expiry_days !== null && previewWorker.document_expiry_days <= 30
|
||||||
: 'text-emerald-700 bg-emerald-50 border border-emerald-100 px-1.5 py-0.5 rounded'
|
? 'text-rose-700 bg-rose-50 border-rose-200 animate-pulse'
|
||||||
|
: previewWorker.document_expiry_days !== null && previewWorker.document_expiry_days <= 90
|
||||||
|
? 'text-amber-700 bg-amber-50 border border-amber-200'
|
||||||
|
: 'text-[#185FA5] bg-blue-50 border-blue-100'
|
||||||
}`}>
|
}`}>
|
||||||
<ShieldCheck className={`w-3.5 h-3.5 flex-shrink-0 ${
|
<Calendar className="w-3.5 h-3.5 flex-shrink-0" />
|
||||||
previewWorker.passport_status?.toLowerCase().includes('pending')
|
<span>Visa Exp: {previewWorker.visa_expiry_date}</span>
|
||||||
? 'text-amber-600'
|
|
||||||
: 'text-emerald-600'
|
|
||||||
}`} />
|
|
||||||
<span>{previewWorker.passport_status}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center space-x-1 font-black text-[9px] uppercase tracking-wider text-amber-700 bg-amber-50 border border-amber-200 px-1.5 py-0.5 rounded w-fit">
|
||||||
|
<Calendar className="w-3.5 h-3.5 flex-shrink-0 text-amber-600" />
|
||||||
|
<span>Visa Expiry Pending</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="text-xs text-slate-500 font-bold">{previewWorker.nationality}</div>
|
<div className="text-xs text-slate-500 font-bold">{previewWorker.nationality}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -24,7 +24,8 @@ import {
|
|||||||
UserCheck,
|
UserCheck,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
User,
|
User,
|
||||||
Phone
|
Phone,
|
||||||
|
MapPin
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
@ -41,6 +42,34 @@ const getLanguageFlag = (lang) => {
|
|||||||
return flags[lang] || '🌐';
|
return flags[lang] || '🌐';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getExpiryStatus = (expiryDate) => {
|
||||||
|
if (!expiryDate) return { text: 'Expiry Pending', color: 'text-slate-500 bg-slate-50 border-slate-200' };
|
||||||
|
const expiry = new Date(expiryDate);
|
||||||
|
const today = new Date();
|
||||||
|
expiry.setHours(0,0,0,0);
|
||||||
|
today.setHours(0,0,0,0);
|
||||||
|
|
||||||
|
const diffTime = expiry.getTime() - today.getTime();
|
||||||
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (diffDays < 0) {
|
||||||
|
return {
|
||||||
|
text: `Expired \${Math.abs(diffDays)} days ago`,
|
||||||
|
color: 'text-rose-700 bg-rose-50 border-rose-200 font-black'
|
||||||
|
};
|
||||||
|
} else if (diffDays === 0) {
|
||||||
|
return {
|
||||||
|
text: 'Expires today',
|
||||||
|
color: 'text-amber-700 bg-amber-50 border-amber-200 font-black'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
text: `Valid until \${expiryDate} (Expires in \${diffDays} days)`,
|
||||||
|
color: 'text-emerald-700 bg-emerald-50 border-emerald-150 font-black'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default function Show({ worker }) {
|
export default function Show({ worker }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const passportDoc = worker.documents?.find(d => d.type === 'passport');
|
const passportDoc = worker.documents?.find(d => d.type === 'passport');
|
||||||
@ -54,6 +83,7 @@ export default function Show({ worker }) {
|
|||||||
// Hiring flow states
|
// Hiring flow states
|
||||||
const [availabilityStatus, setAvailabilityStatus] = useState(worker.availability_status);
|
const [availabilityStatus, setAvailabilityStatus] = useState(worker.availability_status);
|
||||||
const [showHiredModal, setShowHiredModal] = useState(false);
|
const [showHiredModal, setShowHiredModal] = useState(false);
|
||||||
|
const [showHireConfirmModal, setShowHireConfirmModal] = useState(false);
|
||||||
|
|
||||||
// Reviews state
|
// Reviews state
|
||||||
const [showReviewModal, setShowReviewModal] = useState(false);
|
const [showReviewModal, setShowReviewModal] = useState(false);
|
||||||
@ -145,26 +175,7 @@ export default function Show({ worker }) {
|
|||||||
<span>{t('back_to_find_workers', 'BACK TO FIND WORKERS')}</span>
|
<span>{t('back_to_find_workers', 'BACK TO FIND WORKERS')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Quick Profile Actions */}
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<button
|
|
||||||
onClick={handleShare}
|
|
||||||
className="p-2.5 bg-white border border-slate-200 hover:bg-slate-50 rounded-xl text-slate-500 hover:text-[#185FA5] transition-colors flex items-center space-x-1 text-xs font-bold shadow-xs"
|
|
||||||
title="Share Profile link"
|
|
||||||
>
|
|
||||||
<Share2 className="w-4 h-4" />
|
|
||||||
<span className="hidden sm:inline">{t('share_profile', 'Share Profile')}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => setShowReportModal(true)}
|
|
||||||
className="p-2.5 bg-white border border-slate-200 hover:bg-rose-50 rounded-xl text-slate-400 hover:text-rose-600 transition-colors flex items-center space-x-1 text-xs font-bold shadow-xs"
|
|
||||||
title="Report Abuse / Terms violations"
|
|
||||||
>
|
|
||||||
<Flag className="w-4 h-4" />
|
|
||||||
<span className="hidden sm:inline">{t('report_worker', 'Report Worker')}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white rounded-3xl border border-slate-200 shadow-sm overflow-hidden">
|
<div className="bg-white rounded-3xl border border-slate-200 shadow-sm overflow-hidden">
|
||||||
@ -183,28 +194,28 @@ export default function Show({ worker }) {
|
|||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<h1 className="text-2xl sm:text-3xl font-extrabold tracking-tight text-slate-900">{worker.name}</h1>
|
<h1 className="text-2xl sm:text-3xl font-extrabold tracking-tight text-slate-900">{worker.name}</h1>
|
||||||
{worker.verified && (
|
|
||||||
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-[9px] font-black uppercase tracking-wider bg-emerald-500 text-white shadow-xs gap-1 animate-pulse" title="OCR Verified via UAE PDPL Compliant Pipeline">
|
|
||||||
<CheckCircle2 className="w-3.5 h-3.5" />
|
|
||||||
<span>{t('verified')}</span>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Passport verification status bar & Visa Status */}
|
{/* Passport verification status bar & Visa Status */}
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{worker.visa_expiry_date ? (
|
||||||
<div className={`flex items-center space-x-1.5 px-2.5 py-1 rounded-lg border text-[9px] font-black uppercase tracking-wider w-fit ${
|
<div className={`flex items-center space-x-1.5 px-2.5 py-1 rounded-lg border text-[9px] font-black uppercase tracking-wider w-fit ${
|
||||||
worker.passport_status.toLowerCase().includes('pending')
|
worker.document_expiry_days !== null && worker.document_expiry_days <= 30
|
||||||
|
? 'bg-rose-50 text-rose-700 border-rose-200 animate-pulse'
|
||||||
|
: worker.document_expiry_days !== null && worker.document_expiry_days <= 90
|
||||||
? 'bg-amber-50 text-amber-700 border-amber-200'
|
? 'bg-amber-50 text-amber-700 border-amber-200'
|
||||||
: 'bg-emerald-50 text-emerald-700 border-emerald-100'
|
: 'bg-blue-50 text-[#185FA5] border-blue-100'
|
||||||
}`}>
|
}`}>
|
||||||
<ShieldCheck className={`w-3.5 h-3.5 ${
|
<Calendar className="w-3.5 h-3.5" />
|
||||||
worker.passport_status.toLowerCase().includes('pending')
|
<span>Visa Exp: {worker.visa_expiry_date}</span>
|
||||||
? 'text-amber-600'
|
|
||||||
: 'text-emerald-600'
|
|
||||||
}`} />
|
|
||||||
<span>{worker.passport_status}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center space-x-1.5 px-2.5 py-1 rounded-lg border text-[9px] font-black uppercase tracking-wider w-fit bg-amber-50 text-amber-700 border-amber-200">
|
||||||
|
<Calendar className="w-3.5 h-3.5 text-amber-600" />
|
||||||
|
<span>Visa Expiry Pending</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{worker.visa_status && (worker.visa_status.toLowerCase().includes('tourist') || worker.visa_status.toLowerCase().includes('visit')) ? (
|
{worker.visa_status && (worker.visa_status.toLowerCase().includes('tourist') || worker.visa_status.toLowerCase().includes('visit')) ? (
|
||||||
<div className="flex items-center space-x-1.5 bg-amber-50 px-2.5 py-1 rounded-lg text-amber-800 border border-amber-200 text-[9px] font-black uppercase tracking-wider w-fit shadow-xs">
|
<div className="flex items-center space-x-1.5 bg-amber-50 px-2.5 py-1 rounded-lg text-amber-800 border border-amber-200 text-[9px] font-black uppercase tracking-wider w-fit shadow-xs">
|
||||||
<AlertTriangle className="w-3.5 h-3.5 text-amber-600 animate-bounce" />
|
<AlertTriangle className="w-3.5 h-3.5 text-amber-600 animate-bounce" />
|
||||||
@ -267,7 +278,7 @@ export default function Show({ worker }) {
|
|||||||
{availabilityStatus !== 'Hired' && (
|
{availabilityStatus !== 'Hired' && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleMarkHired}
|
onClick={() => setShowHireConfirmModal(true)}
|
||||||
className="bg-emerald-600 hover:bg-emerald-700 text-white border border-emerald-700 px-8 py-3 rounded-xl font-black text-sm shadow-md transition-all flex items-center justify-center space-x-2 flex-1 sm:flex-initial scale-105 active:scale-100"
|
className="bg-emerald-600 hover:bg-emerald-700 text-white border border-emerald-700 px-8 py-3 rounded-xl font-black text-sm shadow-md transition-all flex items-center justify-center space-x-2 flex-1 sm:flex-initial scale-105 active:scale-100"
|
||||||
>
|
>
|
||||||
<CheckCircle2 className="w-4 h-4 text-white" />
|
<CheckCircle2 className="w-4 h-4 text-white" />
|
||||||
@ -310,7 +321,7 @@ export default function Show({ worker }) {
|
|||||||
|
|
||||||
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
|
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
|
||||||
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
|
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
|
||||||
<DollarSign className="w-4 h-4 text-emerald-600" />
|
<DollarSign className="w-4 h-4 text-slate-500" />
|
||||||
<span>{t('salary_expectations', 'Salary Expectations')}</span>
|
<span>{t('salary_expectations', 'Salary Expectations')}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-extrabold text-slate-900 text-sm">{worker.salary} {t('aed', 'AED')} / {t('mo', 'mo')}</span>
|
<span className="font-extrabold text-slate-900 text-sm">{worker.salary} {t('aed', 'AED')} / {t('mo', 'mo')}</span>
|
||||||
@ -318,7 +329,7 @@ export default function Show({ worker }) {
|
|||||||
|
|
||||||
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
|
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
|
||||||
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
|
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
|
||||||
<Briefcase className="w-4 h-4 text-amber-600" />
|
<Briefcase className="w-4 h-4 text-slate-500" />
|
||||||
<span>{t('work_experience', 'Work Experience')}</span>
|
<span>{t('work_experience', 'Work Experience')}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-extrabold text-slate-900 text-xs">{worker.experience}</span>
|
<span className="font-extrabold text-slate-900 text-xs">{worker.experience}</span>
|
||||||
@ -326,7 +337,7 @@ export default function Show({ worker }) {
|
|||||||
|
|
||||||
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
|
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
|
||||||
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
|
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
|
||||||
<Sparkles className="w-4 h-4 text-purple-600" />
|
<Sparkles className="w-4 h-4 text-slate-500" />
|
||||||
<span>{t('job_type', 'Job Type')}</span>
|
<span>{t('job_type', 'Job Type')}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-black text-slate-900 text-xs uppercase">{worker.preferred_job_type}</span>
|
<span className="font-black text-slate-900 text-xs uppercase">{worker.preferred_job_type}</span>
|
||||||
@ -334,7 +345,7 @@ export default function Show({ worker }) {
|
|||||||
|
|
||||||
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
|
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
|
||||||
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
|
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
|
||||||
<HeartHandshake className="w-4 h-4 text-rose-500" />
|
<HeartHandshake className="w-4 h-4 text-slate-500" />
|
||||||
<span>{t('accommodation_preference', 'Accommodation Preference')}</span>
|
<span>{t('accommodation_preference', 'Accommodation Preference')}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-black text-slate-900 text-xs uppercase">{worker.live_in_out}</span>
|
<span className="font-black text-slate-900 text-xs uppercase">{worker.live_in_out}</span>
|
||||||
@ -342,19 +353,31 @@ export default function Show({ worker }) {
|
|||||||
|
|
||||||
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
|
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
|
||||||
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
|
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
|
||||||
<ShieldCheck className={`w-4 h-4 ${worker.passport_status.toLowerCase().includes('pending') ? 'text-amber-600' : 'text-emerald-600'}`} />
|
<MapPin className="w-4 h-4 text-slate-500" />
|
||||||
<span>{t('passport_verification', 'Passport Verification')}</span>
|
<span>{t('preferred_location', 'Preferred Location')}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className={`font-bold text-xs uppercase ${worker.passport_status.toLowerCase().includes('pending') ? 'text-amber-700' : 'text-emerald-700'}`}>{worker.passport_status}</span>
|
<span className="font-extrabold text-slate-900 text-xs capitalize">{worker.preferred_location || t('any', 'Any')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between pb-3 border-b border-slate-200">
|
||||||
|
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
|
||||||
|
<Globe2 className="w-4 h-4 text-slate-500" />
|
||||||
|
<span>{t('current_location_status', 'Current Location')}</span>
|
||||||
|
</div>
|
||||||
|
<span className="font-extrabold text-slate-900 text-xs capitalize">
|
||||||
|
{worker.in_country ? t('in_country', 'In-Country') : t('out_country', 'Out-of-Country')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{worker.in_country && (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
|
<div className="flex items-center space-x-2 text-xs text-slate-600 font-bold">
|
||||||
<FileText className="w-4 h-4 text-blue-600" />
|
<FileText className="w-4 h-4 text-slate-500" />
|
||||||
<span>{t('visa_status', 'Visa Status')}</span>
|
<span>{t('visa_type', 'Visa Type')}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-bold text-blue-700 text-xs uppercase">{worker.visa_status}</span>
|
<span className="font-extrabold text-slate-900 text-xs capitalize">{worker.visa_status || t('pending', 'Pending')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Languages Spoken with visual flags */}
|
{/* Languages Spoken with visual flags */}
|
||||||
@ -400,108 +423,232 @@ export default function Show({ worker }) {
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
{/* Document Card: Passport */}
|
{/* Document Card: Passport */}
|
||||||
<div className="bg-white border border-slate-200 rounded-2xl overflow-hidden shadow-xs hover:shadow-sm">
|
{passportDoc ? (
|
||||||
<div className="bg-slate-50 px-4 py-3 border-b border-slate-100 flex items-center justify-between">
|
<div className="bg-white border border-slate-200 rounded-2xl overflow-hidden shadow-xs hover:shadow-sm lg:col-span-2">
|
||||||
<span className="text-[10px] font-black text-slate-500 uppercase tracking-widest">{t('passport_verified', 'Passport (PDPL Compliant)')}</span>
|
<div className="bg-slate-50 px-5 py-3.5 border-b border-slate-100 flex items-center justify-between">
|
||||||
<ShieldCheck className="w-4 h-4 text-emerald-500" />
|
<div className="flex items-center space-x-2">
|
||||||
|
<span className="text-[11px] font-black text-slate-800 uppercase tracking-wider">{t('passport_details', 'Passport Details')}</span>
|
||||||
|
{(() => {
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 flex space-x-4">
|
{passportDoc.ocr_accuracy && (
|
||||||
<div className="w-20 h-28 bg-slate-950 rounded-lg flex-shrink-0 border border-slate-800 flex flex-col items-center justify-center p-1.5 text-center relative shadow-inner">
|
<span className="text-[9px] font-bold text-emerald-600 bg-emerald-50/50 border border-emerald-100 px-2 py-0.5 rounded-full">
|
||||||
<ShieldCheck className="w-6 h-6 text-emerald-500 mb-1 animate-pulse" />
|
{passportDoc.ocr_accuracy}% Match
|
||||||
<span className="text-[6px] font-black text-emerald-500 uppercase tracking-tighter block">{t('scan_purged', 'Scan Purged')}</span>
|
</span>
|
||||||
<span className="text-[5px] font-medium text-slate-400 mt-1 leading-tight block">{t('uae_pdpl_compliant', 'UAE PDPL compliant')}</span>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 space-y-2">
|
<div className="p-6">
|
||||||
|
{passportDoc.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('given_names', 'Given Names')}</div>
|
||||||
|
<div className="text-xs font-bold text-slate-800">{passportDoc.ocr_data.given_names || 'N/A'}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('surname', 'Surname')}</div>
|
||||||
|
<div className="text-xs font-bold text-slate-800">{passportDoc.ocr_data.surname || 'N/A'}</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">{passportDoc.number}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('sex', 'Sex')}</div>
|
||||||
|
<div className="text-xs font-bold text-slate-800 uppercase">{passportDoc.ocr_data.sex || 'N/A'}</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">{passportDoc.ocr_data.date_of_birth || 'N/A'}</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">{passportDoc.ocr_data.place_of_birth || 'N/A'}</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">{passportDoc.issue_date || 'N/A'}</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">{passportDoc.expiry_date || 'N/A'}</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">{passportDoc.ocr_data.issuing_country || 'N/A'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_id', 'Document ID')}</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">{passportDoc?.number || 'L82739102-UAE'}</div>
|
<div className="text-xs font-bold text-slate-800">{passportDoc.number}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-1">
|
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Issue')}</div>
|
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Issue')}</div>
|
||||||
<div className="text-[10px] font-bold text-slate-800">{passportDoc?.issue_date || '12 Jan 2021'}</div>
|
<div className="text-xs font-bold text-slate-800">{passportDoc.issue_date || 'N/A'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Expiry')}</div>
|
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Expiry')}</div>
|
||||||
<div className="text-[10px] font-bold text-emerald-600">{passportDoc?.expiry_date || '11 Jan 2031'}</div>
|
<div className="text-xs font-bold text-emerald-600">{passportDoc.expiry_date || 'N/A'}</div>
|
||||||
</div>
|
</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>
|
||||||
|
{passportDoc.file_path && (
|
||||||
|
<a
|
||||||
|
href={passportDoc.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 Passport Scan</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="bg-white border border-slate-200 border-dashed rounded-2xl p-6 flex flex-col items-center justify-center text-center space-y-3 min-h-[170px] lg:col-span-2">
|
||||||
|
<div className="w-12 h-12 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center text-slate-400">
|
||||||
|
<FileText className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('ocr_match', 'OCR Match')}</div>
|
<div className="text-xs font-black text-slate-800 uppercase tracking-wider">{t('passport_pending', 'Passport Pending')}</div>
|
||||||
<div className="flex items-center space-x-2 mt-0.5">
|
<p className="text-[10px] text-slate-500 font-medium mt-1 leading-normal max-w-[200px]">
|
||||||
<div className="flex-1 h-1 bg-slate-100 rounded-full overflow-hidden">
|
{t('passport_pending_desc', 'Candidate has not uploaded a passport document yet.')}
|
||||||
<div className="h-full bg-emerald-500" style={{ width: `${passportDoc?.ocr_accuracy || 95}%` }} />
|
</p>
|
||||||
</div>
|
|
||||||
<span className="text-[9px] font-bold text-emerald-600">{passportDoc?.ocr_accuracy || 95}%</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('physical_scan_storage', 'Physical Scan Storage')}</div>
|
|
||||||
<span className="text-[7px] font-mono text-emerald-600 bg-emerald-50 px-1 py-0.5 rounded font-black uppercase tracking-wider block w-fit border border-emerald-100">{t('permanently_purged', 'Permanently Purged')}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Document Card: Visa */}
|
{/* Document Card: Visa */}
|
||||||
<div className="bg-white border border-slate-200 rounded-2xl overflow-hidden shadow-xs hover:shadow-sm">
|
{visaDoc ? (
|
||||||
<div className="bg-slate-50 px-4 py-3 border-b border-slate-100 flex items-center justify-between">
|
<div className="bg-white border border-slate-200 rounded-2xl overflow-hidden shadow-xs hover:shadow-sm lg:col-span-2">
|
||||||
<span className="text-[10px] font-black text-slate-500 uppercase tracking-widest">{t('entry_visa_verified', 'Entry Visa (PDPL Compliant)')}</span>
|
<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('visa_details', 'Entry Visa Details')}</span>
|
||||||
|
{(() => {
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
{worker.visa_status && (worker.visa_status.toLowerCase().includes('tourist') || worker.visa_status.toLowerCase().includes('visit') || worker.visa_status.toLowerCase().includes('cancelled')) ? (
|
{worker.visa_status && (worker.visa_status.toLowerCase().includes('tourist') || worker.visa_status.toLowerCase().includes('visit') || worker.visa_status.toLowerCase().includes('cancelled')) ? (
|
||||||
<AlertTriangle className="w-4 h-4 text-amber-500 animate-pulse" />
|
<span className="inline-flex items-center bg-amber-50 border border-amber-200 text-[9px] font-black uppercase text-amber-700 px-2 py-0.5 rounded-full tracking-wide">
|
||||||
|
{t('transfer_required', 'Transfer Required')}
|
||||||
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<ShieldCheck className="w-4 h-4 text-emerald-500" />
|
<span className="inline-flex items-center bg-emerald-50 border border-emerald-250 text-[9px] font-black uppercase text-emerald-700 px-2 py-0.5 rounded-full tracking-wide">
|
||||||
|
{t('clear_records', 'Clear Records')}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 flex space-x-4">
|
|
||||||
<div className="w-20 h-28 bg-slate-950 rounded-lg flex-shrink-0 border border-slate-800 flex flex-col items-center justify-center p-1.5 text-center relative shadow-inner">
|
|
||||||
<ShieldCheck className="w-6 h-6 text-emerald-500 mb-1 animate-pulse" />
|
|
||||||
<span className="text-[6px] font-black text-emerald-500 uppercase tracking-tighter block">{t('scan_purged', 'Scan Purged')}</span>
|
|
||||||
<span className="text-[5px] font-medium text-slate-400 mt-1 leading-tight block">{t('uae_pdpl_compliant', 'UAE PDPL compliant')}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 space-y-2">
|
<div className="p-6">
|
||||||
|
{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">{visaDoc.ocr_data.name || 'N/A'}</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">{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">{visaDoc.ocr_data.profession || worker.visa_status || 'N/A'}</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">{visaDoc.ocr_data.sponsor || 'N/A'}</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">{visaDoc.ocr_data.passport_number || 'N/A'}</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">{visaDoc.ocr_data.place_of_issue || 'N/A'}</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">{visaDoc.issue_date || 'N/A'}</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">{visaDoc.expiry_date || 'N/A'}</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">{visaDoc.ocr_data.accompanied_by || 'N/A'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-y-4 gap-x-6">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('document_id', 'Document ID')}</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">{visaDoc?.number || 'V2839102-DXB'}</div>
|
<div className="text-xs font-bold text-slate-800">{visaDoc.number}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-1">
|
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Issue')}</div>
|
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('issue_date', 'Issue')}</div>
|
||||||
<div className="text-[10px] font-bold text-slate-800">{visaDoc?.issue_date || '12 Jan 2023'}</div>
|
<div className="text-xs font-bold text-slate-800">{visaDoc.issue_date || 'N/A'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Expiry')}</div>
|
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('expiry_date', 'Expiry')}</div>
|
||||||
<div className="text-[10px] font-bold text-emerald-600">{visaDoc?.expiry_date || '11 Jan 2026'}</div>
|
<div className="text-xs font-bold text-emerald-600">{visaDoc.expiry_date || 'N/A'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
)}
|
||||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('visa_category', 'Visa Category')}</div>
|
|
||||||
<div className="text-xs font-bold text-slate-800">{worker.visa_status}</div>
|
<div className="mt-6 pt-4 border-t border-slate-100 flex items-center justify-between text-xs text-slate-500 font-medium">
|
||||||
</div>
|
<span className="flex items-center space-x-1.5">
|
||||||
<div>
|
<ShieldCheck className="w-4 h-4 text-emerald-600" />
|
||||||
<div className="text-[8px] font-black text-slate-400 uppercase tracking-tighter">{t('verification_status', 'Verification status')}</div>
|
<span>UAE PDPL Compliant Scan Storage (Permanently Purged)</span>
|
||||||
<div className="flex items-center space-x-1.5 mt-0.5">
|
|
||||||
{worker.visa_status && (worker.visa_status.toLowerCase().includes('tourist') || worker.visa_status.toLowerCase().includes('visit')) ? (
|
|
||||||
<span className="inline-flex items-center bg-amber-50 border border-amber-200 text-[8px] font-black uppercase text-amber-700 px-1.5 py-0.5 rounded tracking-wide">
|
|
||||||
{t('transfer_required', 'Transfer Required')}
|
|
||||||
</span>
|
</span>
|
||||||
) : worker.visa_status && worker.visa_status.toLowerCase().includes('cancelled') ? (
|
{visaDoc.file_path && (
|
||||||
<span className="inline-flex items-center bg-rose-50 border border-rose-200 text-[8px] font-black uppercase text-rose-700 px-1.5 py-0.5 rounded tracking-wide animate-pulse">
|
<a
|
||||||
{t('needs_regularization', 'Needs Regularization')}
|
href={visaDoc.file_path}
|
||||||
</span>
|
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"
|
||||||
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-600" />
|
>
|
||||||
<span className="text-[9px] font-bold text-slate-700">{t('clear_records', 'Clear Records')}</span>
|
<FileText className="w-3.5 h-3.5" />
|
||||||
</>
|
<span>View Visa Scan</span>
|
||||||
|
</a>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="bg-white border border-slate-200 border-dashed rounded-2xl p-6 flex flex-col items-center justify-center text-center space-y-3 min-h-[170px] lg:col-span-2">
|
||||||
|
<div className="w-12 h-12 rounded-full bg-slate-50 border border-slate-100 flex items-center justify-center text-slate-400">
|
||||||
|
<FileText className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xs font-black text-slate-800 uppercase tracking-wider">{t('visa_pending', 'Visa Pending')}</div>
|
||||||
|
<p className="text-[10px] text-slate-500 font-medium mt-1 leading-normal max-w-[200px]">
|
||||||
|
{t('visa_pending_desc', 'Candidate has not uploaded a visa document yet.')}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -740,6 +887,44 @@ export default function Show({ worker }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Hired Confirmation Modal */}
|
||||||
|
{showHireConfirmModal && (
|
||||||
|
<div className="fixed inset-0 bg-slate-900/60 backdrop-blur-xs flex items-center justify-center p-4 z-50 animate-fade-in">
|
||||||
|
<div className="bg-white rounded-3xl w-full max-w-md p-6 relative animate-zoom-in text-center space-y-4 border border-slate-200 shadow-2xl">
|
||||||
|
<div className="mx-auto w-16 h-16 rounded-full bg-blue-50 border border-blue-100 flex items-center justify-center text-blue-600">
|
||||||
|
<CheckCircle2 className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="font-extrabold text-lg text-slate-900">{t('confirm_hire_title', 'Confirm Hiring')}</h4>
|
||||||
|
<p className="text-xs text-slate-600 leading-normal px-2">
|
||||||
|
{t('confirm_hire_desc', 'Verify this worker with your needs and make sure to hire this worker. If hired, you can view this worker in the Hired Workers page.')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex space-x-3 pt-2 text-xs font-bold">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowHireConfirmModal(false)}
|
||||||
|
className="flex-1 py-3 border border-slate-200 hover:bg-slate-50 rounded-xl transition-colors"
|
||||||
|
>
|
||||||
|
{t('cancel', 'Cancel')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setShowHireConfirmModal(false);
|
||||||
|
handleMarkHired();
|
||||||
|
}}
|
||||||
|
className="flex-1 bg-emerald-600 hover:bg-emerald-700 text-white py-3 rounded-xl shadow-sm transition-colors"
|
||||||
|
>
|
||||||
|
{t('confirm_hire_action', 'Confirm Hire')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</EmployerLayout>
|
</EmployerLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,8 +47,8 @@
|
|||||||
"bookmark_list": "Bookmark list for interviews",
|
"bookmark_list": "Bookmark list for interviews",
|
||||||
"open_shortlist": "Open shortlist book",
|
"open_shortlist": "Open shortlist book",
|
||||||
"discover_insights": "Discover Insights & Market Stats",
|
"discover_insights": "Discover Insights & Market Stats",
|
||||||
"dubai_insights": "Dubai General Market Insights",
|
"dubai_insights": "General Market Insights",
|
||||||
"hired_dubai": "Hired using app in Dubai",
|
"hired_dubai": "Hired using app in the region",
|
||||||
"cooking_care": "Cooking, Care, Driving",
|
"cooking_care": "Cooking, Care, Driving",
|
||||||
"top_skills": "Top Skills Hired",
|
"top_skills": "Top Skills Hired",
|
||||||
"turnover_rate": "Turnover rate",
|
"turnover_rate": "Turnover rate",
|
||||||
@ -242,19 +242,19 @@
|
|||||||
"charity_events_support_drives": "Charity Events & Support Drives",
|
"charity_events_support_drives": "Charity Events & Support Drives",
|
||||||
"community_charity_events_sponsor_hub": "Community Charity Events - Sponsor Hub",
|
"community_charity_events_sponsor_hub": "Community Charity Events - Sponsor Hub",
|
||||||
"community_charity_event_posted": "COMMUNITY CHARITY EVENT POSTED!",
|
"community_charity_event_posted": "COMMUNITY CHARITY EVENT POSTED!",
|
||||||
"community_charity_event_posted_desc": "Instant Push Notification sent to all workers in Dubai! Morning-of reminder notification scheduled successfully.",
|
"community_charity_event_posted_desc": "Instant Push Notification sent to all workers! Morning-of reminder notification scheduled successfully.",
|
||||||
"dubai_community_support_drives": "Dubai Community Support Drives",
|
"dubai_community_support_drives": "Community Support Drives",
|
||||||
"free_medical_checks_title": "Free Medical Checks, Food Drives & Support Services",
|
"free_medical_checks_title": "Free Medical Checks, Food Drives & Support Services",
|
||||||
"free_medical_checks_desc": "Organizing a support program? Share community campaigns directly. The platform will automatically push an instant pop-up notification to workers, followed by a morning-of event reminder to keep attendance strong.",
|
"free_medical_checks_desc": "Organizing a support program? Share community campaigns directly. The platform will automatically push an instant pop-up notification to workers, followed by a morning-of event reminder to keep attendance strong.",
|
||||||
"search_charity_events_placeholder": "Search charity events & drives...",
|
"search_charity_events_placeholder": "Search charity events & drives...",
|
||||||
"post_charity_event": "Post Charity Event",
|
"post_charity_event": "Post Charity Event",
|
||||||
"broadcast_support_program_desc": "Broadcast a support program, medical camp, or distribution drive to the worker community.",
|
"broadcast_support_program_desc": "Broadcast a support program, medical camp, or distribution drive to the worker community.",
|
||||||
"charity_drive_metadata": "Charity Drive Metadata (Dubai Support)",
|
"charity_drive_metadata": "Charity Drive Metadata",
|
||||||
"event_time_placeholder": "e.g. 9:00 AM - 4:00 PM",
|
"event_time_placeholder": "e.g. 9:00 AM - 4:00 PM",
|
||||||
"what_is_provided": "What is Being Provided",
|
"what_is_provided": "What is Being Provided",
|
||||||
"provided_items_placeholder": "e.g. Free Medical Check, Food Boxes, Supplies",
|
"provided_items_placeholder": "e.g. Free Medical Check, Food Boxes, Supplies",
|
||||||
"location_details_pin": "Location Details & Pin URL",
|
"location_details_pin": "Location Details & Pin URL",
|
||||||
"location_details_placeholder": "e.g. Al Quoz Community Center, Dubai",
|
"location_details_placeholder": "e.g. Al Quoz Community Center",
|
||||||
"maps_pin_link_placeholder": "Google Maps Pin Link (e.g. https://maps.app.goo.gl/xyz)",
|
"maps_pin_link_placeholder": "Google Maps Pin Link (e.g. https://maps.app.goo.gl/xyz)",
|
||||||
"title_label": "Title",
|
"title_label": "Title",
|
||||||
"title_placeholder": "e.g. Free Dental checkup by Emirates Charity",
|
"title_placeholder": "e.g. Free Dental checkup by Emirates Charity",
|
||||||
@ -422,5 +422,7 @@
|
|||||||
"feedback_helps_sponsors_desc": "Your feedback helps other sponsors make secure and direct hiring choices.",
|
"feedback_helps_sponsors_desc": "Your feedback helps other sponsors make secure and direct hiring choices.",
|
||||||
"share_your_experience": "Share Your Experience",
|
"share_your_experience": "Share Your Experience",
|
||||||
"share_experience_placeholder": "Tell other sponsors about childcare skills, cleaning, cooking style, driving safety, etc...",
|
"share_experience_placeholder": "Tell other sponsors about childcare skills, cleaning, cooking style, driving safety, etc...",
|
||||||
"post_trust_review_action": "Post Trust Review"
|
"post_trust_review_action": "Post Trust Review",
|
||||||
|
"no_charity_events_title": "No Charity Events",
|
||||||
|
"no_charity_events_desc": "You haven't posted any charity events yet"
|
||||||
}
|
}
|
||||||
@ -47,8 +47,8 @@
|
|||||||
"bookmark_list": "साक्षात्कार के लिए बुकमार्क सूची",
|
"bookmark_list": "साक्षात्कार के लिए बुकमार्क सूची",
|
||||||
"open_shortlist": "शॉर्टलिस्ट बुक खोलें",
|
"open_shortlist": "शॉर्टलिस्ट बुक खोलें",
|
||||||
"discover_insights": "इनसाइट्स और मार्केट आँकड़े खोजें",
|
"discover_insights": "इनसाइट्स और मार्केट आँकड़े खोजें",
|
||||||
"dubai_insights": "दुबई सामान्य बाज़ार इनसाइट्स",
|
"dubai_insights": "सामान्य बाज़ार इनसाइट्स",
|
||||||
"hired_dubai": "दुबई में ऐप का उपयोग करके काम पर रखा गया",
|
"hired_dubai": "क्षेत्र में ऐप का उपयोग करके काम पर रखा गया",
|
||||||
"cooking_care": "कुकिंग, केयर, ड्राइविंग",
|
"cooking_care": "कुकिंग, केयर, ड्राइविंग",
|
||||||
"top_skills": "शीर्ष कौशल काम पर रखे गए",
|
"top_skills": "शीर्ष कौशल काम पर रखे गए",
|
||||||
"turnover_rate": "टर्नओवर दर",
|
"turnover_rate": "टर्नओवर दर",
|
||||||
@ -242,19 +242,19 @@
|
|||||||
"charity_events_support_drives": "चैरिटी कार्यक्रम और सहायता अभियान",
|
"charity_events_support_drives": "चैरिटी कार्यक्रम और सहायता अभियान",
|
||||||
"community_charity_events_sponsor_hub": "सामुदायिक चैरिटी कार्यक्रम - प्रायोजक केंद्र",
|
"community_charity_events_sponsor_hub": "सामुदायिक चैरिटी कार्यक्रम - प्रायोजक केंद्र",
|
||||||
"community_charity_event_posted": "सामुदायिक चैरिटी कार्यक्रम पोस्ट किया गया!",
|
"community_charity_event_posted": "सामुदायिक चैरिटी कार्यक्रम पोस्ट किया गया!",
|
||||||
"community_charity_event_posted_desc": "दुबई में सभी कामगारों को त्वरित पुश अधिसूचना भेजी गई! कार्यक्रम की सुबह का अनुस्मारक सफलतापूर्वक निर्धारित किया गया।",
|
"community_charity_event_posted_desc": "सभी श्रमिकों को तत्काल पुश सूचना भेजी गई! सुबह का अनुस्मारक सफलतापूर्वक शेड्यूल किया गया।",
|
||||||
"dubai_community_support_drives": "दुबई सामुदायिक सहायता अभियान",
|
"dubai_community_support_drives": "सामुदायिक सहायता अभियान",
|
||||||
"free_medical_checks_title": "मुफ़्त चिकित्सा जांच, भोजन अभियान और सहायता सेवाएँ",
|
"free_medical_checks_title": "मुफ़्त चिकित्सा जांच, भोजन अभियान और सहायता सेवाएँ",
|
||||||
"free_medical_checks_desc": "एक सहायता कार्यक्रम का आयोजन कर रहे हैं? सीधे सामुदायिक अभियानों को साझा करें। प्लेटफ़ॉर्म स्वचालित रूप से कामगारों को एक त्वरित पॉप-अप अधिसूचना भेजेगा, जिसके बाद उपस्थिति को मजबूत रखने के लिए कार्यक्रम की सुबह अनुस्मारक भेजा जाएगा।",
|
"free_medical_checks_desc": "एक सहायता कार्यक्रम का आयोजन कर रहे हैं? सीधे सामुदायिक अभियानों को साझा करें। प्लेटफ़ॉर्म स्वचालित रूप से कामगारों को एक त्वरित पॉप-अप अधिसूचना भेजेगा, जिसके बाद उपस्थिति को मजबूत रखने के लिए कार्यक्रम की सुबह अनुस्मारक भेजा जाएगा।",
|
||||||
"search_charity_events_placeholder": "चैरिटी कार्यक्रमों और अभियानों की खोज करें...",
|
"search_charity_events_placeholder": "चैरिटी कार्यक्रमों और अभियानों की खोज करें...",
|
||||||
"post_charity_event": "चैरिटी कार्यक्रम पोस्ट करें",
|
"post_charity_event": "चैरिटी कार्यक्रम पोस्ट करें",
|
||||||
"broadcast_support_program_desc": "कामगार समुदाय के लिए एक सहायता कार्यक्रम, चिकित्सा शिविर या वितरण अभियान का प्रसारण करें।",
|
"broadcast_support_program_desc": "कामगार समुदाय के लिए एक सहायता कार्यक्रम, चिकित्सा शिविर या वितरण अभियान का प्रसारण करें।",
|
||||||
"charity_drive_metadata": "चैरिटी अभियान मेटाडेटा (दुबई सहायता)",
|
"charity_drive_metadata": "चैरिटी ड्राइव मेटाडेटा",
|
||||||
"event_time_placeholder": "जैसे 9:00 पूर्वाह्न - 4:00 अपराह्न",
|
"event_time_placeholder": "जैसे 9:00 पूर्वाह्न - 4:00 अपराह्न",
|
||||||
"what_is_provided": "क्या प्रदान किया जा रहा है",
|
"what_is_provided": "क्या प्रदान किया जा रहा है",
|
||||||
"provided_items_placeholder": "जैसे मुफ़्त चिकित्सा जांच, भोजन के डिब्बे, आपूर्ति",
|
"provided_items_placeholder": "जैसे मुफ़्त चिकित्सा जांच, भोजन के डिब्बे, आपूर्ति",
|
||||||
"location_details_pin": "स्थान विवरण और पिन यूआरएल",
|
"location_details_pin": "स्थान विवरण और पिन यूआरएल",
|
||||||
"location_details_placeholder": "जैसे अल कुओज़ कम्युनिटी सेंटर, दुबई",
|
"location_details_placeholder": "जैसे- अल क्वोज़ कम्युनिटी सेंटर",
|
||||||
"maps_pin_link_placeholder": "गूगल मैप्स पिन लिंक (जैसे https://maps.app.goo.gl/xyz)",
|
"maps_pin_link_placeholder": "गूगल मैप्स पिन लिंक (जैसे https://maps.app.goo.gl/xyz)",
|
||||||
"title_label": "शीर्षक",
|
"title_label": "शीर्षक",
|
||||||
"title_placeholder": "जैसे अमीरात चैरिटी द्वारा मुफ्त दंत चिकित्सा जांच",
|
"title_placeholder": "जैसे अमीरात चैरिटी द्वारा मुफ्त दंत चिकित्सा जांच",
|
||||||
@ -422,5 +422,7 @@
|
|||||||
"feedback_helps_sponsors_desc": "आपकी प्रतिक्रिया अन्य प्रायोजकों को सुरक्षित और प्रत्यक्ष भर्ती विकल्प चुनने में मदद करती है।",
|
"feedback_helps_sponsors_desc": "आपकी प्रतिक्रिया अन्य प्रायोजकों को सुरक्षित और प्रत्यक्ष भर्ती विकल्प चुनने में मदद करती है।",
|
||||||
"share_your_experience": "अपना अनुभव साझा करें",
|
"share_your_experience": "अपना अनुभव साझा करें",
|
||||||
"share_experience_placeholder": "अन्य प्रायोजकों को बाल देखभाल कौशल, सफाई, खाना पकाने की शैली, ड्राइविंग सुरक्षा आदि के बारे में बताएं...",
|
"share_experience_placeholder": "अन्य प्रायोजकों को बाल देखभाल कौशल, सफाई, खाना पकाने की शैली, ड्राइविंग सुरक्षा आदि के बारे में बताएं...",
|
||||||
"post_trust_review_action": "समीक्षा पोस्ट करें"
|
"post_trust_review_action": "समीक्षा पोस्ट करें",
|
||||||
|
"no_charity_events_title": "कोई चैरिटी कार्यक्रम नहीं",
|
||||||
|
"no_charity_events_desc": "आपने अभी तक कोई चैरिटी कार्यक्रम पोस्ट नहीं किया है"
|
||||||
}
|
}
|
||||||
@ -47,8 +47,8 @@
|
|||||||
"bookmark_list": "Hifadhi orodha kwa ajili ya mahojiano",
|
"bookmark_list": "Hifadhi orodha kwa ajili ya mahojiano",
|
||||||
"open_shortlist": "Fungua kitabu cha orodha fupi",
|
"open_shortlist": "Fungua kitabu cha orodha fupi",
|
||||||
"discover_insights": "Gundua Maarifa na Takwimu za Soko",
|
"discover_insights": "Gundua Maarifa na Takwimu za Soko",
|
||||||
"dubai_insights": "Maarifa ya Jumla ya Soko la Dubai",
|
"dubai_insights": "Maarifa ya Jumla ya Soko",
|
||||||
"hired_dubai": "Walioajiriwa kwa kutumia programu huko Dubai",
|
"hired_dubai": "Walioajiriwa kwa kutumia programu katika mkoa",
|
||||||
"cooking_care": "Kupika, Matunzo, Kuendesha Gari",
|
"cooking_care": "Kupika, Matunzo, Kuendesha Gari",
|
||||||
"top_skills": "Ujuzi Unaoongoza Kuajiriwa",
|
"top_skills": "Ujuzi Unaoongoza Kuajiriwa",
|
||||||
"turnover_rate": "Kiwango cha mabadiliko ya wafanyakazi",
|
"turnover_rate": "Kiwango cha mabadiliko ya wafanyakazi",
|
||||||
@ -242,19 +242,19 @@
|
|||||||
"charity_events_support_drives": "Matukio ya Misaada & Kampeni za Usaidizi",
|
"charity_events_support_drives": "Matukio ya Misaada & Kampeni za Usaidizi",
|
||||||
"community_charity_events_sponsor_hub": "Matukio ya Misaada ya Jamii - Kituo cha Mdhamini",
|
"community_charity_events_sponsor_hub": "Matukio ya Misaada ya Jamii - Kituo cha Mdhamini",
|
||||||
"community_charity_event_posted": "TUKIO LA MISAADA LA JAMII LIMEPOSITIWA!",
|
"community_charity_event_posted": "TUKIO LA MISAADA LA JAMII LIMEPOSITIWA!",
|
||||||
"community_charity_event_posted_desc": "Arifa ya papo hapo imetumwa kwa wafanyakazi wote huko Dubai! Kikumbusho cha asubuhi ya tukio kimeratibiwa kwa mafanikio.",
|
"community_charity_event_posted_desc": "Arifa ya papo hapo imetumwa kwa wafanyakazi wote! Kikumbusho cha asubuhi ya tukio kimeratibiwa kwa mafanikio.",
|
||||||
"dubai_community_support_drives": "Kampeni za Usaidizi za Jamii ya Dubai",
|
"dubai_community_support_drives": "Kampeni za Usaidizi za Jamii",
|
||||||
"free_medical_checks_title": "Uchunguzi wa Matibabu Bila Malipo, Kampeni za Chakula na Huduma za Usaidizi",
|
"free_medical_checks_title": "Uchunguzi wa Matibabu Bila Malipo, Kampeni za Chakula na Huduma za Usaidizi",
|
||||||
"free_medical_checks_desc": "Unaratibu mpango wa usaidizi? Shiriki kampeni za jamii moja kwa moja. Jukwaa litatuma arifa ya papo hapo kwa wafanyakazi, ikifuatiwa na kikumbusho cha asubuhi ya tukio ili kudumisha mahudhurio mazuri.",
|
"free_medical_checks_desc": "Unaratibu mpango wa usaidizi? Shiriki kampeni za jamii moja kwa moja. Jukwaa litatuma arifa ya papo hapo kwa wafanyakazi, ikifuatiwa na kikumbusho cha asubuhi ya tukio ili kudumisha mahudhurio mazuri.",
|
||||||
"search_charity_events_placeholder": "Tafuta matukio ya misaada na kampeni...",
|
"search_charity_events_placeholder": "Tafuta matukio ya misaada na kampeni...",
|
||||||
"post_charity_event": "Positi Tukio la Misaada",
|
"post_charity_event": "Positi Tukio la Misaada",
|
||||||
"broadcast_support_program_desc": "Tangaza mpango wa usaidizi, kambi ya matibabu, au kampeni ya ugawaji kwa jamii ya wafanyakazi.",
|
"broadcast_support_program_desc": "Tangaza mpango wa usaidizi, kambi ya matibabu, au kampeni ya ugawaji kwa jamii ya wafanyakazi.",
|
||||||
"charity_drive_metadata": "Metadata ya Kampeni ya Misaada (Usaidizi wa Dubai)",
|
"charity_drive_metadata": "Metadata ya Kampeni ya Misaada",
|
||||||
"event_time_placeholder": "m.f. 9:00 Asubuhi - 4:00 Jioni",
|
"event_time_placeholder": "m.f. 9:00 Asubuhi - 4:00 Jioni",
|
||||||
"what_is_provided": "Kile Kinachotolewa",
|
"what_is_provided": "Kile Kinachotolewa",
|
||||||
"provided_items_placeholder": "m.f. Uchunguzi wa Matibabu wa Bure, Masanduku ya Chakula, Vifaa",
|
"provided_items_placeholder": "m.f. Uchunguzi wa Matibabu wa Bure, Masanduku ya Chakula, Vifaa",
|
||||||
"location_details_pin": "Maelezo ya Eneo & URL ya Ramani",
|
"location_details_pin": "Maelezo ya Eneo & URL ya Ramani",
|
||||||
"location_details_placeholder": "m.f. Kituo cha Jamii cha Al Quoz, Dubai",
|
"location_details_placeholder": "m.f. Kituo cha Jamii cha Al Quoz",
|
||||||
"maps_pin_link_placeholder": "Kiungo cha Ramani ya Google Maps (m.f. https://maps.app.goo.gl/xyz)",
|
"maps_pin_link_placeholder": "Kiungo cha Ramani ya Google Maps (m.f. https://maps.app.goo.gl/xyz)",
|
||||||
"title_label": "Kichwa",
|
"title_label": "Kichwa",
|
||||||
"title_placeholder": "m.f. Uchunguzi wa meno wa bure na Shirika la Misaada la Emirates",
|
"title_placeholder": "m.f. Uchunguzi wa meno wa bure na Shirika la Misaada la Emirates",
|
||||||
@ -422,5 +422,7 @@
|
|||||||
"feedback_helps_sponsors_desc": "Maoni yako yanasaidia wafadhili wengine kufanya maamuzi salama na ya moja kwa moja ya kuajiri.",
|
"feedback_helps_sponsors_desc": "Maoni yako yanasaidia wafadhili wengine kufanya maamuzi salama na ya moja kwa moja ya kuajiri.",
|
||||||
"share_your_experience": "Shiriki Uzoefu Wako",
|
"share_your_experience": "Shiriki Uzoefu Wako",
|
||||||
"share_experience_placeholder": "Waambie wafadhili wengine kuhusu ujuzi wa kulelea watoto, kusafisha, mtindo wa kupika, usalama wa kuendesha gari, n.k...",
|
"share_experience_placeholder": "Waambie wafadhili wengine kuhusu ujuzi wa kulelea watoto, kusafisha, mtindo wa kupika, usalama wa kuendesha gari, n.k...",
|
||||||
"post_trust_review_action": "Chapisha Mapitio ya Uaminifu"
|
"post_trust_review_action": "Chapisha Mapitio ya Uaminifu",
|
||||||
|
"no_charity_events_title": "Hakuna Matukio ya Misaada",
|
||||||
|
"no_charity_events_desc": "Bado haujaposti matukio yoyote ya misaada"
|
||||||
}
|
}
|
||||||
@ -47,8 +47,8 @@
|
|||||||
"bookmark_list": "நேர்காணல்களுக்கான புக்மார்க் பட்டியல்",
|
"bookmark_list": "நேர்காணல்களுக்கான புக்மார்க் பட்டியல்",
|
||||||
"open_shortlist": "சுருக்கப்பட்டியல் புத்தகத்தைத் திற",
|
"open_shortlist": "சுருக்கப்பட்டியல் புத்தகத்தைத் திற",
|
||||||
"discover_insights": "சந்தை புள்ளிவிவரங்கள் & நுண்ணறிவுகளைக் கண்டறியவும்",
|
"discover_insights": "சந்தை புள்ளிவிவரங்கள் & நுண்ணறிவுகளைக் கண்டறியவும்",
|
||||||
"dubai_insights": "துபாய் பொதுச் சந்தை நுண்ணறிவு",
|
"dubai_insights": "பொதுச் சந்தை நுண்ணறிவு",
|
||||||
"hired_dubai": "துபாயில் செயலியைப் பயன்படுத்தி பணியமர்த்தப்பட்டவர்கள்",
|
"hired_dubai": "இப்பகுதியில் செயலியைப் பயன்படுத்தி பணியமர்த்தப்பட்டவர்கள்",
|
||||||
"cooking_care": "சமையல், பராமரிப்பு, வாகனம் ஓட்டுதல்",
|
"cooking_care": "சமையல், பராமரிப்பு, வாகனம் ஓட்டுதல்",
|
||||||
"top_skills": "அதிகம் பணியமர்த்தப்பட்ட திறன்கள்",
|
"top_skills": "அதிகம் பணியமர்த்தப்பட்ட திறன்கள்",
|
||||||
"turnover_rate": "பணியாளர் வருவாய் விகிதம்",
|
"turnover_rate": "பணியாளர் வருவாய் விகிதம்",
|
||||||
@ -242,19 +242,19 @@
|
|||||||
"charity_events_support_drives": "அறக்கட்டளை நிகழ்வுகள் & ஆதரவு இயக்கங்கள்",
|
"charity_events_support_drives": "அறக்கட்டளை நிகழ்வுகள் & ஆதரவு இயக்கங்கள்",
|
||||||
"community_charity_events_sponsor_hub": "சமூக அறக்கட்டளை நிகழ்வுகள் - முதலாளி மையம்",
|
"community_charity_events_sponsor_hub": "சமூக அறக்கட்டளை நிகழ்வுகள் - முதலாளி மையம்",
|
||||||
"community_charity_event_posted": "சமூக அறக்கட்டளை நிகழ்வு வெளியிடப்பட்டது!",
|
"community_charity_event_posted": "சமூக அறக்கட்டளை நிகழ்வு வெளியிடப்பட்டது!",
|
||||||
"community_charity_event_posted_desc": "துபாயில் உள்ள அனைத்து பணியாளர்களுக்கும் உடனடி புஷ் அறிவிப்பு அனுப்பப்பட்டது! நிகழ்வு காலையிலேயே நினைவூட்டல் வெற்றிகரமாக திட்டமிடப்பட்டது.",
|
"community_charity_event_posted_desc": "அனைத்து ஊழியர்களுக்கும் உடனடி புஷ் அறிவிப்பு அனுப்பப்பட்டது! காலையில் நினைவூட்டல் வெற்றிகரமாக திட்டமிடப்பட்டது.",
|
||||||
"dubai_community_support_drives": "துபாய் சமூக ஆதரவு இயக்கங்கள்",
|
"dubai_community_support_drives": "சமூக ஆதரவு இயக்கங்கள்",
|
||||||
"free_medical_checks_title": "இலவச மருத்துவ பரிசோதனைகள், உணவு இயக்கங்கள் & ஆதரவு சேவைகள்",
|
"free_medical_checks_title": "இலவச மருத்துவ பரிசோதனைகள், உணவு இயக்கங்கள் & ஆதரவு சேவைகள்",
|
||||||
"free_medical_checks_desc": "ஆதரவு திட்டத்தை ஏற்பாடு செய்கிறீர்களா? சமூக பிரச்சாரங்களை நேரடியாக பகிரவும். தளம் தானாகவே பணியாளர்களுக்கு உடனடி புஷ் அறிவிப்பை அனுப்பும், அதைத் தொடர்ந்து வருகையை வலுவாக வைத்திருக்க நிகழ்வு காலையில் நினைவூட்டல் அனுப்பப்படும்.",
|
"free_medical_checks_desc": "ஆதரவு திட்டத்தை ஏற்பாடு செய்கிறீர்களா? சமூக பிரச்சாரங்களை நேரடியாக பகிரவும். தளம் தானாகவே பணியாளர்களுக்கு உடனடி புஷ் அறிவிப்பை அனுப்பும், அதைத் தொடர்ந்து வருகையை வலுவாக வைத்திருக்க நிகழ்வு காலையில் நினைவூட்டல் அனுப்பப்படும்.",
|
||||||
"search_charity_events_placeholder": "அறக்கட்டளை நிகழ்வுகள் & இயக்கங்களை தேடுக...",
|
"search_charity_events_placeholder": "அறக்கட்டளை நிகழ்வுகள் & இயக்கங்களை தேடுக...",
|
||||||
"post_charity_event": "அறக்கட்டளை நிகழ்வை வெளியிடு",
|
"post_charity_event": "அறக்கட்டளை நிகழ்வை வெளியிடு",
|
||||||
"broadcast_support_program_desc": "பணியாளர் சமூகத்திற்கு ஒரு ஆதரவு திட்டம், மருத்துவ முகாம் அல்லது விநியோக இயக்கத்தை ஒளிபரப்பவும்.",
|
"broadcast_support_program_desc": "பணியாளர் சமூகத்திற்கு ஒரு ஆதரவு திட்டம், மருத்துவ முகாம் அல்லது விநியோக இயக்கத்தை ஒளிபரப்பவும்.",
|
||||||
"charity_drive_metadata": "அறக்கட்டளை இயக்க மெட்டாடேட்டா (துபாய் ஆதரவு)",
|
"charity_drive_metadata": "அறக்கட்டளை இயக்க மெட்டாடேட்டா",
|
||||||
"event_time_placeholder": "உதாரணமாக 9:00 முற்பகல் - 4:00 பிற்பகல்",
|
"event_time_placeholder": "உதாரணமாக 9:00 முற்பகல் - 4:00 பிற்பகல்",
|
||||||
"what_is_provided": "வழங்கப்படுவது என்ன",
|
"what_is_provided": "வழங்கப்படுவது என்ன",
|
||||||
"provided_items_placeholder": "உதாரணமாக இலவச மருத்துவ பரிசோதனை, உணவுப் பெட்டிகள், பொருட்கள்",
|
"provided_items_placeholder": "உதாரணமாக இலவச மருத்துவ பரிசோதனை, உணவுப் பெட்டிகள், பொருட்கள்",
|
||||||
"location_details_pin": "இட விவரங்கள் & கூகுள் மேப் இணைப்பு",
|
"location_details_pin": "இட விவரங்கள் & கூகுள் மேப் இணைப்பு",
|
||||||
"location_details_placeholder": "உதாரணமாக அல் கூஸ் சமூக மையம், துபாய்",
|
"location_details_placeholder": "எ.கா. அல் கோஸ் சமூக மையம்",
|
||||||
"maps_pin_link_placeholder": "கூகுள் மேப்ஸ் இருப்பிட இணைப்பு (உதாரணமாக https://maps.app.goo.gl/xyz)",
|
"maps_pin_link_placeholder": "கூகுள் மேப்ஸ் இருப்பிட இணைப்பு (உதாரணமாக https://maps.app.goo.gl/xyz)",
|
||||||
"title_label": "தலைப்பு",
|
"title_label": "தலைப்பு",
|
||||||
"title_placeholder": "உதாரணமாக எமிரேட்ஸ் அறக்கட்டளையின் இலவச பல் பரிசோதனை",
|
"title_placeholder": "உதாரணமாக எமிரேட்ஸ் அறக்கட்டளையின் இலவச பல் பரிசோதனை",
|
||||||
@ -422,5 +422,7 @@
|
|||||||
"feedback_helps_sponsors_desc": "உங்கள் கருத்து மற்ற முதலாளிகளுக்கு பாதுகாப்பான மற்றும் நேரடி பணியமர்த்தல் முடிவுகளை எடுக்க உதவுகிறது.",
|
"feedback_helps_sponsors_desc": "உங்கள் கருத்து மற்ற முதலாளிகளுக்கு பாதுகாப்பான மற்றும் நேரடி பணியமர்த்தல் முடிவுகளை எடுக்க உதவுகிறது.",
|
||||||
"share_your_experience": "உங்கள் அனுபவத்தைப் பகிர்ந்து கொள்ளுங்கள்",
|
"share_your_experience": "உங்கள் அனுபவத்தைப் பகிர்ந்து கொள்ளுங்கள்",
|
||||||
"share_experience_placeholder": "குழந்தை பராமரிப்பு திறன்கள், சுத்தம் செய்தல், சமையல் நடைமுறை, ஓட்டுநர் பாதுகாப்பு போன்றவற்றைப் பற்றி மற்ற முதலாளிகளுக்குத் தெரியப்படுத்துங்கள்...",
|
"share_experience_placeholder": "குழந்தை பராமரிப்பு திறன்கள், சுத்தம் செய்தல், சமையல் நடைமுறை, ஓட்டுநர் பாதுகாப்பு போன்றவற்றைப் பற்றி மற்ற முதலாளிகளுக்குத் தெரியப்படுத்துங்கள்...",
|
||||||
"post_trust_review_action": "மதிப்புரையை வெளியிடு"
|
"post_trust_review_action": "மதிப்புரையை வெளியிடு",
|
||||||
|
"no_charity_events_title": "அறக்கட்டளை நிகழ்வுகள் எதுவுமில்லை",
|
||||||
|
"no_charity_events_desc": "நீங்கள் இன்னும் எந்த அறக்கட்டளை நிகழ்வையும் வெளியிடவில்லை"
|
||||||
}
|
}
|
||||||
@ -47,8 +47,8 @@
|
|||||||
"bookmark_list": "I-save ang listahan para sa mga interview",
|
"bookmark_list": "I-save ang listahan para sa mga interview",
|
||||||
"open_shortlist": "Buksan ang shortlist book",
|
"open_shortlist": "Buksan ang shortlist book",
|
||||||
"discover_insights": "Tuklasin ang mga Insight at Stats sa Merkado",
|
"discover_insights": "Tuklasin ang mga Insight at Stats sa Merkado",
|
||||||
"dubai_insights": "Mga Pangkalahatang Insight sa Merkado sa Dubai",
|
"dubai_insights": "Mga Pangkalahatang Insight sa Merkado",
|
||||||
"hired_dubai": "Na-hire gamit ang app sa Dubai",
|
"hired_dubai": "Na-hire gamit ang app sa rehiyon",
|
||||||
"cooking_care": "Pagluluto, Pag-aalaga, Pagmamaneho",
|
"cooking_care": "Pagluluto, Pag-aalaga, Pagmamaneho",
|
||||||
"top_skills": "Mga Pangunahing Kasanayang Na-hire",
|
"top_skills": "Mga Pangunahing Kasanayang Na-hire",
|
||||||
"turnover_rate": "Turnover rate",
|
"turnover_rate": "Turnover rate",
|
||||||
@ -242,19 +242,19 @@
|
|||||||
"charity_events_support_drives": "Mga Kaganapang Kawanggawa & Kampanya sa Usaid",
|
"charity_events_support_drives": "Mga Kaganapang Kawanggawa & Kampanya sa Usaid",
|
||||||
"community_charity_events_sponsor_hub": "Mga Kaganapang Kawanggawa sa Komunidad - Kituo ng Sponsor",
|
"community_charity_events_sponsor_hub": "Mga Kaganapang Kawanggawa sa Komunidad - Kituo ng Sponsor",
|
||||||
"community_charity_event_posted": "NAI-POST NA ANG CHARITY EVENT NG KOMUNIDAD!",
|
"community_charity_event_posted": "NAI-POST NA ANG CHARITY EVENT NG KOMUNIDAD!",
|
||||||
"community_charity_event_posted_desc": "Agad na nagpadala ng push notification sa lahat ng manggagawa sa Dubai! Matagumpay na nai-schedule ang umagang paalala.",
|
"community_charity_event_posted_desc": "Agad na nagpadala ng push notification sa lahat ng manggagawa! Matagumpay na nai-schedule ang umagang paalala.",
|
||||||
"dubai_community_support_drives": "Mga Kampanya sa Suporta ng Komunidad sa Dubai",
|
"dubai_community_support_drives": "Mga Kampanya sa Suporta ng Komunidad",
|
||||||
"free_medical_checks_title": "Libreng Ugnayan sa Kalusugan, Pamamahagi ng Pagkain & mga Serbisyo sa Suporta",
|
"free_medical_checks_title": "Libreng Ugnayan sa Kalusugan, Pamamahagi ng Pagkain & mga Serbisyo sa Suporta",
|
||||||
"free_medical_checks_desc": "Nag-oorganisa ng programang suporta? Ibahagi ang mga kampanya ng komunidad nang direkta. Awtomatikong magpapadala ng push notification ang platform sa mga manggagawa, na susundan ng paalala sa umaga ng kaganapan.",
|
"free_medical_checks_desc": "Nag-oorganisa ng programang suporta? Ibahagi ang mga kampanya ng komunidad nang direkta. Awtomatikong magpapadala ng push notification ang platform sa mga manggagawa, na susundan ng paalala sa umaga ng kaganapan.",
|
||||||
"search_charity_events_placeholder": "Maghanap ng mga kawanggawa & kampanya...",
|
"search_charity_events_placeholder": "Maghanap ng mga kawanggawa & kampanya...",
|
||||||
"post_charity_event": "I-post ang Charity Event",
|
"post_charity_event": "I-post ang Charity Event",
|
||||||
"broadcast_support_program_desc": "Ipalaganap ang programang suporta, medical camp, o pamamahagi sa komunidad ng mga manggagawa.",
|
"broadcast_support_program_desc": "Ipalaganap ang programang suporta, medical camp, o pamamahagi sa komunidad ng mga manggagawa.",
|
||||||
"charity_drive_metadata": "Metadata ng Charity Drive (Suporta sa Dubai)",
|
"charity_drive_metadata": "Metadata ng Charity Drive",
|
||||||
"event_time_placeholder": "hal. 9:00 AM - 4:00 PM",
|
"event_time_placeholder": "hal. 9:00 AM - 4:00 PM",
|
||||||
"what_is_provided": "Ano ang Ipinapamahagi",
|
"what_is_provided": "Ano ang Ipinapamahagi",
|
||||||
"provided_items_placeholder": "hal. Libreng Dental Check, Kahon ng Pagkain, mga Kagamitan",
|
"provided_items_placeholder": "hal. Libreng Dental Check, Kahon ng Pagkain, mga Kagamitan",
|
||||||
"location_details_pin": "Mga Detalye ng Lokasyon & Pin Link",
|
"location_details_pin": "Mga Detalye ng Lokasyon & Pin Link",
|
||||||
"location_details_placeholder": "hal. Al Quoz Community Center, Dubai",
|
"location_details_placeholder": "hal. Al Quoz Community Center",
|
||||||
"maps_pin_link_placeholder": "Link ng Google Maps (hal. https://maps.app.goo.gl/xyz)",
|
"maps_pin_link_placeholder": "Link ng Google Maps (hal. https://maps.app.goo.gl/xyz)",
|
||||||
"title_label": "Pamagat",
|
"title_label": "Pamagat",
|
||||||
"title_placeholder": "hal. Libreng Dental Checkup ng Emirates Charity",
|
"title_placeholder": "hal. Libreng Dental Checkup ng Emirates Charity",
|
||||||
@ -422,5 +422,7 @@
|
|||||||
"feedback_helps_sponsors_desc": "Ang iyong puna ay tumutulong sa iba pang mga sponsor na gumawa ng ligtas at direktang mga desisyon sa pag-hire.",
|
"feedback_helps_sponsors_desc": "Ang iyong puna ay tumutulong sa iba pang mga sponsor na gumawa ng ligtas at direktang mga desisyon sa pag-hire.",
|
||||||
"share_your_experience": "Ibahagi ang Iyong Karanasan",
|
"share_your_experience": "Ibahagi ang Iyong Karanasan",
|
||||||
"share_experience_placeholder": "Sabihin sa iba pang mga sponsor ang tungkol sa mga kasanayan sa childcare, paglilinis, estilo ng pagluluto, kaligtasan sa pagmamaneho, atbp...",
|
"share_experience_placeholder": "Sabihin sa iba pang mga sponsor ang tungkol sa mga kasanayan sa childcare, paglilinis, estilo ng pagluluto, kaligtasan sa pagmamaneho, atbp...",
|
||||||
"post_trust_review_action": "Mag-post ng Review ng Tiwala"
|
"post_trust_review_action": "Mag-post ng Review ng Tiwala",
|
||||||
|
"no_charity_events_title": "Walang mga Charity Event",
|
||||||
|
"no_charity_events_desc": "Hindi ka pa nagpo-post ng anumang mga charity event"
|
||||||
}
|
}
|
||||||
@ -537,6 +537,70 @@ public function test_register_passport_and_visa_with_direct_json_payload()
|
|||||||
$this->assertEquals('Sponsor Name', $visaDoc->ocr_data['sponsor']);
|
$this->assertEquals('Sponsor Name', $visaDoc->ocr_data['sponsor']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test worker passport and visa registration when they are passed as JSON strings.
|
||||||
|
*/
|
||||||
|
public function test_register_passport_and_visa_with_json_encoded_strings()
|
||||||
|
{
|
||||||
|
$response = $this->postJson('/api/workers/register', [
|
||||||
|
'name' => 'JSON String Worker',
|
||||||
|
'phone' => '+971509999999',
|
||||||
|
'salary' => 3000,
|
||||||
|
'password' => 'secret123',
|
||||||
|
'language' => 'EN',
|
||||||
|
'passport' => json_encode([
|
||||||
|
'authority' => 'Authority',
|
||||||
|
'date_of_birth' => '14/05/1990',
|
||||||
|
'date_of_expiry' => '05/07/2028',
|
||||||
|
'date_of_issue' => '05/07/2018',
|
||||||
|
'document_type' => 'P',
|
||||||
|
'given_names' => 'Johnny',
|
||||||
|
'issuing_country' => 'PHL',
|
||||||
|
'nationality' => 'PHL',
|
||||||
|
'passport_number' => 'Z99B1234',
|
||||||
|
'personal_number' => '28',
|
||||||
|
'place_of_birth' => 'Manila',
|
||||||
|
'sex' => 'M',
|
||||||
|
'surname' => 'Test',
|
||||||
|
]),
|
||||||
|
'visa' => json_encode([
|
||||||
|
'accompanied_by' => '0',
|
||||||
|
'expiry_date' => '2027-02-15',
|
||||||
|
'file_number' => '202/2022/2840234',
|
||||||
|
'id_number' => '784198839607840',
|
||||||
|
'issue_date' => '2025-02-16',
|
||||||
|
'name' => 'Johnny Test',
|
||||||
|
'passport_number' => 'Z99B1234',
|
||||||
|
'place_of_issue' => 'Abu Dhabi',
|
||||||
|
'profession' => 'HELPER',
|
||||||
|
'sponsor' => 'Sponsor Name',
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(201);
|
||||||
|
$workerId = $response->json('data.worker.id');
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('workers', [
|
||||||
|
'id' => $workerId,
|
||||||
|
'name' => 'Johnny Test',
|
||||||
|
'phone' => '+971509999999',
|
||||||
|
'verified' => true,
|
||||||
|
'visa_status' => 'HELPER',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('worker_documents', [
|
||||||
|
'worker_id' => $workerId,
|
||||||
|
'type' => 'passport',
|
||||||
|
'number' => 'Z99B1234',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('worker_documents', [
|
||||||
|
'worker_id' => $workerId,
|
||||||
|
'type' => 'visa',
|
||||||
|
'number' => '202/2022/2840234',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test updating profile with new API fields.
|
* Test updating profile with new API fields.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user