2026-07-07 14:16:17 +05:30

943 lines
59 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useMemo } from 'react';
import { Head, Link, router } from '@inertiajs/react';
import EmployerLayout from '../../Layouts/EmployerLayout';
import { useTranslation } from '../../lib/LanguageContext';
import {
CheckCircle2,
Globe2,
Briefcase,
DollarSign,
MessageSquare,
Trash2,
SlidersHorizontal,
ShieldCheck,
AlertTriangle,
Sparkles,
Star,
Eye,
User,
X,
HeartHandshake,
CheckCircle,
MapPin,
Calendar,
Search,
RotateCcw,
ArrowUpDown
} from 'lucide-react';
import FilterDrawer, { FilterSection, FilterSelect, FilterInput, FilterChips, FilterCheckboxList } from '../../components/Employer/FilterDrawer';
const getLanguageFlag = (lang) => {
const flags = {
'English': '🇬🇧',
'Arabic': '🇦🇪',
'Tagalog': '🇵🇭',
'Hindi': '🇮🇳',
'Swahili': '🇰🇪',
'French': '🇫🇷',
'Indonesian': '🇮🇩'
};
return flags[lang] || '🌐';
};
export default function Shortlist({ shortlistedWorkers, filtersMetadata = {} }) {
const { t } = useTranslation();
const [workers, setWorkers] = useState(shortlistedWorkers || []);
// Comparison & Quick Preview State
const [comparisonIds, setComparisonIds] = useState([]);
const [previewWorker, setPreviewWorker] = useState(null);
// Basic Filters
const [searchQuery, setSearchQuery] = useState('');
const [selectedProfession, setSelectedProfession] = useState('All Professions');
const [selectedNationalities, setSelectedNationalities] = useState([]);
const [selectedGender, setSelectedGender] = useState('All Genders');
const [selectedExperience, setSelectedExperience] = useState('All Experience');
const [selectedReligion, setSelectedReligion] = useState('All Religions');
const [maxSalary, setMaxSalary] = useState(5000);
// Advanced Multi-Select Filters & Sorting
const [selectedLanguages, setSelectedLanguages] = useState([]);
const [selectedVisaStatuses, setSelectedVisaStatuses] = useState([]);
const [selectedSkills, setSelectedSkills] = useState([]);
const [selectedWorkTypes, setSelectedWorkTypes] = useState([]);
const [sortBy, setSortBy] = useState('default');
// Advanced Filters Toggle & Details
const [filterLocation, setFilterLocation] = useState('All');
const [filterJobType, setFilterJobType] = useState('All');
const [filterAccommodation, setFilterAccommodation] = useState('All');
const [filterInCountry, setFilterInCountry] = useState('All');
const [filterVisaType, setFilterVisaType] = useState('All');
// Drawer
const [drawerOpen, setDrawerOpen] = useState(false);
const resetFilters = () => {
setSearchQuery('');
setSelectedProfession('All Professions');
setSelectedNationalities([]);
setSelectedGender('All Genders');
setSelectedExperience('All Experience');
setSelectedReligion('All Religions');
setMaxSalary(5000);
setSelectedLanguages([]);
setSelectedVisaStatuses([]);
setSelectedSkills([]);
setSelectedWorkTypes([]);
setSortBy('default');
setFilterLocation('All');
setFilterJobType('All');
setFilterAccommodation('All');
setFilterInCountry('All');
setFilterVisaType('All');
};
const activeFilterCount = useMemo(() => {
let count = 0;
if (filterLocation !== 'All' && filterLocation.trim() !== '') count++;
if (filterJobType !== 'All') count++;
if (filterAccommodation !== 'All') count++;
if (selectedNationalities.length > 0) count++;
if (selectedGender !== 'All Genders') count++;
if (selectedProfession !== 'All Professions') count++;
if (filterInCountry !== 'All') count++;
if (filterInCountry === 'In Country' && filterVisaType !== 'All') count++;
if (selectedSkills.length > 0) count++;
if (selectedLanguages.length > 0) count++;
if (maxSalary < 5000) count++;
return count;
}, [filterLocation, filterJobType, filterAccommodation, selectedNationalities, selectedGender, selectedProfession, filterInCountry, filterVisaType, selectedSkills, selectedLanguages, maxSalary]);
const activeFilterTags = useMemo(() => {
const tags = [];
if (filterLocation !== 'All' && filterLocation.trim()) tags.push({ key: 'loc', label: `📍 ${filterLocation}`, clear: () => setFilterLocation('All') });
if (filterJobType !== 'All') tags.push({ key: 'job', label: `💼 ${filterJobType}`, clear: () => setFilterJobType('All') });
if (filterAccommodation !== 'All') tags.push({ key: 'acc', label: `🏠 ${filterAccommodation.replace('_', '-')}`, clear: () => setFilterAccommodation('All') });
if (selectedNationalities.length > 0) tags.push({ key: 'nat', label: `🌍 ${selectedNationalities.length} Nat`, clear: () => setSelectedNationalities([]) });
if (selectedGender !== 'All Genders') tags.push({ key: 'gender', label: `🚻 ${selectedGender}`, clear: () => setSelectedGender('All Genders') });
if (selectedProfession !== 'All Professions') tags.push({ key: 'profession', label: `🧑‍🔧 ${selectedProfession}`, clear: () => setSelectedProfession('All Professions') });
if (filterInCountry !== 'All') tags.push({ key: 'country', label: `✈️ ${filterInCountry}`, clear: () => { setFilterInCountry('All'); setFilterVisaType('All'); } });
if (filterInCountry === 'In Country' && filterVisaType !== 'All') tags.push({ key: 'visa', label: `🪪 ${filterVisaType}`, clear: () => setFilterVisaType('All') });
if (selectedSkills.length > 0) tags.push({ key: 'skills', label: `🛠 ${selectedSkills.length} skill${selectedSkills.length > 1 ? 's' : ''}`, clear: () => setSelectedSkills([]) });
if (selectedLanguages.length > 0) tags.push({ key: 'lang', label: `🗣 ${selectedLanguages.length} lang`, clear: () => setSelectedLanguages([]) });
if (maxSalary < 5000) tags.push({ key: 'salary', label: `💰 < ${maxSalary} AED`, clear: () => setMaxSalary(5000) });
return tags;
}, [filterLocation, filterJobType, filterAccommodation, selectedNationalities, selectedGender, selectedProfession, filterInCountry, filterVisaType, selectedSkills, selectedLanguages, maxSalary]);
const toggleMultiSelect = (item, setSelectedList) => {
setSelectedList(prev =>
prev.includes(item) ? prev.filter(x => x !== item) : [...prev, item]
);
};
const removeWorker = (id) => {
// Optimistic UI state update
setWorkers(workers.filter(w => w.id !== id));
setComparisonIds(comparisonIds.filter(i => i !== id));
// Persist change to database
router.post(`/employer/shortlist/${id}/remove`, {}, {
preserveScroll: true
});
};
const handleToggleComparison = (id) => {
if (comparisonIds.includes(id)) {
setComparisonIds(comparisonIds.filter(i => i !== id));
} else {
if (comparisonIds.length >= 3) {
alert(t('max_compare_alert', 'You can compare a maximum of 3 candidates.'));
return;
}
setComparisonIds([...comparisonIds, id]);
}
};
// Filter and Sort Worker List
const filteredWorkers = useMemo(() => {
let list = [...workers];
list = list.filter(worker => {
// Search Query
if (searchQuery.trim() !== '') {
const query = searchQuery.toLowerCase();
const matchName = worker.name.toLowerCase().includes(query);
const matchBio = worker.bio?.toLowerCase().includes(query) || false;
const matchSkills = worker.skills?.some(s => s.toLowerCase().includes(query)) || false;
if (!matchName && !matchBio && !matchSkills) return false;
}
// Dropdown filters
if (selectedProfession !== 'All Professions' && (!worker.main_profession || worker.main_profession.toLowerCase() !== selectedProfession.toLowerCase())) return false;
if (selectedNationalities.length > 0 && (!worker.nationality || !selectedNationalities.map(n => n.toLowerCase()).includes(worker.nationality.toLowerCase()))) return false;
if (selectedGender !== 'All Genders' && worker.gender && worker.gender.toLowerCase() !== selectedGender.toLowerCase()) return false;
if (selectedExperience !== 'All Experience' && worker.experience !== selectedExperience) return false;
if (selectedReligion !== 'All Religions' && worker.religion !== selectedReligion) return false;
if (maxSalary < 5000 && worker.salary > maxSalary) return false;
// Multi-select filters
if (selectedLanguages.length > 0) {
const hasLang = worker.languages?.some(l => selectedLanguages.includes(l));
if (!hasLang) return false;
}
if (selectedSkills.length > 0) {
const hasSkill = worker.skills?.some(s => selectedSkills.includes(s.toLowerCase()));
if (!hasSkill) return false;
}
if (selectedVisaStatuses.length > 0) {
if (!selectedVisaStatuses.includes(worker.visa_status)) return false;
}
if (selectedWorkTypes.length > 0 && !selectedWorkTypes.includes(worker.preferred_job_type)) return false;
// Preferred Location
if (filterLocation !== 'All' && filterLocation.trim() !== '') {
const locKey = filterLocation.toLowerCase();
const locationMapping = {
'dubai': ['dubai', 'marina', 'barsha', 'nahda', 'jumeirah', 'deira', 'downtown', 'silicon', 'sports', 'motor', 'jlt', 'jbr', 'meydan', 'ranches', 'bay', 'mirdif', 'quoz'],
'abu dhabi': ['abu dhabi', 'yas', 'khalifa', 'reem', 'saadiyat', 'raha', 'mussafah', 'zahiyah', 'karamah'],
'oman': ['oman', 'muscat', 'salalah', 'sohar', 'nizwa', 'sur', 'ibri', 'rustaq']
};
const allowedKeywords = locationMapping[locKey] || [locKey];
if (!worker.preferred_location) return false;
const wLoc = worker.preferred_location.toLowerCase();
const matched = allowedKeywords.some(keyword => wLoc.includes(keyword));
if (!matched) return false;
}
// Job Type
if (filterJobType !== 'All') {
if (!worker.preferred_job_type || worker.preferred_job_type.toLowerCase() !== filterJobType.toLowerCase()) return false;
}
// Accommodation
if (filterAccommodation !== 'All') {
if (!worker.live_in_out || worker.live_in_out.toLowerCase() !== filterAccommodation.toLowerCase()) return false;
}
// In/Out Country
if (filterInCountry !== 'All') {
const wantInCountry = filterInCountry === 'In Country';
const wVal = worker.in_country;
if ((wVal === true || wVal === '1' || wVal === 1) !== wantInCountry) return false;
}
// Visa Type (if in country)
if (filterInCountry === 'In Country' && filterVisaType !== 'All') {
if (!worker.visa_status || !worker.visa_status.toLowerCase().includes(filterVisaType.toLowerCase())) return false;
}
return true;
});
// Sorting
if (sortBy === 'salary_asc') {
list.sort((a, b) => a.salary - b.salary);
} else if (sortBy === 'salary_desc') {
list.sort((a, b) => b.salary - a.salary);
} else if (sortBy === 'rating') {
list.sort((a, b) => b.rating - a.rating);
} else if (sortBy === 'experience') {
list.sort((a, b) => b.age - a.age); // approximate experience by age
}
return list;
}, [
workers,
searchQuery,
selectedProfession,
selectedNationalities,
selectedGender,
selectedExperience,
selectedReligion,
maxSalary,
selectedLanguages,
selectedVisaStatuses,
selectedSkills,
selectedWorkTypes,
sortBy,
filterLocation,
filterJobType,
filterAccommodation,
filterInCountry,
filterVisaType
]);
const comparedWorkers = useMemo(() => {
return workers.filter(w => comparisonIds.includes(w.id));
}, [workers, comparisonIds]);
return (
<EmployerLayout title={t('my_shortlist', 'My Shortlist')}>
<Head title={`${t('shortlist', 'Shortlist')} - ${t('employer_portal', 'Sponsor Portal')}`} />
<div className="space-y-6 select-none pb-16">
<div className="flex items-center justify-between">
<p className="text-xs text-slate-500 font-medium">
{t('candidates_saved', 'You have {count} candidates saved for review').split('{count}')[0]}
<span className="text-slate-900 font-bold">{workers.length}</span>
{t('candidates_saved', 'You have {count} candidates saved for review').split('{count}')[1]}
</p>
<Link
href="/employer/workers"
className="text-xs font-bold text-[#185FA5] hover:underline flex items-center space-x-1"
>
<span>{t('browse_more', 'Browse more workers')}</span>
</Link>
</div>
{/* ── Filter Drawer ── */}
<FilterDrawer
open={drawerOpen}
onClose={() => setDrawerOpen(false)}
onReset={resetFilters}
activeCount={activeFilterCount}
title="Filter Saved Workers"
>
{/* Profession */}
{filtersMetadata.professions && (
<FilterSection label="Profession">
<FilterSelect
id="filter_main_profession"
value={selectedProfession}
onChange={e => setSelectedProfession(e.target.value)}
>
{filtersMetadata.professions.map(prof => (
<option key={prof} value={prof}>
{prof === 'All Professions' ? 'All Professions' : prof}
</option>
))}
</FilterSelect>
</FilterSection>
)}
{/* Preferred Location */}
<FilterSection label="Preferred Location">
<FilterSelect
id="filter_preferred_location"
value={filterLocation}
onChange={e => setFilterLocation(e.target.value)}
>
<option value="All">All Locations</option>
<option value="Dubai">Dubai</option>
<option value="Abu Dhabi">Abu Dhabi</option>
<option value="Oman">Oman</option>
</FilterSelect>
</FilterSection>
{/* Job Type */}
<FilterSection label="Job Type">
<FilterChips
options={[
{ value: 'All', label: 'All' },
{ value: 'full-time', label: 'Full-time' },
{ value: 'part-time', label: 'Part-time' },
]}
selected={filterJobType}
onChange={setFilterJobType}
/>
</FilterSection>
{/* Accommodation */}
<FilterSection label="Accommodation Type">
<FilterChips
options={[
{ value: 'All', label: 'All' },
{ value: 'live_in', label: 'Live-in' },
{ value: 'live_out', label: 'Live-out' },
]}
selected={filterAccommodation}
onChange={setFilterAccommodation}
/>
</FilterSection>
{/* Nationality */}
{filtersMetadata.nationalities?.length > 1 && (
<FilterSection label="Nationality">
<FilterCheckboxList
items={filtersMetadata.nationalities.filter(n => n !== 'All Nationalities')}
selected={selectedNationalities}
onToggle={nat => toggleMultiSelect(nat, setSelectedNationalities)}
/>
</FilterSection>
)}
{/* Gender */}
<FilterSection label="Gender">
<FilterChips
options={[
{ value: 'All Genders', label: 'All' },
{ value: 'male', label: 'Male' },
{ value: 'female', label: 'Female' },
]}
selected={selectedGender}
onChange={setSelectedGender}
/>
</FilterSection>
{/* Country Status */}
<FilterSection label="Country Status">
<FilterChips
options={[
{ value: 'All', label: 'All' },
{ value: 'In Country', label: 'In Country' },
{ value: 'Out of Country', label: 'Out of Country' },
]}
selected={filterInCountry}
onChange={val => {
setFilterInCountry(val);
if (val !== 'In Country') setFilterVisaType('All');
}}
/>
</FilterSection>
{/* Visa Type */}
<FilterSection label="Visa Type">
<div className={filterInCountry !== 'In Country' ? 'opacity-40 pointer-events-none' : ''}>
<FilterSelect
id="filter_visa_status"
value={filterVisaType}
onChange={e => setFilterVisaType(e.target.value)}
disabled={filterInCountry !== 'In Country'}
>
<option value="All">All Visa Types</option>
<option value="Residence Visa">Residence Visa</option>
<option value="Tourist Visa">Tourist Visa</option>
<option value="Employment Visa">Employment Visa</option>
</FilterSelect>
{filterInCountry !== 'In Country' && (
<p className="text-[11px] text-slate-400 mt-1.5 font-medium">Select "In Country" to enable this filter</p>
)}
</div>
</FilterSection>
{/* Skills */}
{filtersMetadata.skills?.length > 1 && (
<FilterSection label="Skills">
<FilterCheckboxList
items={filtersMetadata.skills.slice(1).map(s => s.toLowerCase())}
selected={selectedSkills}
onToggle={skill => toggleMultiSelect(skill, setSelectedSkills)}
/>
</FilterSection>
)}
{/* Languages */}
{filtersMetadata.languages?.length > 1 && (
<FilterSection label="Languages">
<FilterCheckboxList
items={filtersMetadata.languages.slice(1)}
selected={selectedLanguages}
onToggle={lang => toggleMultiSelect(lang, setSelectedLanguages)}
/>
</FilterSection>
)}
{/* Salary Range */}
<FilterSection label={maxSalary === 5000 ? "Max Salary — Any" : `Max Salary — ${maxSalary} AED`}>
<input
type="range"
min="500"
max="5000"
step="100"
value={maxSalary}
onChange={e => setMaxSalary(Number(e.target.value))}
className="w-full accent-[#185FA5]"
/>
<div className="flex justify-between text-[10px] font-bold text-slate-400 mt-1">
<span>500 AED</span>
<span>5,000 AED</span>
</div>
</FilterSection>
</FilterDrawer>
{/* Filter and Search Panel */}
<div className="bg-white p-5 rounded-2xl border border-slate-200 shadow-sm">
<div className="flex flex-col sm:flex-row sm:items-center gap-3">
{/* Search */}
<div className="relative flex-1">
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400" />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={t('search_by_name_skills_bio', 'Search by name, skills, bio...')}
className="w-full pl-11 pr-4 py-3 rounded-xl border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5]"
/>
</div>
<div className="flex items-center gap-3">
{/* Sort */}
<div className="flex items-center bg-slate-50 rounded-xl border border-slate-200 px-3 py-2 text-xs font-bold text-slate-700">
<ArrowUpDown className="w-4 h-4 text-slate-400 mr-2" />
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="bg-transparent focus:outline-none cursor-pointer"
>
<option value="default">{t('default_match', 'Default Match')}</option>
<option value="salary_asc">{t('salary_low_to_high', 'Salary: Low to High')}</option>
<option value="salary_desc">{t('salary_high_to_low', 'Salary: High to Low')}</option>
<option value="rating">{t('rating_high_to_low', 'Rating: High to Low')}</option>
<option value="experience">{t('experience_level', 'Experience Level')}</option>
</select>
</div>
{/* Reset */}
<button
type="button"
onClick={resetFilters}
className="flex items-center justify-center space-x-1.5 px-3 py-2.5 rounded-xl border border-slate-200 hover:bg-slate-50 text-xs font-semibold text-slate-600 transition-colors"
title="Reset all filters"
>
<RotateCcw className="w-4 h-4 text-slate-400" />
</button>
{/* Filter Drawer button */}
<button
type="button"
onClick={() => setDrawerOpen(true)}
className={`relative flex items-center space-x-2 px-4 py-2.5 rounded-xl border transition-all text-xs font-bold ${
activeFilterCount > 0
? 'bg-[#185FA5] text-white border-[#185FA5] shadow-md shadow-blue-200'
: 'border-slate-200 hover:bg-slate-50 text-slate-600 bg-white'
}`}
>
<SlidersHorizontal className="w-4 h-4" />
<span>{t('filters', 'Filters')}</span>
{activeFilterCount > 0 && (
<span className="bg-white/25 text-white text-[10px] font-black px-1.5 py-0.5 rounded-full leading-none">
{activeFilterCount}
</span>
)}
</button>
</div>
</div>
{/* Active filter tags */}
{activeFilterTags.length > 0 && (
<div className="mt-3 pt-3 border-t border-slate-100 flex flex-wrap gap-2 items-center">
<span className="text-[10px] font-black text-slate-400 uppercase tracking-widest mr-1">Active:</span>
{activeFilterTags.map(tag => (
<button
key={tag.key}
onClick={tag.clear}
className="inline-flex items-center space-x-1.5 px-2.5 py-1 bg-[#185FA5]/10 text-[#185FA5] text-[11px] font-bold rounded-full border border-[#185FA5]/20 hover:bg-[#185FA5]/20 transition-all"
>
<span>{tag.label}</span>
<span className="text-[#185FA5]/60 font-black">×</span>
</button>
))}
<button onClick={resetFilters} className="text-[11px] font-bold text-rose-500 hover:text-rose-700 ml-1 transition-colors">Clear all</button>
</div>
)}
</div>
{/* Listing metadata bar */}
<div className="flex items-center justify-between px-1">
<div className="text-xs font-semibold text-slate-500">
{t('found_workers_matching', 'Found {count} workers matching your preferences')
.split('{count}')[0]}
<span className="text-slate-900 font-bold">{filteredWorkers.length}</span>
{t('found_workers_matching', 'Found {count} workers matching your preferences')
.split('{count}')[1] || ''}
</div>
<div className="flex items-center space-x-2 text-xs font-medium text-slate-600">
<CheckCircle className="w-3.5 h-3.5 text-emerald-600" />
<span>{t('direct_sponsoring_active', 'Direct Sponsorship Active')}</span>
</div>
</div>
{workers.length > 0 ? (
filteredWorkers.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredWorkers.map((worker) => {
const isComparing = comparisonIds.includes(worker.id);
return (
<div key={worker.id} className="bg-white rounded-2xl border border-slate-200 shadow-sm hover:shadow-md transition-all flex flex-col justify-between overflow-hidden group relative">
{/* Card Header with Photo Option and Availability indicator */}
<div className="bg-slate-50/40 p-4 sm:p-5 pb-3 border-b border-slate-100 flex items-start justify-between relative">
<div className="flex items-center space-x-3 min-w-0 flex-1">
{/* Photo (optional) container */}
<div className="w-12 h-12 rounded-xl bg-blue-50 text-[#185FA5] flex items-center justify-center font-bold text-lg border border-blue-100 shadow-inner flex-shrink-0 overflow-hidden relative">
{worker.photo ? (
<img src={worker.photo} alt={worker.name} className="w-full h-full object-cover" />
) : (
<User className="w-5 h-5 text-slate-400" />
)}
</div>
<div className="min-w-0 flex-1 space-y-1">
<div className="font-extrabold text-sm sm:text-base text-slate-900 group-hover:text-[#185FA5] transition-colors truncate flex items-center gap-1.5 flex-wrap">
<span>{worker.name}</span>
{worker.main_profession && (
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-[8px] font-black uppercase tracking-wider bg-blue-50 text-[#185FA5] border border-blue-100">
{worker.main_profession}
</span>
)}
</div>
{/* Passport Status Verification Badge & Visa Status */}
<div className="flex flex-wrap gap-1">
{worker.visa_status && (worker.visa_status.toLowerCase().includes('tourist') || worker.visa_status.toLowerCase().includes('visit')) ? (
<div className="flex items-center space-x-0.5 text-[8px] font-black uppercase text-amber-700 bg-amber-50 px-1.5 py-0.5 rounded border border-amber-200">
<AlertTriangle className="w-2.5 h-2.5 text-amber-600 flex-shrink-0 animate-bounce" />
<span>{worker.visa_status}</span>
</div>
) : worker.visa_status && worker.visa_status.toLowerCase().includes('cancelled') ? (
<div className="flex items-center space-x-0.5 text-[8px] font-black uppercase text-rose-700 bg-rose-50 px-1.5 py-0.5 rounded border border-rose-200 animate-pulse">
<AlertTriangle className="w-2.5 h-2.5 text-rose-600 flex-shrink-0" />
<span>{worker.visa_status}</span>
</div>
) : (
<div className="flex items-center space-x-0.5 text-[8px] font-black uppercase text-blue-700 bg-blue-50 px-1.5 py-0.5 rounded border border-blue-100">
<span>{worker.visa_status}</span>
</div>
)}
</div>
<div className="flex items-center space-x-1 text-[10px] text-slate-500 font-bold">
<Globe2 className="w-3 h-3 text-slate-400" />
<span>{worker.nationality}</span>
<span></span>
<span>{worker.age} yrs</span>
</div>
</div>
</div>
{/* Top Right Actions & Salary */}
<div className="flex flex-col items-end justify-between self-stretch flex-shrink-0 ml-3">
<button
type="button"
onClick={() => removeWorker(worker.id)}
className="p-1.5 bg-rose-50 hover:bg-rose-100 border border-rose-100 text-rose-600 rounded-lg transition-colors"
title={t('remove_from_shortlist', 'Remove from shortlist')}
>
<Trash2 className="w-3.5 h-3.5" />
</button>
<div className="text-right mt-auto pt-2">
<div className="text-[#185FA5] text-base font-extrabold tracking-tight">
{worker.salary} <span className="text-[10px] font-bold text-slate-500">AED</span>
</div>
<div className="text-[9px] text-slate-400 font-bold mt-[-2px]">/mo</div>
</div>
</div>
</div>
{/* Card Body */}
<div className="p-4 sm:p-5 pt-3 space-y-3 flex-1 flex flex-col justify-between">
<div className="space-y-3">
{/* Details Table */}
<div className="grid grid-cols-2 gap-2 p-2.5 bg-slate-50/70 border border-slate-100/50 rounded-xl text-[10px] text-slate-600 font-bold">
<div className="flex items-center space-x-1.5 truncate">
<Briefcase className="w-3 h-3 text-slate-400 flex-shrink-0" />
<span className="truncate">Exp: {worker.experience}</span>
</div>
<div className="flex items-center space-x-1.5 truncate">
<Sparkles className="w-3 h-3 text-slate-400 flex-shrink-0" />
<span className="truncate uppercase text-[9px]">{worker.preferred_job_type}</span>
</div>
<div className="flex items-center space-x-1.5 truncate">
<MapPin className="w-3 h-3 text-[#185FA5] flex-shrink-0" />
<span className="truncate text-[#185FA5]">{worker.preferred_location || 'Not Specified'}</span>
</div>
<div className="flex items-center space-x-1.5 truncate">
<Star className="w-3 h-3 text-amber-500 flex-shrink-0 fill-amber-500" />
<span className="truncate">{worker.rating || '4.5'} ({worker.reviews_count || '12'})</span>
</div>
</div>
{/* Core exact platform Skills List */}
<div className="space-y-0.5">
<div className="text-[8px] font-black text-slate-400 uppercase tracking-widest">{t('platform_skills', 'Platform Skills')}</div>
<div className="flex flex-wrap gap-1">
{worker.skills?.slice(0, 3).map(skill => (
<span key={skill} className="px-2 py-0.5 bg-blue-50 border border-blue-100 text-[#185FA5] text-[8px] font-black rounded-md uppercase tracking-wider">
{skill}
</span>
))}
{worker.skills?.length > 3 && (
<span className="px-1.5 py-0.5 bg-slate-50 border border-slate-200 text-slate-500 text-[8px] font-black rounded-md">
+{worker.skills.length - 3} More
</span>
)}
</div>
</div>
{/* Languages Badges with Visual Flags */}
<div className="space-y-0.5">
<div className="text-[8px] font-black text-slate-400 uppercase tracking-widest">{t('languages_spoken', 'Languages Spoken')}</div>
<div className="flex flex-wrap gap-1">
{worker.languages?.map(lang => (
<span key={lang} className="text-[8px] bg-slate-50 text-slate-600 px-2 py-0.5 rounded font-bold uppercase flex items-center space-x-1 border border-slate-200">
<span>{getLanguageFlag(lang)}</span>
<span>{lang}</span>
</span>
))}
</div>
</div>
</div>
{/* Actions block */}
<div className="pt-3 border-t border-slate-100 space-y-2">
<div className="grid grid-cols-2 gap-2">
<button
type="button"
onClick={() => setPreviewWorker(worker)}
className="w-full bg-slate-50 border border-slate-200 hover:bg-slate-100 text-slate-700 rounded-xl h-9 font-bold text-xs flex items-center justify-center transition-colors space-x-1"
>
<Eye className="w-3.5 h-3.5" />
<span>{t('quick_preview', 'Quick Preview')}</span>
</button>
<button
type="button"
onClick={() => handleToggleComparison(worker.id)}
className={`w-full rounded-xl h-9 font-bold text-xs flex items-center justify-center transition-colors border ${
isComparing
? 'bg-purple-600 border-purple-600 text-white hover:bg-purple-700'
: 'bg-white border-slate-200 hover:bg-slate-50 text-slate-700'
}`}
>
{isComparing ? t('comparing_active', 'Comparing ✓') : t('compare_candidate', 'Compare')}
</button>
</div>
<div className="grid grid-cols-2 gap-2">
<Link
href={`/employer/workers/${worker.id}`}
className="w-full bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-xl h-9 font-bold text-xs flex items-center justify-center transition-colors"
>
{t('open_profile', 'Open Profile')}
</Link>
<Link
href={`/employer/messages/${worker.id}`}
className="w-full bg-[#185FA5] hover:bg-[#144f8a] text-white rounded-xl h-9 font-bold text-xs flex items-center justify-center space-x-1.5 transition-colors shadow-xs"
>
<MessageSquare className="w-4 h-4" />
<span>{t('message', 'Message')}</span>
</Link>
</div>
</div>
</div>
</div>
);
})}
</div>
) : (
<div className="text-center py-16 bg-white rounded-2xl border border-slate-200 shadow-sm space-y-3 w-full col-span-full">
<SlidersHorizontal className="w-12 h-12 text-slate-300 mx-auto" />
<div className="text-base font-bold text-slate-800">{t('no_matching_results', 'No matching saved workers')}</div>
<p className="text-xs text-slate-500 max-w-sm mx-auto">
{t('no_matching_results_desc', 'Try clearing or modifying your filters to find your saved candidates.')}
</p>
<button
onClick={resetFilters}
className="inline-block mt-2 bg-[#185FA5] text-white px-6 py-2.5 rounded-xl text-xs font-bold shadow-sm hover:bg-[#144f8a] transition-colors"
>
{t('clear_all_filters', 'Clear All Filters')}
</button>
</div>
)
) : (
<div className="text-center py-16 bg-white rounded-2xl border border-slate-200 shadow-sm space-y-3 w-full col-span-full">
<SlidersHorizontal className="w-12 h-12 text-slate-300 mx-auto" />
<div className="text-base font-bold text-slate-800">{t('shortlist_empty', 'Your shortlist is empty')}</div>
<p className="text-xs text-slate-500 max-w-sm mx-auto">
{t('shortlist_empty_desc', 'Explore candidate profiles and add them to your saved shortlist.')}
</p>
<Link
href="/employer/workers"
className="inline-block mt-2 bg-[#185FA5] text-white px-6 py-2.5 rounded-xl text-xs font-bold shadow-sm hover:bg-[#144f8a] transition-colors"
>
{t('find_workers_now', 'Find Workers Now')}
</Link>
</div>
)}
{/* Worker Comparison Drawer */}
{comparisonIds.length > 0 && (
<div className="fixed bottom-0 left-0 right-0 bg-white border-t-2 border-[#185FA5] shadow-2xl p-6 z-50 animate-slide-up flex flex-col space-y-4 max-w-7xl mx-auto rounded-t-3xl">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<span className="w-2.5 h-2.5 rounded-full bg-[#185FA5] animate-ping" />
<h4 className="font-extrabold text-sm text-slate-900">{t('comparing_workers', 'Comparing Workers')} ({comparedWorkers.length} of 3)</h4>
</div>
<button
onClick={() => setComparisonIds([])}
className="text-xs font-bold text-rose-600 hover:underline flex items-center space-x-1"
>
<X className="w-4 h-4" />
<span>{t('clear_all', 'Clear all')}</span>
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{comparedWorkers.map(w => (
<div key={w.id} className="bg-slate-50 border border-slate-200 rounded-2xl p-4 space-y-3 relative">
<button
onClick={() => handleToggleComparison(w.id)}
className="absolute top-2 right-2 text-slate-400 hover:text-slate-600"
>
<X className="w-4 h-4" />
</button>
<div className="flex items-center space-x-2">
<div className="w-8 h-8 rounded-full bg-blue-100 text-[#185FA5] font-black text-xs flex items-center justify-center overflow-hidden">
{w.photo ? (
<img src={w.photo} alt={w.name} className="w-full h-full object-cover" />
) : (
<span>{w.name.charAt(0)}</span>
)}
</div>
<div>
<div className="font-bold text-xs text-slate-900 truncate max-w-[120px]">{w.name}</div>
<div className="text-[10px] text-slate-500 font-bold">{w.nationality}</div>
</div>
</div>
<div className="grid grid-cols-2 gap-2 text-[10px] bg-white p-2.5 rounded-xl border border-slate-100 font-bold text-slate-600">
<div>Salary: <span className="text-slate-800">{w.salary} AED</span></div>
<div>Age: <span className="text-slate-800">{w.age} yrs</span></div>
<div>Job Type: <span className="text-slate-800 uppercase">{w.preferred_job_type}</span></div>
<div className="truncate">Location: <span className="text-slate-800 font-medium" title={w.preferred_location}>{w.preferred_location || 'Not Specified'}</span></div>
</div>
{/* Skills Display in Comparison */}
<div className="space-y-1">
<div className="text-[8px] font-black text-slate-400 uppercase tracking-wider">Skills</div>
<div className="flex flex-wrap gap-1">
{w.skills?.map(skill => (
<span key={skill} className="px-1.5 py-0.5 bg-blue-50 border border-blue-100 text-[#185FA5] text-[8px] font-black rounded uppercase tracking-wider">
{skill}
</span>
))}
</div>
</div>
</div>
))}
</div>
</div>
)}
{/* Quick Preview Modal overlay */}
{previewWorker && (
<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-lg border border-slate-200 shadow-2xl p-6 relative animate-zoom-in space-y-6">
<button
onClick={() => setPreviewWorker(null)}
className="absolute top-4 right-4 p-2 bg-slate-50 hover:bg-slate-100 rounded-full text-slate-400 hover:text-slate-600 transition-colors"
>
<X className="w-5 h-5" />
</button>
<div className="flex items-center space-x-4">
<div className="w-16 h-16 bg-blue-50 text-[#185FA5] rounded-2xl border border-blue-100 font-black text-2xl flex items-center justify-center shadow-inner overflow-hidden flex-shrink-0">
{previewWorker.photo ? (
<img src={previewWorker.photo} alt={previewWorker.name} className="w-full h-full object-cover" />
) : (
<User className="w-8 h-8 text-slate-400" />
)}
</div>
<div>
<div className="flex items-center space-x-1">
<h4 className="font-extrabold text-lg text-slate-900">{previewWorker.name}</h4>
</div>
{previewWorker.visa_expiry_date ? (
<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 ${
previewWorker.document_expiry_days !== null && previewWorker.document_expiry_days <= 30
? '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'
}`}>
<Calendar className="w-3.5 h-3.5 flex-shrink-0" />
<span>Visa Exp: {previewWorker.visa_expiry_date}</span>
</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>
</div>
<div className="grid grid-cols-2 gap-4 text-xs font-bold text-slate-600">
<div className="p-3 bg-slate-50/50 rounded-xl">
<div className="text-[10px] text-slate-400 uppercase tracking-widest mb-0.5">{t('expectations', 'Expectations')}</div>
<div className="text-slate-800">{previewWorker.salary} AED / mo</div>
</div>
<div className="p-3 bg-slate-50/50 rounded-xl">
<div className="text-[10px] text-slate-400 uppercase tracking-widest mb-0.5">{t('job_type', 'Job Type')}</div>
<div className="text-slate-800 uppercase text-[10px] mt-1">{previewWorker.preferred_job_type}</div>
</div>
<div className="p-3 bg-slate-50/50 rounded-xl">
<div className="text-[10px] text-slate-400 uppercase tracking-widest mb-0.5">{t('gender', 'Gender')}</div>
<div className="text-slate-800 capitalize mt-1">{previewWorker.gender || 'Female'}</div>
</div>
<div className="p-3 bg-slate-50/50 rounded-xl">
<div className="text-[10px] text-slate-400 uppercase tracking-widest mb-0.5">{t('visa_status', 'Visa Status')}</div>
<div className="text-slate-800 capitalize mt-1">{previewWorker.visa_status || 'Residence Visa'}</div>
</div>
<div className="p-3 bg-slate-50/50 rounded-xl col-span-2">
<div className="text-[10px] text-slate-400 uppercase tracking-widest mb-0.5">{t('preferred_location', 'Preferred Location')}</div>
<div className="text-slate-800 flex items-center space-x-1.5 mt-1">
<MapPin className="w-3.5 h-3.5 text-[#185FA5]" />
<span>{previewWorker.preferred_location || 'Not Specified'}</span>
</div>
</div>
<div className="p-3 bg-slate-50/50 rounded-xl col-span-2">
<div className="text-[10px] text-slate-400 uppercase tracking-widest mb-0.5">{t('languages', 'Languages')}</div>
<div className="flex flex-wrap gap-1.5 mt-1">
{previewWorker.languages?.map(lang => (
<span key={lang} className="text-[9px] bg-white border border-slate-200 text-slate-700 px-2 py-0.5 rounded font-bold flex items-center space-x-1">
<span>{getLanguageFlag(lang)}</span>
<span>{lang}</span>
</span>
))}
</div>
</div>
</div>
{/* Core Platform Skills inside Quick Preview */}
<div className="space-y-1 pt-1">
<div className="text-[9px] font-black text-slate-400 uppercase tracking-widest">{t('platform_skills', 'Platform Skills')}</div>
<div className="flex flex-wrap gap-1">
{previewWorker.skills?.map(skill => (
<span key={skill} className="px-2.5 py-0.5 bg-blue-50 border border-blue-100 text-[#185FA5] text-[9px] font-black rounded-lg uppercase tracking-wider">
{skill}
</span>
))}
</div>
</div>
<div className="flex items-center space-x-3 pt-2">
<button
onClick={() => removeWorker(previewWorker.id)}
className="flex-1 border border-rose-200 hover:bg-rose-50 text-rose-600 rounded-xl h-11 text-xs font-bold flex items-center justify-center space-x-2"
>
<Trash2 className="w-4 h-4" />
<span>{t('remove_shortlist', 'Remove Shortlist')}</span>
</button>
<Link
href={`/employer/workers/${previewWorker.id}`}
className="flex-1 bg-[#185FA5] hover:bg-[#144f8a] text-white rounded-xl h-11 text-xs font-black flex items-center justify-center shadow-xs"
>
{t('view_full_details', 'View Full Details')}
</Link>
</div>
</div>
</div>
)}
</div>
</EmployerLayout>
);
}