525 lines
35 KiB
JavaScript

import React, { useState, useMemo } from 'react';
import { Head, Link, router } from '@inertiajs/react';
import EmployerLayout from '@/Layouts/EmployerLayout';
import {
Plus,
Search,
Filter,
Download,
MoreHorizontal,
MapPin,
DollarSign,
Users,
Calendar,
Briefcase,
CheckCircle2,
XCircle,
Clock,
Eye,
Edit2,
Trash2,
LayoutGrid,
List,
Shield,
Navigation,
Layers
} from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { toast } from 'sonner';
// Helper function to map jobs dynamically to categories and skills based on title
const getJobMetadata = (title = '') => {
const t = title.toLowerCase();
if (t.includes('electrician') || t.includes('electrical')) {
return {
category: 'Skilled Worker',
skills: ['Electrical Wiring', 'Maintenance', 'Safety Inspection', 'Wiring Diagrams']
};
}
if (t.includes('clean') || t.includes('housekeep') || t.includes('maid') || t.includes('laundry')) {
return {
category: 'General Worker',
skills: ['Cleaning', 'Housekeeping', 'Laundry', 'Sanitization']
};
}
if (t.includes('plumb')) {
return {
category: 'Skilled Worker',
skills: ['Plumbing', 'Pipe Fitting', 'Leak Repair', 'Blueprint Reading']
};
}
if (t.includes('security') || t.includes('guard') || t.includes('watchman')) {
return {
category: 'Security',
skills: ['Security', 'Surveillance', 'Access Control', 'Patrolling']
};
}
if (t.includes('driver') || t.includes('chauffeur') || t.includes('delivery')) {
return {
category: 'Driver',
skills: ['Driving', 'Route Knowledge', 'Vehicle Safety', 'Navigation']
};
}
if (t.includes('mason') || t.includes('carpenter') || t.includes('woodwork') || t.includes('construction')) {
return {
category: 'Skilled Worker',
skills: ['Carpentry', 'Woodwork', 'Masonry', 'Tool Operation']
};
}
if (t.includes('store') || t.includes('warehouse') || t.includes('inventory') || t.includes('keeper')) {
return {
category: 'Logistics',
skills: ['Inventory', 'Stock Management', 'Packaging', 'Labeling']
};
}
if (t.includes('helper') || t.includes('labor') || t.includes('assistant')) {
return {
category: 'General Worker',
skills: ['Loading', 'Unloading', 'Physical Labor', 'Material Handling']
};
}
// Fallback
return {
category: 'Skilled Worker',
skills: ['General Support', 'Task Execution', 'Operations']
};
};
export default function Index({ initialJobs }) {
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState('All');
const [categoryFilter, setCategoryFilter] = useState('All');
const [skillFilter, setSkillFilter] = useState('All');
const [viewMode, setViewMode] = useState('grid');
const [jobs, setJobs] = useState(initialJobs || []);
const handleDelete = (jobId) => {
if (confirm('Are you sure you want to delete this job posting? This action cannot be undone.')) {
router.delete(`/employer/jobs/${jobId}`, {
onSuccess: () => {
toast.success('Job deleted successfully.');
},
onError: () => {
toast.error('Failed to delete job.');
}
});
}
};
// Derived category list
const categories = ['All', 'Skilled Worker', 'General Worker', 'Security', 'Driver', 'Logistics'];
// Derived skills list
const skills = [
'All',
'Electrical Wiring',
'Cleaning',
'Housekeeping',
'Plumbing',
'Pipe Fitting',
'Security',
'Surveillance',
'Driving',
'Route Knowledge',
'Carpentry',
'Woodwork',
'Inventory',
'Stock Management',
'Loading',
'Unloading'
];
const filteredJobs = useMemo(() => {
return jobs.filter(job => {
const meta = getJobMetadata(job.title);
const matchesSearch = job.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
job.location.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus = statusFilter === 'All' || job.status.toLowerCase() === statusFilter.toLowerCase();
const matchesCategory = categoryFilter === 'All' || meta.category === categoryFilter;
const matchesSkill = skillFilter === 'All' || meta.skills.includes(skillFilter);
return matchesSearch && matchesStatus && matchesCategory && matchesSkill;
});
}, [jobs, searchTerm, statusFilter, categoryFilter, skillFilter]);
return (
<EmployerLayout title="My Jobs">
<Head title="My Jobs - Employer Portal" />
<div className="space-y-8 select-none">
{/* Header Actions */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 className="text-[28px] font-black text-[#0f172a] tracking-tight">Active Job Postings</h1>
<p className="text-sm font-medium text-slate-500 mt-1">Manage and track your recruitment status</p>
</div>
<Link
href="/employer/jobs/create"
className="bg-[#185FA5] text-white px-7 py-3.5 rounded-[18px] font-extrabold text-xs uppercase tracking-widest shadow-xl shadow-blue-500/10 hover:bg-[#144f8a] transition-all flex items-center justify-center space-x-2"
>
<Plus className="w-4 h-4 stroke-[3px]" />
<span>Post a Job</span>
</Link>
</div>
{/* Filters & Search Bar */}
<div className="bg-white p-6 rounded-[28px] border border-slate-200/80 shadow-sm flex flex-col xl:flex-row gap-4 items-stretch xl:items-center">
{/* Search */}
<div className="relative flex-1 min-w-[280px]">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input
type="text"
placeholder="Search by job title, skills, or location..."
className="w-full pl-11 pr-4 py-3 bg-slate-50/50 border border-slate-200/80 rounded-2xl text-xs font-bold text-slate-800 focus:ring-4 focus:ring-blue-500/5 focus:border-[#185FA5] focus:bg-white outline-none transition-all placeholder-slate-400"
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
/>
</div>
{/* Select Dropdowns */}
<div className="flex flex-col sm:flex-row gap-3">
<select
className="px-4 py-3 bg-slate-50/50 border border-slate-200/80 rounded-2xl text-xs font-bold text-slate-700 focus:ring-4 focus:ring-blue-500/5 focus:border-[#185FA5] outline-none transition-all min-w-[160px]"
value={categoryFilter}
onChange={e => setCategoryFilter(e.target.value)}
>
<option value="All">All Categories</option>
{categories.filter(c => c !== 'All').map(c => (
<option key={c} value={c}>{c}</option>
))}
</select>
<select
className="px-4 py-3 bg-slate-50/50 border border-slate-200/80 rounded-2xl text-xs font-bold text-slate-700 focus:ring-4 focus:ring-blue-500/5 focus:border-[#185FA5] outline-none transition-all min-w-[160px]"
value={skillFilter}
onChange={e => setSkillFilter(e.target.value)}
>
<option value="All">All Skills</option>
{skills.filter(s => s !== 'All').map(s => (
<option key={s} value={s}>{s}</option>
))}
</select>
</div>
{/* Status Toggle & Views */}
<div className="flex flex-wrap items-center gap-4 justify-between xl:justify-end flex-shrink-0">
<div className="flex items-center bg-slate-50 border border-slate-100 p-1 rounded-2xl">
{['All', 'Active', 'Closed', 'Draft'].map(status => (
<button
key={status}
onClick={() => setStatusFilter(status)}
className={`px-4 py-2 text-[10px] font-black rounded-xl transition-all ${
statusFilter === status
? 'bg-white text-[#185FA5] shadow-sm border border-slate-200/50'
: 'text-slate-400 hover:text-slate-600'
}`}
>
{status.toUpperCase()}
</button>
))}
</div>
<div className="flex items-center space-x-2">
<button className="p-3 bg-white border border-slate-200 text-slate-400 hover:text-[#185FA5] rounded-xl transition-all shadow-sm">
<Download className="w-4 h-4" />
</button>
<div className="h-6 w-px bg-slate-200" />
<button
onClick={() => setViewMode('grid')}
className={`p-3 rounded-xl transition-all border ${viewMode === 'grid' ? 'bg-blue-50 border-blue-100 text-[#185FA5]' : 'bg-white border-slate-200 text-slate-400 hover:text-[#185FA5]'}`}
>
<LayoutGrid className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode('list')}
className={`p-3 rounded-xl transition-all border ${viewMode === 'list' ? 'bg-blue-50 border-blue-100 text-[#185FA5]' : 'bg-white border-slate-200 text-slate-400 hover:text-[#185FA5]'}`}
>
<List className="w-4 h-4" />
</button>
</div>
</div>
</div>
{/* Jobs Display */}
{filteredJobs.length > 0 ? (
viewMode === 'grid' ? (
/* Grid Layout */
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredJobs.map((job) => {
const meta = getJobMetadata(job.title);
const isFilled = job.hired_count >= job.workers_needed;
const percentFilled = Math.min(100, Math.round((job.hired_count / job.workers_needed) * 100));
return (
<div key={job.id} className="bg-white rounded-[28px] border border-slate-200/80 shadow-sm hover:shadow-md transition-all duration-300 overflow-hidden flex flex-col group">
<div className="p-6 flex-1 flex flex-col justify-between space-y-5">
{/* Header */}
<div className="flex items-start justify-between gap-2">
<div className="flex items-start space-x-3.5">
<div className="w-11 h-11 bg-blue-50/50 rounded-2xl flex items-center justify-center flex-shrink-0 border border-blue-100/50">
{meta.category === 'Security' ? <Shield className="w-5.5 h-5.5 text-[#185FA5]" /> :
meta.category === 'Driver' ? <Navigation className="w-5.5 h-5.5 text-[#185FA5]" /> :
meta.category === 'Logistics' ? <Layers className="w-5.5 h-5.5 text-[#185FA5]" /> :
<Briefcase className="w-5.5 h-5.5 text-[#185FA5]" />}
</div>
<div>
<h3 className="font-extrabold text-slate-900 text-[14px] leading-snug tracking-tight group-hover:text-[#185FA5] transition-colors line-clamp-2">
<Link href={`/employer/jobs/${job.id}`}>{job.title}</Link>
</h3>
<span className="text-[10px] font-bold text-slate-400 block mt-1 uppercase tracking-wider">{job.posted_at}</span>
</div>
</div>
<Badge
variant="outline"
className={`px-2.5 py-0.5 rounded-full text-[9px] font-black uppercase tracking-wider flex items-center flex-shrink-0 border-0 ${
job.status.toLowerCase() === 'active' ? 'bg-emerald-50 text-emerald-700' :
job.status.toLowerCase() === 'closed' ? 'bg-rose-50 text-rose-700' :
'bg-slate-50 text-slate-500'
}`}
>
<span className={`w-1.5 h-1.5 rounded-full mr-1.5 ${
job.status.toLowerCase() === 'active' ? 'bg-emerald-500' :
job.status.toLowerCase() === 'closed' ? 'bg-rose-500' :
'bg-slate-400'
}`} />
{job.status}
</Badge>
</div>
{/* Location & Salary */}
<div className="space-y-2 text-xs font-bold text-slate-600">
<div className="flex items-center space-x-2">
<MapPin className="w-4 h-4 text-slate-400" />
<span className="line-clamp-1">{job.location}</span>
</div>
<div className="flex items-center space-x-2">
<DollarSign className="w-4 h-4 text-emerald-600" />
<span>$ {job.salary.toLocaleString()} AED / month</span>
</div>
</div>
{/* Stats & Progress Bar */}
<div className="space-y-2 bg-slate-50/50 p-4 rounded-2xl border border-slate-100">
<div className="grid grid-cols-3 gap-2 text-center">
<div>
<div className="text-[14px] font-black text-slate-900">{job.workers_needed}</div>
<div className="text-[9px] font-extrabold text-slate-400 uppercase tracking-wider">Total Req</div>
</div>
<div>
<div className="text-[14px] font-black text-slate-900">{job.hired_count}</div>
<div className="text-[9px] font-extrabold text-slate-400 uppercase tracking-wider">Hired</div>
</div>
<div>
<div className="text-[14px] font-black text-slate-900">{job.applied_count}</div>
<div className="text-[9px] font-extrabold text-slate-400 uppercase tracking-wider">Applications</div>
</div>
</div>
<div className="space-y-1">
<div className="w-full h-1.5 bg-slate-100 rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-500 ${isFilled ? 'bg-emerald-500' : 'bg-[#185FA5]'}`}
style={{ width: `${percentFilled}%` }}
/>
</div>
<div className="flex justify-end text-[9px] font-black uppercase tracking-wider">
{isFilled ? (
<span className="text-emerald-600">All positions filled</span>
) : (
<span className="text-slate-500">{job.workers_needed - job.hired_count} more to hire</span>
)}
</div>
</div>
</div>
{/* Skills Tags */}
<div className="space-y-1">
<div className="text-[9px] font-extrabold text-slate-400 uppercase tracking-wider">Skills</div>
<div className="flex flex-wrap gap-1.5">
{meta.skills.slice(0, 2).map((skill, idx) => (
<span key={idx} className="bg-slate-50 text-slate-600 border border-slate-100 px-2.5 py-1 rounded-lg text-[10px] font-bold">
{skill}
</span>
))}
{meta.skills.length > 2 && (
<span className="bg-blue-50 text-[#185FA5] px-2 py-1 rounded-lg text-[10px] font-black">
+{meta.skills.length - 2}
</span>
)}
</div>
</div>
{/* Category Row */}
<div className="flex items-center justify-between text-xs border-t border-slate-100 pt-3.5">
<span className="font-bold text-slate-400">Category</span>
<span className="font-extrabold text-slate-700">{meta.category}</span>
</div>
{/* Actions footer */}
<div className="flex items-center space-x-3 pt-1">
<Link
href={`/employer/jobs/${job.id}/applicants`}
className="flex-1 bg-[#185FA5] text-white py-3 rounded-xl font-black text-[10px] uppercase tracking-[0.15em] shadow-lg shadow-blue-500/5 hover:bg-[#144f8a] transition-all flex items-center justify-center space-x-2"
>
<Eye className="w-3.5 h-3.5" />
<span>View Applicants</span>
</Link>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="p-3 bg-slate-50 hover:bg-slate-100 border border-slate-100 text-slate-400 hover:text-slate-900 rounded-xl transition-all">
<MoreHorizontal className="w-4 h-4" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48 p-2 rounded-2xl shadow-xl border border-slate-100">
<DropdownMenuLabel className="text-[10px] font-black text-slate-400 uppercase tracking-widest px-2 py-2">Quick Actions</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem className="p-2.5 rounded-xl focus:bg-blue-50 cursor-pointer" asChild>
<Link href={`/employer/jobs/${job.id}`} className="flex items-center space-x-3 text-slate-700 w-full">
<Eye className="w-4 h-4 text-blue-500" />
<span className="text-xs font-bold">View Details</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="p-2.5 rounded-xl focus:bg-blue-50 cursor-pointer" asChild>
<Link href={`/employer/jobs/${job.id}/edit`} className="flex items-center space-x-3 text-[#185FA5] w-full">
<Edit2 className="w-4 h-4" />
<span className="text-xs font-bold">Edit Posting</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="p-2.5 rounded-xl focus:bg-rose-50 cursor-pointer" onClick={() => handleDelete(job.id)}>
<div className="flex items-center space-x-3 text-rose-600 w-full">
<Trash2 className="w-4 h-4" />
<span className="text-xs font-bold">Delete Job</span>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
);
})}
</div>
) : (
/* List Layout */
<div className="bg-white rounded-[28px] border border-slate-200/80 shadow-sm overflow-hidden divide-y divide-slate-100">
{filteredJobs.map((job) => {
const meta = getJobMetadata(job.title);
const isFilled = job.hired_count >= job.workers_needed;
return (
<div key={job.id} className="p-6 flex flex-col lg:flex-row lg:items-center justify-between gap-6 hover:bg-slate-50/50 transition-colors">
<div className="flex items-start space-x-4 flex-1">
<div className="w-12 h-12 bg-blue-50 rounded-2xl flex items-center justify-center flex-shrink-0 border border-blue-100/50">
{meta.category === 'Security' ? <Shield className="w-6 h-6 text-[#185FA5]" /> :
meta.category === 'Driver' ? <Navigation className="w-6 h-6 text-[#185FA5]" /> :
meta.category === 'Logistics' ? <Layers className="w-6 h-6 text-[#185FA5]" /> :
<Briefcase className="w-6 h-6 text-[#185FA5]" />}
</div>
<div className="space-y-1 flex-1">
<div className="flex flex-wrap items-center gap-2">
<h3 className="font-extrabold text-slate-900 text-base leading-snug tracking-tight hover:text-[#185FA5] transition-colors">
<Link href={`/employer/jobs/${job.id}`}>{job.title}</Link>
</h3>
<Badge
variant="outline"
className={`px-2.5 py-0.5 rounded-full text-[9px] font-black uppercase tracking-wider flex items-center border-0 ${
job.status.toLowerCase() === 'active' ? 'bg-emerald-50 text-emerald-700' :
job.status.toLowerCase() === 'closed' ? 'bg-rose-50 text-rose-700' :
'bg-slate-50 text-slate-500'
}`}
>
{job.status}
</Badge>
</div>
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs font-bold text-slate-500">
<span className="uppercase text-[9px] tracking-wider font-extrabold text-slate-400">Posted {job.posted_at}</span>
<span className="flex items-center space-x-1"><MapPin className="w-3.5 h-3.5 text-slate-400" /> <span>{job.location}</span></span>
<span className="flex items-center space-x-1"><DollarSign className="w-3.5 h-3.5 text-emerald-600" /> <span>$ {job.salary.toLocaleString()} AED</span></span>
<span className="text-slate-400">|</span>
<span className="text-[#185FA5]">{meta.category}</span>
</div>
</div>
</div>
<div className="flex flex-wrap items-center gap-6 justify-between lg:justify-end">
<div className="text-right space-y-1">
<div className="text-xs font-bold text-slate-500">
Hired: <span className="text-slate-950 font-black">{job.hired_count}</span> / <span className="text-slate-600">{job.workers_needed}</span> ({job.applied_count} applied)
</div>
<div className="text-[10px] font-black uppercase tracking-wider">
{isFilled ? <span className="text-emerald-600">Filled</span> : <span className="text-[#185FA5]">{job.workers_needed - job.hired_count} needed</span>}
</div>
</div>
<div className="flex items-center space-x-3">
<Link
href={`/employer/jobs/${job.id}/applicants`}
className="bg-[#185FA5] text-white px-5 py-2.5 rounded-xl font-black text-[10px] uppercase tracking-[0.15em] shadow-lg shadow-blue-500/5 hover:bg-[#144f8a] transition-all flex items-center space-x-2"
>
<Eye className="w-3.5 h-3.5" />
<span>Applicants</span>
</Link>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="p-2.5 bg-slate-50 hover:bg-slate-100 border border-slate-100 text-slate-400 hover:text-slate-900 rounded-xl transition-all">
<MoreHorizontal className="w-4 h-4" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48 p-2 rounded-2xl shadow-xl border border-slate-100">
<DropdownMenuLabel className="text-[10px] font-black text-slate-400 uppercase tracking-widest px-2 py-2">Quick Actions</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem className="p-2.5 rounded-xl focus:bg-blue-50 cursor-pointer" asChild>
<Link href={`/employer/jobs/${job.id}`} className="flex items-center space-x-3 text-slate-700 w-full">
<Eye className="w-4 h-4 text-blue-500" />
<span className="text-xs font-bold">View Details</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="p-2.5 rounded-xl focus:bg-blue-50 cursor-pointer" asChild>
<Link href={`/employer/jobs/${job.id}/edit`} className="flex items-center space-x-3 text-[#185FA5] w-full">
<Edit2 className="w-4 h-4" />
<span className="text-xs font-bold">Edit Posting</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="p-2.5 rounded-xl focus:bg-rose-50 cursor-pointer" onClick={() => handleDelete(job.id)}>
<div className="flex items-center space-x-3 text-rose-600 w-full">
<Trash2 className="w-4 h-4" />
<span className="text-xs font-bold">Delete Job</span>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
);
})}
</div>
)
) : (
/* Empty State */
<div className="col-span-full py-20 text-center bg-white rounded-[28px] border border-dashed border-slate-200">
<Briefcase className="w-12 h-12 text-slate-200 mx-auto mb-4" />
<h3 className="text-base font-bold text-slate-400 uppercase tracking-widest">No jobs found</h3>
<Link href="/employer/jobs/create" className="mt-4 inline-flex items-center space-x-2 text-[#185FA5] font-black text-xs uppercase tracking-widest hover:underline">
<Plus className="w-4 h-4" />
<span>Post your first job</span>
</Link>
</div>
)}
</div>
</EmployerLayout>
);
}