342 lines
19 KiB
JavaScript
342 lines
19 KiB
JavaScript
import React, { useState, useMemo } from 'react';
|
|
import { Head, Link } from '@inertiajs/react';
|
|
import EmployerLayout from '../../../Layouts/EmployerLayout';
|
|
import {
|
|
Search,
|
|
SlidersHorizontal,
|
|
CheckCircle2,
|
|
MapPin,
|
|
Briefcase,
|
|
DollarSign,
|
|
Calendar,
|
|
Bookmark,
|
|
RotateCcw,
|
|
HeartHandshake,
|
|
Globe2,
|
|
Sparkles,
|
|
Filter
|
|
} from 'lucide-react';
|
|
|
|
export default function Index({ initialWorkers, filtersMetadata }) {
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [selectedCategory, setSelectedCategory] = useState('All Categories');
|
|
const [selectedNationality, setSelectedNationality] = useState('All Nationalities');
|
|
const [selectedAvailability, setSelectedAvailability] = useState('All Availabilities');
|
|
const [selectedExperience, setSelectedExperience] = useState('All Experience');
|
|
const [selectedReligion, setSelectedReligion] = useState('All Religions');
|
|
const [maxSalary, setMaxSalary] = useState(3000);
|
|
const [shortlistedIds, setShortlistedIds] = useState([101, 103]);
|
|
|
|
const toggleShortlist = (id) => {
|
|
if (shortlistedIds.includes(id)) {
|
|
setShortlistedIds(shortlistedIds.filter(i => i !== id));
|
|
} else {
|
|
setShortlistedIds([...shortlistedIds, id]);
|
|
}
|
|
};
|
|
|
|
const resetFilters = () => {
|
|
setSearchQuery('');
|
|
setSelectedCategory('All Categories');
|
|
setSelectedNationality('All Nationalities');
|
|
setSelectedAvailability('All Availabilities');
|
|
setSelectedExperience('All Experience');
|
|
setSelectedReligion('All Religions');
|
|
setMaxSalary(3000);
|
|
};
|
|
|
|
const filteredWorkers = useMemo(() => {
|
|
return 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);
|
|
const matchSkills = worker.skills.some(s => s.toLowerCase().includes(query));
|
|
if (!matchName && !matchBio && !matchSkills) return false;
|
|
}
|
|
|
|
// Category
|
|
if (selectedCategory !== 'All Categories' && worker.category !== selectedCategory) {
|
|
return false;
|
|
}
|
|
|
|
// Nationality
|
|
if (selectedNationality !== 'All Nationalities' && worker.nationality !== selectedNationality) {
|
|
return false;
|
|
}
|
|
|
|
// Availability
|
|
if (selectedAvailability !== 'All Availabilities' && worker.availability !== selectedAvailability) {
|
|
return false;
|
|
}
|
|
|
|
// Experience
|
|
if (selectedExperience !== 'All Experience' && worker.experience !== selectedExperience) {
|
|
return false;
|
|
}
|
|
|
|
// Religion
|
|
if (selectedReligion !== 'All Religions' && worker.religion !== selectedReligion) {
|
|
return false;
|
|
}
|
|
|
|
// Salary Bracket
|
|
if (worker.salary > maxSalary) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}, [
|
|
initialWorkers,
|
|
searchQuery,
|
|
selectedCategory,
|
|
selectedNationality,
|
|
selectedAvailability,
|
|
selectedExperience,
|
|
selectedReligion,
|
|
maxSalary
|
|
]);
|
|
|
|
return (
|
|
<EmployerLayout title="Find Domestic Workers">
|
|
<Head title="Find Workers - Employer Portal" />
|
|
|
|
<div className="space-y-6 select-none">
|
|
{/* Search & Top Filters Bar */}
|
|
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-4">
|
|
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-4">
|
|
{/* Search Input */}
|
|
<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="Search by name, bio, or skills (e.g., Cooking, Childcare, Arabic)..."
|
|
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>
|
|
|
|
{/* Reset Filters CTA */}
|
|
<button
|
|
type="button"
|
|
onClick={resetFilters}
|
|
className="flex items-center justify-center space-x-2 px-4 py-3 rounded-xl border border-slate-200 hover:bg-slate-50 text-xs font-semibold text-slate-600 transition-colors flex-shrink-0"
|
|
>
|
|
<RotateCcw className="w-4 h-4 text-slate-400" />
|
|
<span>Reset Filters</span>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Detailed Filters Grid */}
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-3 pt-4 border-t border-slate-100">
|
|
{/* Category Dropdown */}
|
|
<div>
|
|
<label className="block text-[11px] font-semibold text-slate-500 uppercase tracking-wider mb-1">Category</label>
|
|
<select
|
|
value={selectedCategory}
|
|
onChange={(e) => setSelectedCategory(e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-200 text-xs bg-slate-50/50 focus:bg-white focus:outline-none focus:border-[#185FA5]"
|
|
>
|
|
{filtersMetadata.categories.map(cat => (
|
|
<option key={cat} value={cat}>{cat}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* Nationality Dropdown */}
|
|
<div>
|
|
<label className="block text-[11px] font-semibold text-slate-500 uppercase tracking-wider mb-1">Nationality</label>
|
|
<select
|
|
value={selectedNationality}
|
|
onChange={(e) => setSelectedNationality(e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-200 text-xs bg-slate-50/50 focus:bg-white focus:outline-none focus:border-[#185FA5]"
|
|
>
|
|
{filtersMetadata.nationalities.map(nat => (
|
|
<option key={nat} value={nat}>{nat}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* Availability Dropdown */}
|
|
<div>
|
|
<label className="block text-[11px] font-semibold text-slate-500 uppercase tracking-wider mb-1">Availability</label>
|
|
<select
|
|
value={selectedAvailability}
|
|
onChange={(e) => setSelectedAvailability(e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-200 text-xs bg-slate-50/50 focus:bg-white focus:outline-none focus:border-[#185FA5]"
|
|
>
|
|
{filtersMetadata.availabilities.map(av => (
|
|
<option key={av} value={av}>{av}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* Experience Dropdown */}
|
|
<div>
|
|
<label className="block text-[11px] font-semibold text-slate-500 uppercase tracking-wider mb-1">Experience</label>
|
|
<select
|
|
value={selectedExperience}
|
|
onChange={(e) => setSelectedExperience(e.target.value)}
|
|
className="w-full px-3 py-2 rounded-xl border border-slate-200 text-xs bg-slate-50/50 focus:bg-white focus:outline-none focus:border-[#185FA5]"
|
|
>
|
|
{filtersMetadata.experienceLevels.map(exp => (
|
|
<option key={exp} value={exp}>{exp}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* Max Salary Slider */}
|
|
<div>
|
|
<div className="flex items-center justify-between mb-1 text-[11px]">
|
|
<span className="font-semibold text-slate-500 uppercase tracking-wider">Max Salary</span>
|
|
<span className="font-bold text-[#185FA5]">{maxSalary} AED</span>
|
|
</div>
|
|
<input
|
|
type="range"
|
|
min="1200"
|
|
max="3000"
|
|
step="100"
|
|
value={maxSalary}
|
|
onChange={(e) => setMaxSalary(Number(e.target.value))}
|
|
className="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-[#185FA5]"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Results Header */}
|
|
<div className="flex items-center justify-between px-1">
|
|
<div className="text-xs font-semibold text-slate-500">
|
|
Showing <span className="text-slate-900 font-bold">{filteredWorkers.length}</span> domestic workers matching your criteria
|
|
</div>
|
|
<div className="flex items-center space-x-2 text-xs font-medium text-slate-600">
|
|
<Filter className="w-3.5 h-3.5 text-[#185FA5]" />
|
|
<span>OCR Vetted & Legal</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Workers Grid */}
|
|
{filteredWorkers.length > 0 ? (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{filteredWorkers.map((worker) => {
|
|
const isShortlisted = shortlistedIds.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 overflow-hidden group">
|
|
{/* Card Header Banner */}
|
|
<div className="bg-slate-50 p-6 border-b border-slate-100 flex items-start justify-between relative">
|
|
<div className="flex items-center space-x-3">
|
|
<div className="w-14 h-14 rounded-full bg-blue-100 text-[#185FA5] flex items-center justify-center font-bold text-xl border border-blue-200 shadow-inner flex-shrink-0">
|
|
{worker.name.charAt(0)}
|
|
</div>
|
|
<div className="truncate">
|
|
<div className="font-bold text-base text-slate-900 group-hover:text-[#185FA5] transition-colors truncate flex items-center space-x-1.5">
|
|
<span>{worker.name}</span>
|
|
{worker.verified && <CheckCircle2 className="w-4 h-4 text-emerald-600 flex-shrink-0" />}
|
|
</div>
|
|
<div className="flex items-center space-x-1.5 text-xs text-slate-500 mt-0.5">
|
|
<Globe2 className="w-3.5 h-3.5 text-slate-400" />
|
|
<span>{worker.nationality}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Shortlist Toggle Button */}
|
|
<button
|
|
type="button"
|
|
onClick={() => toggleShortlist(worker.id)}
|
|
className={`p-2 rounded-xl transition-all ${
|
|
isShortlisted
|
|
? 'bg-blue-50 text-[#185FA5] hover:bg-blue-100'
|
|
: 'bg-white text-slate-400 hover:text-[#185FA5] border border-slate-200'
|
|
}`}
|
|
>
|
|
<Bookmark className={`w-5 h-5 ${isShortlisted ? 'fill-[#185FA5]' : ''}`} />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Card Body */}
|
|
<div className="p-6 flex-1 flex flex-col justify-between space-y-4">
|
|
<div className="space-y-4">
|
|
{/* Top Skill Pill & Salary */}
|
|
<div className="flex items-center justify-between text-xs">
|
|
<span className="px-2.5 py-1 bg-blue-50 text-[#185FA5] font-bold rounded-lg border border-blue-100">
|
|
{worker.category}
|
|
</span>
|
|
<div className="flex items-center space-x-1 font-bold text-slate-800">
|
|
<DollarSign className="w-4 h-4 text-emerald-600" />
|
|
<span>{worker.salary} AED / mo</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Bio Snippet */}
|
|
<p className="text-xs text-slate-600 line-clamp-2 leading-relaxed">
|
|
"{worker.bio}"
|
|
</p>
|
|
|
|
{/* Details Grid */}
|
|
<div className="grid grid-cols-2 gap-2 p-3 bg-slate-50 rounded-xl text-[11px] text-slate-600 font-medium">
|
|
<div className="flex items-center space-x-1.5">
|
|
<Briefcase className="w-3.5 h-3.5 text-slate-400" />
|
|
<span>Exp: {worker.experience}</span>
|
|
</div>
|
|
<div className="flex items-center space-x-1.5 truncate">
|
|
<Calendar className="w-3.5 h-3.5 text-slate-400" />
|
|
<span className="truncate">Avail: {worker.availability}</span>
|
|
</div>
|
|
<div className="flex items-center space-x-1.5">
|
|
<HeartHandshake className="w-3.5 h-3.5 text-slate-400" />
|
|
<span>{worker.religion}</span>
|
|
</div>
|
|
<div className="flex items-center space-x-1.5 truncate">
|
|
<Sparkles className="w-3.5 h-3.5 text-slate-400" />
|
|
<span className="truncate">Age: {worker.age} yrs</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Skill Tags */}
|
|
<div className="flex flex-wrap gap-1.5">
|
|
{worker.skills.map(s => (
|
|
<span key={s} className="text-[10px] bg-slate-100 text-slate-600 px-2 py-0.5 rounded-md font-semibold">
|
|
{s}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* View Profile Button */}
|
|
<Link
|
|
href={`/employer/workers/${worker.id}`}
|
|
className="w-full bg-[#185FA5] hover:bg-[#144f8a] text-white rounded-xl h-10 font-bold text-xs flex items-center justify-center transition-colors shadow-sm"
|
|
>
|
|
View Full Profile
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
) : (
|
|
<div className="text-center py-16 bg-white rounded-2xl border border-slate-200 shadow-sm space-y-3">
|
|
<SlidersHorizontal className="w-12 h-12 text-slate-300 mx-auto" />
|
|
<div className="text-base font-bold text-slate-800">No workers match your filter criteria</div>
|
|
<p className="text-xs text-slate-500 max-w-sm mx-auto">
|
|
Try adjusting your salary slider, broadening your category selection, or resetting all filters.
|
|
</p>
|
|
<button
|
|
type="button"
|
|
onClick={resetFilters}
|
|
className="mt-2 bg-[#185FA5] text-white px-5 py-2.5 rounded-xl text-xs font-bold shadow-sm hover:bg-[#144f8a] transition-colors"
|
|
>
|
|
Reset All Filters
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</EmployerLayout>
|
|
);
|
|
}
|