first commit

This commit is contained in:
2025-10-09 20:05:39 +05:30
commit d4fcb658e3
69 changed files with 13582 additions and 0 deletions

View 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;