1112 lines
66 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, useEffect } from 'react';
import { Head, Link, router } from '@inertiajs/react';
import EmployerLayout from '../../../Layouts/EmployerLayout';
import { useTranslation } from '../../../lib/LanguageContext';
import {
Search,
SlidersHorizontal,
CheckCircle2,
MapPin,
Briefcase,
DollarSign,
Calendar,
Bookmark,
RotateCcw,
HeartHandshake,
Globe2,
Sparkles,
Filter,
ArrowUpDown,
Check,
X,
Eye,
ChevronDown,
Layers,
AlertTriangle,
Star,
CheckCircle,
ShieldCheck,
User
} 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 Index({ initialWorkers = [], initialShortlistedIds = [], filtersMetadata = {} }) {
const { t } = useTranslation();
// 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 [showAdvancedFilters, setShowAdvancedFilters] = useState(false);
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');
// UI Helpers
const [isLangOpen, setIsLangOpen] = useState(false);
const [isVisaOpen, setIsVisaOpen] = useState(false);
const [isSkillsOpen, setIsSkillsOpen] = useState(false);
const [isWorkTypeOpen, setIsWorkTypeOpen] = useState(false);
// Pagination
const [visibleCount, setVisibleCount] = useState(6);
// Shortlist / Favorites
const [shortlistedIds, setShortlistedIds] = useState(initialShortlistedIds || []);
// Saved Presets
const [savedPresets, setSavedPresets] = useState([
{ id: 1, name: 'Live-in Baby Care', workType: 'Live-in', minSalary: 1200 },
{ id: 2, name: 'Experienced Driver', experience: '5+ Years' }
]);
const [presetNameInput, setPresetNameInput] = useState('');
const [showPresetModal, setShowPresetModal] = useState(false);
// Drawer
const [drawerOpen, setDrawerOpen] = useState(false);
// Worker Comparison & Quick Preview
const [comparisonIds, setComparisonIds] = useState([]);
const [previewWorker, setPreviewWorker] = useState(null);
// Retrieve active preset query if present
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const cat = params.get('category');
const nat = params.get('nationality');
const sal = params.get('max_salary');
const loc = params.get('preferred_location');
const job = params.get('job_type') || params.get('preferred_job_type');
const acc = params.get('live_in_out') || params.get('accommodation_type') || params.get('accomadation_type');
const inCountryVal = params.get('in_country');
const outCountryVal = params.get('out_country');
const visa = params.get('visa_status') || params.get('next_visa_type') || params.get('visa_type');
const gender = params.get('gender');
const prof = params.get('main_profession') || params.get('profession');
let hasAdvanced = false;
if (prof) {
setSelectedProfession(prof);
}
if (nat) {
setSelectedNationalities(nat.split(',').map(x => x.trim()).filter(Boolean));
}
if (gender) {
setSelectedGender(gender.toLowerCase() === 'male' ? 'Male' : (gender.toLowerCase() === 'female' ? 'Female' : 'All Genders'));
}
if (sal) setMaxSalary(Number(sal));
if (loc) {
setFilterLocation(loc);
hasAdvanced = true;
}
if (job) {
setFilterJobType(job);
hasAdvanced = true;
}
if (acc) {
setFilterAccommodation(acc);
hasAdvanced = true;
}
if (inCountryVal) {
const isTrue = inCountryVal === 'true' || inCountryVal === '1' || inCountryVal === 'yes' || inCountryVal === 'in' || inCountryVal === 'In Country';
setFilterInCountry(isTrue ? 'In Country' : 'Out of Country');
hasAdvanced = true;
} else if (outCountryVal) {
const isTrue = outCountryVal === 'true' || outCountryVal === '1' || outCountryVal === 'yes' || outCountryVal === 'out' || outCountryVal === 'Out of Country';
setFilterInCountry(isTrue ? 'Out of Country' : 'In Country');
hasAdvanced = true;
}
if (visa) {
setFilterVisaType(visa);
hasAdvanced = true;
}
if (hasAdvanced) {
setShowAdvancedFilters(true);
}
}, []);
const toggleShortlist = (id) => {
if (shortlistedIds.includes(id)) {
setShortlistedIds(shortlistedIds.filter(i => i !== id));
} else {
setShortlistedIds([...shortlistedIds, id]);
}
router.post('/employer/shortlist/toggle', { worker_id: id }, {
preserveScroll: true,
onSuccess: (page) => {
if (page.props.initialShortlistedIds) {
setShortlistedIds(page.props.initialShortlistedIds);
}
}
});
};
const toggleMultiSelect = (item, setSelectedList) => {
setSelectedList(prev =>
prev.includes(item) ? prev.filter(x => x !== item) : [...prev, item]
);
};
const applyPreset = (preset) => {
resetFilters();
if (preset.workType) setSelectedWorkTypes([preset.workType]);
if (preset.experience) setSelectedExperience(preset.experience);
};
const saveCurrentPreset = (e) => {
e.preventDefault();
if (!presetNameInput.trim()) return;
const newPreset = {
id: Date.now(),
name: presetNameInput,
workType: selectedWorkTypes.length > 0 ? selectedWorkTypes[0] : null,
experience: selectedExperience !== 'All Experience' ? selectedExperience : null,
};
setSavedPresets([...savedPresets, newPreset]);
setPresetNameInput('');
setShowPresetModal(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');
setVisibleCount(6);
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]);
// Filter and Sort Worker List
const filteredWorkers = useMemo(() => {
let workers = initialWorkers.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') {
workers.sort((a, b) => a.salary - b.salary);
} else if (sortBy === 'salary_desc') {
workers.sort((a, b) => b.salary - a.salary);
} else if (sortBy === 'rating') {
workers.sort((a, b) => b.rating - a.rating);
} else if (sortBy === 'experience') {
workers.sort((a, b) => {
const parseAge = (val) => {
if (!val) return 0;
if (typeof val === 'number') return val;
const num = parseInt(val, 10);
return isNaN(num) ? 0 : num;
};
return parseAge(b.age) - parseAge(a.age);
});
}
return workers;
}, [
initialWorkers,
searchQuery,
selectedProfession,
selectedNationalities,
selectedGender,
selectedExperience,
selectedReligion,
maxSalary,
selectedLanguages,
selectedVisaStatuses,
selectedSkills,
selectedWorkTypes,
sortBy,
filterLocation,
filterJobType,
filterAccommodation,
filterInCountry,
filterVisaType
]);
const paginatedWorkers = useMemo(() => {
return filteredWorkers.slice(0, visibleCount);
}, [filteredWorkers, visibleCount]);
const handleToggleComparison = (id) => {
if (comparisonIds.includes(id)) {
setComparisonIds(comparisonIds.filter(i => i !== id));
} else {
if (comparisonIds.length >= 3) {
alert('You can compare a maximum of 3 candidates.');
return;
}
setComparisonIds([...comparisonIds, id]);
}
};
const comparedWorkers = useMemo(() => {
return initialWorkers.filter(w => comparisonIds.includes(w.id));
}, [initialWorkers, comparisonIds]);
return (
<EmployerLayout title={t('find_vetted_workers')}>
<Head title={t('find_workers_platform')} />
<div className="space-y-8 select-none pb-16">
{/* 1. Header Banner */}
<div className="bg-white rounded-3xl p-8 border border-slate-200 shadow-sm relative overflow-hidden flex flex-col md:flex-row md:items-center justify-between gap-6">
<div className="absolute top-0 right-0 w-48 h-48 bg-blue-50 rounded-full -mr-16 -mt-16 opacity-40 pointer-events-none" />
<div className="space-y-2 relative z-10">
<div className="flex items-center space-x-2">
<span className="bg-blue-50 text-[#185FA5] px-3 py-1 rounded-xl text-[10px] font-black uppercase tracking-wider border border-blue-100 flex items-center space-x-1">
<HeartHandshake className="w-3.5 h-3.5" />
<span>{t('no_middlemen_platform')}</span>
</span>
</div>
<h2 className="text-2xl font-black text-slate-900 tracking-tight leading-none">{t('find_verified_candidates')}</h2>
<p className="text-slate-500 font-bold text-xs">{t('connect_vetted_workers_desc')}</p>
</div>
</div>
{/* ── Filter Drawer ── */}
<FilterDrawer
open={drawerOpen}
onClose={() => setDrawerOpen(false)}
onReset={resetFilters}
activeCount={activeFilterCount}
title="Filter 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')}
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')}</option>
<option value="salary_asc">{t('salary_low_to_high')}</option>
<option value="salary_desc">{t('salary_high_to_low')}</option>
<option value="rating">{t('rating_high_to_low')}</option>
<option value="experience">{t('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').split('{count}')[0]}
<span className="text-slate-900 font-bold">{filteredWorkers.length}</span>
{t('found_workers_matching').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')}</span>
</div>
</div>
{/* Marketplace Workers Grid */}
{paginatedWorkers.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{paginatedWorkers.map((worker) => {
const isShortlisted = shortlistedIds.includes(worker.id);
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_expiry_date ? (
<div className={`flex items-center space-x-0.5 text-[8px] font-black uppercase px-1.5 py-0.5 rounded border ${
worker.document_expiry_days !== null && worker.document_expiry_days <= 30
? 'text-rose-700 bg-rose-50 border-rose-200 animate-pulse'
: worker.document_expiry_days !== null && worker.document_expiry_days <= 90
? 'text-amber-700 bg-amber-50 border-amber-200'
: 'text-[#185FA5] bg-blue-50 border-blue-100'
}`}>
<Calendar className="w-2.5 h-2.5 flex-shrink-0" />
<span>Visa Exp: {worker.visa_expiry_date}</span>
</div>
) : (
<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">
<Calendar className="w-2.5 h-2.5 flex-shrink-0 text-amber-600" />
<span>Visa Expiry Pending</span>
</div>
)}
{worker.visa_status && (worker.visa_status.toLowerCase().includes('tourist') || worker.visa_status.toLowerCase().includes('visit')) ? (
<div className="flex items-center space-x-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} {t('yrs', '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={() => toggleShortlist(worker.id)}
className={`p-1.5 rounded-lg transition-all border ${
isShortlisted
? 'bg-blue-50 border-blue-200 text-[#185FA5]'
: 'bg-white hover:bg-slate-50 border-slate-200 text-slate-400 hover:text-[#185FA5] shadow-xs'
}`}
>
<Bookmark className={`w-3.5 h-3.5 ${isShortlisted ? 'fill-[#185FA5]' : ''}`} />
</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">{t('aed', 'AED')}</span>
</div>
<div className="text-[9px] text-slate-400 font-bold mt-[-2px]">/{t('mo', '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">{t('experience', '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} ({worker.reviews_count})</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')}</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', 'Comparing ✓') : t('compare_candidate', 'Compare')}
</button>
</div>
<Link
href={`/employer/workers/${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 transition-colors shadow-xs"
>
View Profile
</Link>
</div>
</div>
</div>
);
})}
</div>
) : (
<div className="text-center py-20 bg-white rounded-2xl border border-slate-200 shadow-sm space-y-4">
<SlidersHorizontal className="w-14 h-14 text-slate-300 mx-auto" />
<h4 className="text-base font-bold text-slate-800">{t('no_candidates_matching')}</h4>
<p className="text-xs text-slate-500 max-w-sm mx-auto leading-relaxed">
{t('adjust_filters_desc')}
</p>
<button
type="button"
onClick={resetFilters}
className="bg-[#185FA5] text-white px-5 py-2.5 rounded-xl text-xs font-bold hover:bg-[#144f8a] transition-all shadow-sm"
>
{t('reset_filters')}
</button>
</div>
)}
{/* Load More Pagination */}
{filteredWorkers.length > visibleCount && (
<div className="text-center pt-6">
<button
onClick={() => setVisibleCount(visibleCount + 6)}
className="bg-white border border-slate-200 hover:bg-slate-50 text-[#185FA5] px-8 py-3 rounded-xl text-xs font-bold transition-all shadow-xs"
>
{t('load_more_workers')}
</button>
</div>
)}
{/* 5. Worker Comparison Drawer (Col 12 Fixed bottom) */}
{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_count').replace('{count}', comparedWorkers.length)}
</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>{t('salary', 'Salary')}: <span className="text-slate-800">{w.salary} {t('aed', 'AED')}</span></div>
<div>{t('age', 'Age')}: <span className="text-slate-800">{w.age} {t('yrs', 'yrs')}</span></div>
<div>{t('job_type', 'Job Type')}: <span className="text-slate-800 uppercase">{w.preferred_job_type}</span></div>
<div className="truncate">{t('location', 'Location')}: <span className="text-slate-800 font-medium" title={w.preferred_location}>{w.preferred_location || 'Not Specified'}</span></div>
</div>
<div className="space-y-1">
<div className="text-[8px] font-black text-slate-400 uppercase tracking-wider">{t('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>
)}
{/* 6. 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.main_profession && (
<div className="text-[10px] font-black text-[#185FA5] bg-blue-50 border border-blue-100 px-2 py-0.5 rounded-md inline-block w-fit mb-1">
{previewWorker.main_profession}
</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} {t('aed', 'AED')} / {t('mo', '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_spoken', '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={() => toggleShortlist(previewWorker.id)}
className="flex-1 border border-slate-200 hover:bg-slate-50 rounded-xl h-11 text-xs font-bold text-slate-700 flex items-center justify-center space-x-2"
>
<Bookmark className="w-4 h-4" />
<span>{shortlistedIds.includes(previewWorker.id) ? t('shortlisted', 'Shortlisted ✓') : t('add_to_shortlist', 'Add to 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>
)}
{/* Preset Modal */}
{showPresetModal && (
<div className="fixed inset-0 bg-slate-900/60 backdrop-blur-xs flex items-center justify-center p-4 z-50">
<form onSubmit={saveCurrentPreset} className="bg-white rounded-3xl w-full max-w-sm p-6 relative animate-zoom-in space-y-4 border border-slate-200 shadow-2xl">
<h4 className="font-extrabold text-sm text-slate-900">Name this search preset</h4>
<input
type="text"
placeholder="e.g. Filipino Nannies, Drivers..."
value={presetNameInput}
onChange={(e) => setPresetNameInput(e.target.value)}
className="w-full p-2.5 border border-slate-300 rounded-xl text-xs focus:outline-none focus:border-[#185FA5]"
required
/>
<div className="flex justify-end space-x-2 pt-2 text-xs font-bold">
<button
type="button"
onClick={() => setShowPresetModal(false)}
className="px-4 py-2 border border-slate-200 hover:bg-slate-50 rounded-lg"
>
{t('cancel', 'Cancel')}
</button>
<button
type="submit"
className="px-4 py-2 bg-[#185FA5] text-white hover:bg-[#144f8a] rounded-lg"
>
Save Filter Preset
</button>
</div>
</form>
</div>
)}
</div>
</EmployerLayout>
);
}