first commit
This commit is contained in:
482
src/components/career/careerscomponent.tsx
Normal file
482
src/components/career/careerscomponent.tsx
Normal file
@ -0,0 +1,482 @@
|
||||
// components/Career.tsx
|
||||
'use client';
|
||||
import { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { MapPin, Clock, DollarSign, Building, Users, ChevronRight } from 'lucide-react';
|
||||
import { careerService, Job, JobApplicationData } from '../../services/careerService';
|
||||
import { fileUploadService } from '../../services/fileUploadService';
|
||||
|
||||
const Career: React.FC = () => {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [jobs, setJobs] = useState<Job[]>([]);
|
||||
const [selectedJob, setSelectedJob] = useState<Job | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [submitSuccess, setSubmitSuccess] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
fullName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
experience: '',
|
||||
coverLetter: '',
|
||||
resume: null as File | null
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
loadJobs();
|
||||
}, []);
|
||||
|
||||
const loadJobs = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const fetchedJobs = await careerService.getActiveJobs();
|
||||
setJobs(fetchedJobs);
|
||||
|
||||
// Set first job as default selection if available
|
||||
if (fetchedJobs.length > 0) {
|
||||
setSelectedJob(fetchedJobs[0]);
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to load job listings. Please try again later.');
|
||||
console.error('Error loading jobs:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleJobSelect = (job: Job) => {
|
||||
setSelectedJob(job);
|
||||
setSubmitSuccess(false);
|
||||
// Reset form when job changes
|
||||
setFormData({
|
||||
fullName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
experience: '',
|
||||
coverLetter: '',
|
||||
resume: null
|
||||
});
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}));
|
||||
};
|
||||
|
||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files && e.target.files[0]) {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
resume: e.target.files![0]
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!selectedJob) {
|
||||
alert('Please select a job position.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setSubmitting(true);
|
||||
setError(null);
|
||||
|
||||
let resumeUrl = '';
|
||||
|
||||
// Upload resume if provided
|
||||
if (formData.resume) {
|
||||
try {
|
||||
const uploadResponse = await fileUploadService.uploadFile(formData.resume);
|
||||
resumeUrl = uploadResponse.url;
|
||||
} catch (uploadError) {
|
||||
console.error('Resume upload failed:', uploadError);
|
||||
setError('Failed to upload resume. Please try again.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare application data
|
||||
const applicationData: JobApplicationData = {
|
||||
jobId: parseInt(selectedJob.id),
|
||||
fullName: formData.fullName,
|
||||
email: formData.email,
|
||||
phone: formData.phone,
|
||||
experience: formData.experience,
|
||||
coverLetter: formData.coverLetter || undefined,
|
||||
resumeUrl: resumeUrl || undefined
|
||||
};
|
||||
|
||||
// Submit application
|
||||
const success = await careerService.submitApplication(applicationData);
|
||||
|
||||
if (success) {
|
||||
setSubmitSuccess(true);
|
||||
// Reset form
|
||||
setFormData({
|
||||
fullName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
experience: '',
|
||||
coverLetter: '',
|
||||
resume: null
|
||||
});
|
||||
|
||||
// Clear file input
|
||||
const fileInput = document.getElementById('resume') as HTMLInputElement;
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
}
|
||||
} else {
|
||||
setError('Failed to submit application. Please try again.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error submitting application:', err);
|
||||
setError('Failed to submit application. Please try again.');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getJobTitle = (): string => {
|
||||
if (!selectedJob) return 'Job Application';
|
||||
return `${selectedJob.title} - Application`;
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-900 mx-auto mb-4"></div>
|
||||
<p className="text-gray-600">Loading career opportunities...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
{/* Breadcrumb Section */}
|
||||
<section className="py-4" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<nav className="flex items-center space-x-2 text-sm">
|
||||
<Link
|
||||
href="/"
|
||||
className="hover:opacity-70 transition-opacity duration-200"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
Home
|
||||
</Link>
|
||||
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
|
||||
<span className="font-medium" style={{ color: '#e64838' }}>
|
||||
Careers
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
{/* Page Header */}
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center mb-4">
|
||||
<h1 className="text-3xl font-bold" style={{ color: '#012068' }}>
|
||||
Join Our Team
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-base max-w-2xl leading-relaxed" style={{ color: '#333' }}>
|
||||
Explore career opportunities and be part of our mission to advance trauma care and medical excellence
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Error Message */}
|
||||
{error && (
|
||||
<div className="max-w-7xl mx-auto px-4 py-4">
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<p className="text-red-800 text-sm">{error}</p>
|
||||
<button
|
||||
onClick={loadJobs}
|
||||
className="text-red-600 underline text-sm mt-2"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main Content */}
|
||||
<section className="py-6" style={{ backgroundColor: '#fff' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
{jobs.length === 0 && !error ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-500 text-lg mb-4">No job openings available at the moment.</p>
|
||||
<p className="text-gray-400">Please check back later for new opportunities.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid lg:grid-cols-2 gap-6 lg:gap-8">
|
||||
|
||||
{/* Left Side - Job Listings */}
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-lg font-medium mb-4" style={{ color: '#012068' }}>
|
||||
Available Positions ({jobs.length})
|
||||
</h3>
|
||||
|
||||
{jobs.map((job) => (
|
||||
<div
|
||||
key={job.id}
|
||||
className={`bg-white rounded-lg border cursor-pointer transition-all duration-300 ${
|
||||
selectedJob?.id === job.id
|
||||
? 'shadow-lg border-blue-200'
|
||||
: 'border-gray-100 hover:shadow-md'
|
||||
}`}
|
||||
onClick={() => handleJobSelect(job)}
|
||||
>
|
||||
<div className="p-4 sm:p-6">
|
||||
<div className="flex flex-col sm:flex-row sm:items-start justify-between mb-3 gap-2">
|
||||
<h4 className="text-base sm:text-lg font-medium" style={{ color: '#012068' }}>
|
||||
{job.title}
|
||||
</h4>
|
||||
<span
|
||||
className="px-2 py-1 text-xs font-medium rounded self-start"
|
||||
style={{ backgroundColor: '#012068', color: '#f4f4f4' }}
|
||||
>
|
||||
{job.type}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3 text-xs mb-3" style={{ color: '#012068', opacity: 0.7 }}>
|
||||
<div className="flex items-center">
|
||||
<Building className="w-3 h-3 mr-1" />
|
||||
<span className="hidden sm:inline">{job.department}</span>
|
||||
<span className="sm:hidden">{job.department.split(' ')[0]}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<MapPin className="w-3 h-3 mr-1" />
|
||||
<span className="hidden sm:inline">{job.location}</span>
|
||||
<span className="sm:hidden">
|
||||
{job.location.includes('Vellore') ? 'Vellore' : job.location.split(',')[0]}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Clock className="w-3 h-3 mr-1" />
|
||||
{job.experience}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs leading-relaxed mb-3 line-clamp-2" style={{ color: '#333' }}>
|
||||
{job.description}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between">
|
||||
<div className="flex items-center text-xs" style={{ color: '#012068', opacity: 0.7 }}>
|
||||
{job.salary}
|
||||
</div>
|
||||
<button className="text-xs font-medium hover:opacity-70 transition-opacity text-left sm:text-right" style={{ color: '#e64838' }}>
|
||||
{selectedJob?.id === job.id ? 'Selected' : 'Select & Apply'} →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Right Side - Application Form */}
|
||||
<div className="lg:sticky lg:top-8 h-fit">
|
||||
<div className="bg-white rounded-lg border border-gray-300 p-4 sm:p-6">
|
||||
<div className="mb-6">
|
||||
<h3 className="text-base sm:text-lg font-medium mb-2" style={{ color: '#012068' }}>
|
||||
{getJobTitle()}
|
||||
</h3>
|
||||
{selectedJob && (
|
||||
<div className="flex flex-wrap items-center gap-3 text-xs" style={{ color: '#012068', opacity: 0.7 }}>
|
||||
<div className="flex items-center">
|
||||
<Building className="w-3 h-3 mr-1" />
|
||||
<span className="hidden sm:inline">{selectedJob.department}</span>
|
||||
<span className="sm:hidden">{selectedJob.department.split(' ')[0]}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<MapPin className="w-3 h-3 mr-1" />
|
||||
<span className="hidden sm:inline">{selectedJob.location}</span>
|
||||
<span className="sm:hidden">
|
||||
{selectedJob.location.includes('Vellore') ? 'Vellore' : selectedJob.location.split(',')[0]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{submitSuccess && (
|
||||
<div className="mb-6 bg-green-50 border border-green-200 rounded-lg p-4">
|
||||
<p className="text-green-800 text-sm font-medium">Application Submitted Successfully!</p>
|
||||
<p className="text-green-700 text-xs mt-1">We'll review your application and get back to you soon.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="fullName" className="block text-xs font-medium mb-2" style={{ color: '#012068' }}>
|
||||
Full Name *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="fullName"
|
||||
name="fullName"
|
||||
value={formData.fullName}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
disabled={submitting}
|
||||
className="w-full px-3 py-2 text-xs border border-gray-300 rounded focus:outline-none focus:ring-2 focus:border-transparent disabled:bg-gray-100"
|
||||
style={{ '--tw-ring-color': '#012068', color:'#333' } as React.CSSProperties}
|
||||
placeholder="Enter your full name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-xs font-medium mb-2" style={{ color: '#012068' }}>
|
||||
Email Address *
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
disabled={submitting}
|
||||
className="w-full px-3 py-2 text-xs border border-gray-300 rounded focus:outline-none focus:ring-2 focus:border-transparent disabled:bg-gray-100"
|
||||
style={{ '--tw-ring-color': '#012068', color:'#333' } as React.CSSProperties}
|
||||
placeholder="Enter your email address"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="phone" className="block text-xs font-medium mb-2" style={{ color: '#012068' }}>
|
||||
Phone Number *
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
name="phone"
|
||||
value={formData.phone}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
disabled={submitting}
|
||||
className="w-full px-3 py-2 text-xs border border-gray-300 rounded focus:outline-none focus:ring-2 focus:border-transparent disabled:bg-gray-100"
|
||||
style={{ '--tw-ring-color': '#012068', color:'#333' } as React.CSSProperties}
|
||||
placeholder="Enter your phone number"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="experience" className="block text-xs font-medium mb-2" style={{ color: '#012068' }}>
|
||||
Years of Experience *
|
||||
</label>
|
||||
<select
|
||||
id="experience"
|
||||
name="experience"
|
||||
value={formData.experience}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
disabled={submitting}
|
||||
className="w-full px-3 py-2 text-xs border border-gray-300 rounded focus:outline-none focus:ring-2 focus:border-transparent disabled:bg-gray-100"
|
||||
style={{ '--tw-ring-color': '#012068', color:'#333' } as React.CSSProperties}
|
||||
>
|
||||
<option value="">Select experience</option>
|
||||
<option value="0-1">0-1 years</option>
|
||||
<option value="1-3">1-3 years</option>
|
||||
<option value="3-5">3-5 years</option>
|
||||
<option value="5-10">5-10 years</option>
|
||||
<option value="10+">10+ years</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="resume" className="block text-xs font-medium mb-2" style={{ color: '#012068' }}>
|
||||
Resume/CV
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
id="resume"
|
||||
name="resume"
|
||||
onChange={handleFileChange}
|
||||
accept=".pdf,.doc,.docx"
|
||||
disabled={submitting}
|
||||
className="w-full px-3 py-2 text-xs border border-gray-300 rounded focus:outline-none focus:ring-2 focus:border-transparent disabled:bg-gray-100"
|
||||
style={{ '--tw-ring-color': '#012068', color:'#333' } as React.CSSProperties}
|
||||
/>
|
||||
<p className="text-xs mt-1" style={{ color: '#012068', opacity: 0.7 }}>
|
||||
Accepted formats: PDF, DOC, DOCX (Max 5MB)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="coverLetter" className="block text-xs font-medium mb-2" style={{ color: '#012068' }}>
|
||||
Cover Letter
|
||||
</label>
|
||||
<textarea
|
||||
id="coverLetter"
|
||||
name="coverLetter"
|
||||
value={formData.coverLetter}
|
||||
onChange={handleInputChange}
|
||||
rows={4}
|
||||
disabled={submitting}
|
||||
className="w-full px-3 py-2 text-xs border border-gray-300 rounded focus:outline-none focus:ring-2 focus:border-transparent disabled:bg-gray-100"
|
||||
style={{ '--tw-ring-color': '#012068', color:'#333' } as React.CSSProperties}
|
||||
placeholder="Tell us why you're interested in this position..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting || !selectedJob}
|
||||
className="w-full px-6 py-3 text-sm font-medium rounded hover:opacity-90 transition-opacity duration-300 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
style={{
|
||||
backgroundColor: '#012068',
|
||||
color: '#f4f4f4',
|
||||
'--tw-ring-color': '#012068'
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
{submitting ? 'Submitting...' : 'Submit Application'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Job Details */}
|
||||
{selectedJob && (
|
||||
<div className="mt-6 pt-6 border-t border-gray-300">
|
||||
<h4 className="text-base font-medium mb-3" style={{ color: '#012068' }}>Job Requirements</h4>
|
||||
<ul className="space-y-1">
|
||||
{selectedJob.requirements.slice(0, 3).map((req, index) => (
|
||||
<li key={index} className="text-sm flex items-start" style={{ color: '#333' }}>
|
||||
<span className="mr-2" style={{ color: '#e64838' }}>•</span>
|
||||
{req}
|
||||
</li>
|
||||
))}
|
||||
{selectedJob.requirements.length > 3 && (
|
||||
<li className="text-sm text-gray-500">
|
||||
+{selectedJob.requirements.length - 3} more requirements
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Career;
|
||||
Reference in New Issue
Block a user