Files
cmc_frontend/src/components/education/CourseDetail.tsx
2026-02-11 11:05:00 +05:30

402 lines
14 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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