1112 lines
66 KiB
JavaScript
1112 lines
66 KiB
JavaScript
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>
|
||
);
|
||
}
|