325 lines
20 KiB
JavaScript
325 lines
20 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { Head, Link, useForm } from '@inertiajs/react';
|
|
import EmployerLayout from '@/Layouts/EmployerLayout';
|
|
import {
|
|
Plus,
|
|
ArrowLeft,
|
|
Briefcase,
|
|
MapPin,
|
|
DollarSign,
|
|
Calendar,
|
|
LayoutGrid,
|
|
Users,
|
|
FileText,
|
|
Sparkles,
|
|
ShieldCheck,
|
|
Save
|
|
} from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
|
|
export default function Edit({ job }) {
|
|
const { data, setData, post, processing, errors } = useForm({
|
|
title: job.title || '',
|
|
workers_needed: job.workers_needed || 1,
|
|
location: job.location || '',
|
|
salary: job.salary ? Math.round(job.salary) : '',
|
|
job_type: job.job_type || 'Full Time',
|
|
start_date: job.start_date || '',
|
|
description: job.description || '',
|
|
requirements: job.requirements || '',
|
|
status: job.status || 'active'
|
|
});
|
|
|
|
const [clientErrors, setClientErrors] = useState({});
|
|
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault();
|
|
|
|
// Client-side validation
|
|
const newErrors = {};
|
|
if (!data.title.trim()) {
|
|
newErrors.title = 'Job title is required.';
|
|
}
|
|
if (!data.workers_needed || data.workers_needed < 1) {
|
|
newErrors.workers_needed = 'Workers needed must be at least 1.';
|
|
}
|
|
if (!data.location.trim()) {
|
|
newErrors.location = 'Job location is required.';
|
|
}
|
|
if (!data.salary || data.salary < 0) {
|
|
newErrors.salary = 'Monthly salary must be a positive number.';
|
|
}
|
|
if (!data.start_date) {
|
|
newErrors.start_date = 'Start date is required.';
|
|
}
|
|
if (!data.description.trim()) {
|
|
newErrors.description = 'Job description is required.';
|
|
}
|
|
if (!data.status) {
|
|
newErrors.status = 'Status is required.';
|
|
}
|
|
|
|
if (Object.keys(newErrors).length > 0) {
|
|
setClientErrors(newErrors);
|
|
toast.error('Validation failed. Please correct the fields in red.');
|
|
return;
|
|
}
|
|
|
|
setClientErrors({});
|
|
post(`/employer/jobs/${job.id}/edit`, {
|
|
onSuccess: () => {
|
|
toast.success('Job updated successfully.');
|
|
},
|
|
onError: () => {
|
|
toast.error('Failed to update job. Please check the inputs.');
|
|
}
|
|
});
|
|
};
|
|
|
|
return (
|
|
<EmployerLayout title="Edit Job">
|
|
<Head title="Edit Job - Employer Portal" />
|
|
|
|
<div className="max-w-4xl mx-auto space-y-6 pb-12 select-none">
|
|
<Link
|
|
href="/employer/jobs"
|
|
className="inline-flex items-center space-x-2 text-xs font-bold text-slate-600 hover:text-[#185FA5] transition-colors"
|
|
>
|
|
<ArrowLeft className="w-4 h-4" />
|
|
<span>Back to My Jobs</span>
|
|
</Link>
|
|
|
|
<div className="bg-white rounded-[32px] border border-slate-200 shadow-sm overflow-hidden">
|
|
<div className="p-8 border-b border-slate-50 bg-slate-50/30 flex items-center justify-between">
|
|
<div className="flex items-center space-x-4">
|
|
<div className="p-3 bg-blue-50 rounded-2xl">
|
|
<Briefcase className="w-6 h-6 text-[#185FA5]" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-xl font-black text-slate-900 tracking-tight uppercase">Edit Job Posting</h1>
|
|
<p className="text-[10px] text-slate-400 font-bold uppercase tracking-widest mt-1">Update details for "{job.title}"</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="p-8 space-y-8">
|
|
{/* Section 1: Basic Info */}
|
|
<div className="space-y-6">
|
|
<h3 className="text-xs font-black text-[#185FA5] uppercase tracking-[0.2em] flex items-center">
|
|
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full mr-2" />
|
|
Basic Information
|
|
</h3>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div className="space-y-2 col-span-2">
|
|
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Job Title</label>
|
|
<div className="relative">
|
|
<Briefcase className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
|
<input
|
|
type="text"
|
|
placeholder="e.g. Senior Electrician for Villa Project"
|
|
className={`w-full pl-11 pr-4 py-3 bg-slate-50 border rounded-2xl text-sm font-bold text-slate-900 focus:ring-4 focus:ring-blue-500/10 focus:border-[#185FA5] outline-none transition-all ${
|
|
(clientErrors.title || errors.title) ? 'border-red-500 bg-red-50/20' : 'border-slate-100'
|
|
}`}
|
|
value={data.title}
|
|
onChange={e => setData('title', e.target.value)}
|
|
/>
|
|
</div>
|
|
{(clientErrors.title || errors.title) && (
|
|
<p className="text-xs font-bold text-red-500 mt-1 ml-1">{clientErrors.title || errors.title}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Workers Needed</label>
|
|
<div className="relative">
|
|
<Users className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
|
<input
|
|
type="number"
|
|
min="1"
|
|
className={`w-full pl-11 pr-4 py-3 bg-slate-50 border rounded-2xl text-sm font-bold text-slate-900 focus:ring-4 focus:ring-blue-500/10 focus:border-[#185FA5] outline-none transition-all ${
|
|
(clientErrors.workers_needed || errors.workers_needed) ? 'border-red-500 bg-red-50/20' : 'border-slate-100'
|
|
}`}
|
|
value={data.workers_needed}
|
|
onChange={e => setData('workers_needed', e.target.value)}
|
|
/>
|
|
</div>
|
|
{(clientErrors.workers_needed || errors.workers_needed) && (
|
|
<p className="text-xs font-bold text-red-500 mt-1 ml-1">{clientErrors.workers_needed || errors.workers_needed}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Job Type</label>
|
|
<div className="grid grid-cols-3 gap-2">
|
|
{['Full Time', 'Part Time', 'Contract'].map(type => (
|
|
<button
|
|
key={type}
|
|
type="button"
|
|
onClick={() => setData('job_type', type)}
|
|
className={`py-3 text-[10px] font-black rounded-xl border transition-all ${
|
|
data.job_type === type
|
|
? 'bg-[#185FA5] text-white border-[#185FA5] shadow-md shadow-blue-500/20'
|
|
: 'bg-white text-slate-400 border-slate-100 hover:border-slate-300'
|
|
}`}
|
|
>
|
|
{type.toUpperCase()}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2 col-span-2">
|
|
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Job Status</label>
|
|
<div className="grid grid-cols-3 gap-2">
|
|
{[
|
|
{ key: 'active', label: 'Active' },
|
|
{ key: 'closed', label: 'Closed' },
|
|
{ key: 'draft', label: 'Draft' }
|
|
].map(s => (
|
|
<button
|
|
key={s.key}
|
|
type="button"
|
|
onClick={() => setData('status', s.key)}
|
|
className={`py-3 text-[10px] font-black rounded-xl border transition-all ${
|
|
data.status === s.key
|
|
? 'bg-[#185FA5] text-white border-[#185FA5] shadow-md shadow-blue-500/20'
|
|
: 'bg-white text-slate-400 border-slate-100 hover:border-slate-300'
|
|
}`}
|
|
>
|
|
{s.label.toUpperCase()}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Section 2: Logistics & Pay */}
|
|
<div className="space-y-6">
|
|
<h3 className="text-xs font-black text-[#185FA5] uppercase tracking-[0.2em] flex items-center">
|
|
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full mr-2" />
|
|
Logistics & Compensation
|
|
</h3>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<div className="space-y-2">
|
|
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Job Location</label>
|
|
<div className="relative">
|
|
<MapPin className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
|
<input
|
|
type="text"
|
|
placeholder="e.g. Dubai Marina"
|
|
className={`w-full pl-11 pr-4 py-3 bg-slate-50 border rounded-2xl text-sm font-bold text-slate-900 focus:ring-4 focus:ring-blue-500/10 focus:border-[#185FA5] outline-none transition-all ${
|
|
(clientErrors.location || errors.location) ? 'border-red-500 bg-red-50/20' : 'border-slate-100'
|
|
}`}
|
|
value={data.location}
|
|
onChange={e => setData('location', e.target.value)}
|
|
/>
|
|
</div>
|
|
{(clientErrors.location || errors.location) && (
|
|
<p className="text-xs font-bold text-red-500 mt-1 ml-1">{clientErrors.location || errors.location}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Salary (AED / mo)</label>
|
|
<div className="relative">
|
|
<DollarSign className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
|
<input
|
|
type="number"
|
|
placeholder="e.g. 2500"
|
|
className={`w-full pl-11 pr-4 py-3 bg-slate-50 border rounded-2xl text-sm font-bold text-slate-900 focus:ring-4 focus:ring-blue-500/10 focus:border-[#185FA5] outline-none transition-all ${
|
|
(clientErrors.salary || errors.salary) ? 'border-red-500 bg-red-50/20' : 'border-slate-100'
|
|
}`}
|
|
value={data.salary}
|
|
onChange={e => setData('salary', e.target.value)}
|
|
/>
|
|
</div>
|
|
{(clientErrors.salary || errors.salary) && (
|
|
<p className="text-xs font-bold text-red-500 mt-1 ml-1">{clientErrors.salary || errors.salary}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Start Date</label>
|
|
<div className="relative">
|
|
<Calendar className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
|
<input
|
|
type="date"
|
|
className={`w-full pl-11 pr-4 py-3 bg-slate-50 border rounded-2xl text-sm font-bold text-slate-900 focus:ring-4 focus:ring-blue-500/10 focus:border-[#185FA5] outline-none transition-all ${
|
|
(clientErrors.start_date || errors.start_date) ? 'border-red-500 bg-red-50/20' : 'border-slate-100'
|
|
}`}
|
|
value={data.start_date}
|
|
onChange={e => setData('start_date', e.target.value)}
|
|
/>
|
|
</div>
|
|
{(clientErrors.start_date || errors.start_date) && (
|
|
<p className="text-xs font-bold text-red-500 mt-1 ml-1">{clientErrors.start_date || errors.start_date}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Section 3: Details */}
|
|
<div className="space-y-6">
|
|
<h3 className="text-xs font-black text-[#185FA5] uppercase tracking-[0.2em] flex items-center">
|
|
<div className="w-1.5 h-1.5 bg-[#185FA5] rounded-full mr-2" />
|
|
Detailed Requirements
|
|
</h3>
|
|
|
|
<div className="space-y-6">
|
|
<div className="space-y-2">
|
|
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Job Description</label>
|
|
<textarea
|
|
placeholder="Briefly describe the responsibilities..."
|
|
maxLength={300}
|
|
className={`w-full px-5 py-4 bg-slate-50 border rounded-2xl text-sm font-bold text-slate-900 focus:ring-4 focus:ring-blue-500/10 focus:border-[#185FA5] outline-none transition-all min-h-[100px] resize-none ${
|
|
(clientErrors.description || errors.description) ? 'border-red-500 bg-red-50/20' : 'border-slate-100'
|
|
}`}
|
|
value={data.description}
|
|
onChange={e => setData('description', e.target.value)}
|
|
/>
|
|
<div className="flex justify-between items-center px-1">
|
|
{(clientErrors.description || errors.description) ? (
|
|
<p className="text-xs font-bold text-red-500">{clientErrors.description || errors.description}</p>
|
|
) : <div />}
|
|
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">
|
|
{data.description.length} / 300 Characters
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-[10px] font-black text-slate-400 uppercase tracking-widest ml-1">Special Requirements (Optional)</label>
|
|
<div className="relative">
|
|
<FileText className="absolute left-4 top-5 w-4 h-4 text-slate-400" />
|
|
<textarea
|
|
placeholder="e.g. Must speak Arabic, 5+ years experience, transferable visa..."
|
|
className="w-full pl-11 pr-4 py-4 bg-slate-50 border border-slate-100 rounded-2xl text-sm font-bold text-slate-900 focus:ring-4 focus:ring-blue-500/10 focus:border-[#185FA5] outline-none transition-all min-h-[100px] resize-none"
|
|
value={data.requirements}
|
|
onChange={e => setData('requirements', e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Submit Button */}
|
|
<div className="pt-6 border-t border-slate-100">
|
|
<button
|
|
type="submit"
|
|
disabled={processing}
|
|
className="w-full bg-[#185FA5] text-white py-5 rounded-2xl font-black text-sm uppercase tracking-widest shadow-xl shadow-blue-500/20 hover:bg-[#144f8a] active:scale-[0.98] transition-all flex items-center justify-center space-x-3"
|
|
>
|
|
<Save className="w-5 h-5" />
|
|
<span>Save Changes</span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</EmployerLayout>
|
|
);
|
|
}
|