402 lines
14 KiB
TypeScript
402 lines
14 KiB
TypeScript
// components/CourseDetail.tsx
|
||
'use client';
|
||
import { useState, useEffect } from 'react';
|
||
import Link from 'next/link';
|
||
import {
|
||
ChevronRight,
|
||
Clock,
|
||
Users,
|
||
Calendar,
|
||
User,
|
||
ExternalLink
|
||
} from 'lucide-react';
|
||
import { educationService, CourseApplicationData } from '../../services/educationService';
|
||
import { fileUploadService } from '../../services/fileUploadService';
|
||
|
||
interface CourseDetailProps {
|
||
courseId?: string;
|
||
}
|
||
|
||
const CourseDetail: React.FC<CourseDetailProps> = ({ courseId }) => {
|
||
const [mounted, setMounted] = useState(false);
|
||
const [course, setCourse] = useState<any>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [submitting, setSubmitting] = useState(false);
|
||
const [submitSuccess, setSubmitSuccess] = useState(false);
|
||
|
||
// Application form data
|
||
const [formData, setFormData] = useState({
|
||
fullName: '',
|
||
email: '',
|
||
phone: '',
|
||
qualification: '',
|
||
experience: '',
|
||
coverLetter: '',
|
||
resume: null as File | null
|
||
});
|
||
|
||
useEffect(() => {
|
||
setMounted(true);
|
||
|
||
// Get course ID from URL params if not provided as prop
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const id = courseId || urlParams.get('id');
|
||
|
||
if (id) {
|
||
loadCourseDetail(parseInt(id));
|
||
} else {
|
||
setError('Course ID not provided');
|
||
setLoading(false);
|
||
}
|
||
}, [courseId]);
|
||
|
||
const loadCourseDetail = async (id: number) => {
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const courseData = await educationService.getCourseById(id);
|
||
if (courseData) {
|
||
setCourse(courseData);
|
||
} else {
|
||
setError('Course not found');
|
||
}
|
||
} catch (err) {
|
||
setError('Failed to load course details');
|
||
console.error('Error loading course:', err);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
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 (!course) {
|
||
alert('Course not found.');
|
||
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.warn('Resume upload failed, submitting application without resume:', uploadError);
|
||
resumeUrl = '';
|
||
}
|
||
}
|
||
|
||
// Prepare application data
|
||
const applicationData: CourseApplicationData = {
|
||
courseId: parseInt(course.id),
|
||
fullName: formData.fullName,
|
||
email: formData.email,
|
||
phone: formData.phone,
|
||
qualification: formData.qualification,
|
||
experience: formData.experience || undefined,
|
||
coverLetter: formData.coverLetter || undefined,
|
||
resumeUrl: resumeUrl || undefined
|
||
};
|
||
|
||
// Submit application
|
||
const success = await educationService.submitApplication(applicationData);
|
||
|
||
if (success) {
|
||
setSubmitSuccess(true);
|
||
// Reset form
|
||
setFormData({
|
||
fullName: '',
|
||
email: '',
|
||
phone: '',
|
||
qualification: '',
|
||
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);
|
||
}
|
||
};
|
||
|
||
if (!mounted) {
|
||
return null;
|
||
}
|
||
|
||
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 course details...</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (error || !course) {
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center">
|
||
<div className="text-center">
|
||
<p className="text-red-600 text-xl mb-4">⚠️ {error || 'Course not found'}</p>
|
||
<Link href="/education-training" className="text-blue-600 hover:underline">
|
||
← Back to Education & Training
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-white">
|
||
{/* Header Section with Background Pattern */}
|
||
<section
|
||
className="py-8 relative overflow-hidden"
|
||
style={{ backgroundColor: '#f4f4f4' }}
|
||
>
|
||
<div className="max-w-7xl mx-auto px-4 relative z-10">
|
||
{/* Breadcrumb */}
|
||
<nav className="flex items-center space-x-2 text-sm mb-6">
|
||
<Link
|
||
href="/"
|
||
className="hover:opacity-70 transition-opacity duration-200"
|
||
style={{ color: '#012068' }}
|
||
>
|
||
Home
|
||
</Link>
|
||
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
|
||
<Link
|
||
href="/education-training"
|
||
className="hover:opacity-70 transition-opacity duration-200"
|
||
style={{ color: '#012068' }}
|
||
>
|
||
Education & Training
|
||
</Link>
|
||
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
|
||
<span className="font-medium" style={{ color: '#e64838' }}>
|
||
Course Details
|
||
</span>
|
||
</nav>
|
||
|
||
{/* Course Title and Meta */}
|
||
<div className="mb-8">
|
||
<div className="flex items-center mb-4">
|
||
<h1 className="text-4xl font-bold" style={{ color: '#012068' }}>
|
||
{course.title}
|
||
</h1>
|
||
</div>
|
||
<div className="flex flex-wrap items-center gap-6 text-lg mb-6" style={{ color: '#012068' }}>
|
||
<div className="flex items-center">
|
||
<Clock className="w-5 h-5 mr-2" />
|
||
<span>Duration: {course.duration}</span>
|
||
</div>
|
||
<div className="flex items-center">
|
||
<Users className="w-5 h-5 mr-2" />
|
||
<span>No of seats: {course.seats}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* Course Info Bar */}
|
||
<section className="py-6 border-b border-gray-200">
|
||
<div className="max-w-7xl mx-auto px-4">
|
||
<div className="flex flex-wrap items-center justify-between gap-4">
|
||
<div className="flex flex-wrap items-center gap-6 text-sm">
|
||
<div className="flex items-center">
|
||
<User className="w-4 h-4 mr-2" style={{ color: '#012068' }} />
|
||
<span style={{ color: '#666' }}>Instructor: </span>
|
||
<span className="font-medium" style={{ color: '#012068' }}>
|
||
{course.instructor}
|
||
</span>
|
||
</div>
|
||
{course.startDate && (
|
||
<div className="flex items-center">
|
||
<Calendar className="w-4 h-4 mr-2" style={{ color: '#012068' }} />
|
||
<span style={{ color: '#666' }}>Start Date: </span>
|
||
<span className="font-medium" style={{ color: '#012068' }}>
|
||
{new Date(course.startDate).toLocaleDateString()}
|
||
</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
<div className="flex gap-3">
|
||
<button
|
||
className="px-6 py-2 text-sm font-medium rounded border-2 hover:opacity-70 transition-opacity duration-300"
|
||
style={{
|
||
borderColor: '#012068',
|
||
color: '#012068'
|
||
}}
|
||
>
|
||
Download Brochure
|
||
</button>
|
||
<button
|
||
className="px-6 py-2 mr-2 text-sm font-medium rounded hover:opacity-90 transition-opacity duration-300"
|
||
style={{
|
||
backgroundColor: '#012068',
|
||
color: 'white'
|
||
}}
|
||
onClick={() => {
|
||
document.getElementById('applicationForm')?.scrollIntoView({ behavior: 'smooth' });
|
||
}}
|
||
>
|
||
Apply Now
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* Main Content - Full Width */}
|
||
<section className="py-8">
|
||
<div className="max-w-7xl mx-auto px-4">
|
||
|
||
{/* Left Column - Course Information */}
|
||
<div>
|
||
<div className="bg-white rounded-lg border border-gray-200 p-8 space-y-12">
|
||
|
||
{/* Overview Section */}
|
||
<div>
|
||
<h2 className="text-2xl font-bold mb-6" style={{ color: '#012068' }}>
|
||
Overview
|
||
</h2>
|
||
<div className="prose max-w-none">
|
||
<p className="text-base leading-relaxed mb-6" style={{ color: '#666' }}>
|
||
{course.description}
|
||
</p>
|
||
|
||
{course.objectives && course.objectives.length > 0 && (
|
||
<>
|
||
<h3 className="text-xl font-semibold mb-4" style={{ color: '#012068' }}>
|
||
Learning Objectives
|
||
</h3>
|
||
<p className="text-sm mb-4" style={{ color: '#666' }}>
|
||
The main objectives of the program can be summarised as follows:
|
||
</p>
|
||
<ul className="space-y-3">
|
||
{course.objectives.map((objective: string, index: number) => (
|
||
<li key={index} className="flex items-start">
|
||
<span className="text-sm font-medium mr-3" style={{ color: '#012068' }}>
|
||
•
|
||
</span>
|
||
<span className="text-sm leading-relaxed" style={{ color: '#666' }}>
|
||
{objective}
|
||
</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Eligibility Criteria Section */}
|
||
{course.eligibility && course.eligibility.length > 0 && (
|
||
<div>
|
||
<h2 className="text-2xl font-bold mb-6" style={{ color: '#012068' }}>
|
||
Eligibility Criteria
|
||
</h2>
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full border border-gray-200 rounded-lg">
|
||
<thead>
|
||
<tr style={{ backgroundColor: '#f8f9fa' }}>
|
||
<th
|
||
className="px-6 py-4 text-left text-sm font-semibold border-b border-gray-200"
|
||
style={{ color: '#012068' }}
|
||
>
|
||
Sr. No.
|
||
</th>
|
||
<th
|
||
className="px-6 py-4 text-left text-sm font-semibold border-b border-gray-200"
|
||
style={{ color: '#012068' }}
|
||
>
|
||
Eligibility Requirements
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{course.eligibility.map((criteria: string, index: number) => (
|
||
<tr key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
||
<td className="px-6 py-4 text-sm border-b border-gray-200" style={{ color: '#012068' }}>
|
||
{index + 1}
|
||
</td>
|
||
<td className="px-6 py-4 text-sm border-b border-gray-200" style={{ color: '#666' }}>
|
||
{criteria}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* How to Apply Section */}
|
||
<div>
|
||
<h2 className="text-2xl font-bold mb-6" style={{ color: '#012068' }}>
|
||
How to Apply
|
||
</h2>
|
||
|
||
<div className="mb-6">
|
||
<p className="text-sm mb-2" style={{ color: '#666' }}>
|
||
Use the application form on the right to apply for this course, or visit our admissions website:
|
||
</p>
|
||
<a
|
||
href="mailto:traumasurg@cmcvellore.ac.in"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="inline-flex items-center text-sm font-medium hover:opacity-70 transition-opacity duration-200"
|
||
style={{ color: '#e64838' }}
|
||
>
|
||
traumasurg@cmcvellore.ac.in
|
||
<ExternalLink className="w-4 h-4 ml-1" />
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default CourseDetail; |