From d375cad253d81ca73c680e23116a6f5a2cce1ccc Mon Sep 17 00:00:00 2001 From: mukeshs Date: Fri, 10 Oct 2025 17:48:11 +0530 Subject: [PATCH] Image error resolve --- .../education/EducationTraining.tsx | 655 +++++++++++------- src/services/educationService.ts | 80 +-- 2 files changed, 449 insertions(+), 286 deletions(-) diff --git a/src/components/education/EducationTraining.tsx b/src/components/education/EducationTraining.tsx index 3b0853b..d173809 100644 --- a/src/components/education/EducationTraining.tsx +++ b/src/components/education/EducationTraining.tsx @@ -1,254 +1,417 @@ -// services/educationService.ts -export interface ApiCourse { - id: number; - title: string; - description: string; - duration: string; - seats: number; - category: string; - level: string; - instructor: string; - price?: string; - startDate?: string; - imageUrl?: string; - eligibility: string[]; - objectives: string[]; - isActive: boolean; - createdDate?: string; - updatedDate?: string; -} +// components/EducationTraining.tsx +'use client'; +import { useState, useEffect } from 'react'; +import Image from 'next/image'; +import Link from 'next/link'; +import { ChevronRight, Clock, Users, Award, Calendar } from 'lucide-react'; +import { educationService, Course } from '../../services/educationService'; +import { upcomingEventsService, UpcomingEvent } from '../../services/upcomingEventsService'; -export interface Course { - id: string; - title: string; - description: string; - duration: string; - seats: number; - category: string; - level: string; - instructor: string; - price: string; - startDate: string; - image: string; - eligibility: string[]; - objectives: string[]; -} +const EducationTraining: React.FC = () => { + const [mounted, setMounted] = useState(false); + const [courses, setCourses] = useState([]); + const [upcomingEvents, setUpcomingEvents] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [selectedCategory, setSelectedCategory] = useState('All'); + const [searchQuery, setSearchQuery] = useState(''); -export interface CourseApplicationData { - courseId: number; - fullName: string; - email: string; - phone: string; - qualification: string; - experience?: string; - coverLetter?: string; - resumeUrl?: string; -} + const categories = ['All', 'Certification', 'Training', 'Workshop', 'Fellowship']; -class EducationService { - private apiBaseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080'; + useEffect(() => { + setMounted(true); + loadData(); + }, []); - // Helper method to convert relative URLs to absolute URLs - private getFullImageUrl(imageUrl: string | undefined): string { - if (!imageUrl || imageUrl.trim() === '') { - return this.getRandomDefaultImage(); - } - - // If it's already a full URL, return as-is - if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) { - return imageUrl; - } - - // If it's a relative URL, prepend the backend API URL - const cleanUrl = imageUrl.startsWith('/') ? imageUrl.substring(1) : imageUrl; - return `${this.apiBaseUrl}/${cleanUrl}`; - } - - async getActiveCourses(): Promise { - try { - const response = await fetch(`${this.apiBaseUrl}/api/courses/active`, { - cache: 'no-store' - }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const apiCourses: ApiCourse[] = await response.json(); - return this.transformApiCoursesToCourses(apiCourses); - } catch (error) { - console.error('Error fetching courses:', error); - return this.getFallbackCourses(); - } - } - - async getCourseById(id: number): Promise { - try { - const token = typeof window !== 'undefined' ? localStorage.getItem('authToken') : null; - const response = await fetch(`${this.apiBaseUrl}/api/courses/${id}`, { - headers: { - 'Content-Type': 'application/json', - ...(token ? { Authorization: `Bearer ${token}` } : {}), - }, - cache: 'no-store' - }); - - if (!response.ok) { - if (response.status === 404) return null; - throw new Error(`HTTP error! status: ${response.status}`); - } - - const apiCourse: ApiCourse = await response.json(); - return this.transformApiCourseToCourse(apiCourse); - } catch (error) { - console.error(`Error fetching course ${id}:`, error); - return null; - } - } - - async submitApplication(applicationData: CourseApplicationData): Promise { - try { - const response = await fetch(`${this.apiBaseUrl}/api/course-applications`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(applicationData), - }); - - return response.ok; - } catch (error) { - console.error('Error submitting application:', error); - return false; - } - } - - private transformApiCoursesToCourses(apiCourses: ApiCourse[]): Course[] { - return apiCourses.map(apiCourse => this.transformApiCourseToCourse(apiCourse)); - } - - private transformApiCourseToCourse(apiCourse: ApiCourse): Course { - return { - id: apiCourse.id.toString(), - title: apiCourse.title, - description: apiCourse.description, - duration: apiCourse.duration, - seats: apiCourse.seats, - category: apiCourse.category, - level: apiCourse.level, - instructor: apiCourse.instructor, - price: apiCourse.price || 'N/A', - startDate: apiCourse.startDate || '', - image: this.getFullImageUrl(apiCourse.imageUrl), - eligibility: apiCourse.eligibility || [], - objectives: apiCourse.objectives || [] + const loadData = async () => { + try { + setLoading(true); + setError(null); + + // Load both courses and upcoming events concurrently + const [fetchedCourses, fetchedEvents] = await Promise.all([ + educationService.getActiveCourses(), + upcomingEventsService.getActiveUpcomingEvents() + ]); + + setCourses(fetchedCourses); + setUpcomingEvents(fetchedEvents); + } catch (err) { + setError('Failed to load courses and events. Please try again later.'); + console.error('Error loading data:', err); + } finally { + setLoading(false); + } }; - } - private getRandomDefaultImage(): string { - const defaultImages = [ - "https://images.unsplash.com/photo-1576091160550-2173dba999ef?w=400&h=200&fit=crop&crop=center", - "https://images.unsplash.com/photo-1559757175-0eb30cd8c063?w=400&h=300&fit=crop&crop=center", - "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=400&h=300&fit=crop&crop=center", - "https://images.unsplash.com/photo-1582750433449-648ed127bb54?w=400&h=300&fit=crop&crop=center", - "https://images.unsplash.com/photo-1551601651-2a8555f1a136?w=400&h=300&fit=crop&crop=center", - "https://images.unsplash.com/photo-1559757148-5c350d0d3c56?w=400&h=300&fit=crop&crop=center" - ]; - return defaultImages[Math.floor(Math.random() * defaultImages.length)]; - } + if (!mounted) { + return null; + } - private getFallbackCourses(): Course[] { - return [ - { - id: '1', - title: "ATLS® (Advanced Trauma Life Support)", - description: "Eligibility: MBBS + internship complete. Last Course: Aug 31 – Sep 2, 2023 (60 doctors certified). Next Schedule: [#Incomplete – Date TBD]", - duration: "3 Days", - seats: 60, - category: "Certification", - level: "Professional", - image: "https://images.unsplash.com/photo-1576091160550-2173dba999ef?w=400&h=200&fit=crop&crop=center", - instructor: "Trauma Faculty Team", - price: "N/A", - startDate: "2023-08-31", - eligibility: ["MBBS + internship complete"], - objectives: ["Advanced trauma life support skills", "Emergency trauma management"] - }, - { - id: '2', - title: "ATCN® (Advanced Trauma Care for Nurses)", - description: "First Course: Apr 11-13, 2024 (manikin-based training). Participants: 40 critical care nurses from CMC and partner hospitals. Next Batch: [#Incomplete – Date TBD]", - duration: "3 Days", - seats: 40, - category: "Training", - level: "Professional", - image: "https://images.unsplash.com/photo-1559757175-0eb30cd8c063?w=400&h=300&fit=crop&crop=center", - instructor: "Nursing Faculty Team", - price: "N/A", - startDate: "2024-04-11", - eligibility: ["Registered Nurse", "Critical care experience preferred"], - objectives: ["Advanced trauma nursing skills", "Manikin-based training proficiency"] - }, - { - id: '3', - title: "Trauma First Responder Program", - description: "Partners: RCPSG Hope Foundation, local colleges. Locations: Walajapet, Auxilium College—250 students trained. Curriculum: CPR, airway support, bleeding control, scene assessment.", - duration: "Varies", - seats: 250, - category: "Workshop", - level: "Beginner", - image: "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=400&h=300&fit=crop&crop=center", - instructor: "Community Trainers", - price: "N/A", - startDate: "2023-01-01", - eligibility: ["Students", "Community members"], - objectives: ["CPR proficiency", "Basic trauma response", "Scene safety assessment"] - }, - { - id: '4', - title: "FNB in Trauma Surgery", - description: "3-year structured training program in acute surgery, ICU management, and research. Open to MS-qualified surgeons seeking specialized trauma surgery expertise.", - duration: "3 Years", - seats: 8, - category: "Certification", - level: "Advanced", - image: "https://images.unsplash.com/photo-1582750433449-648ed127bb54?w=400&h=300&fit=crop&crop=center", - instructor: "Senior Trauma Surgeons", - price: "N/A", - startDate: "2025-07-01", - eligibility: ["MS qualification in Surgery", "Valid medical license"], - objectives: ["Advanced trauma surgery skills", "ICU management", "Research methodology"] - }, - { - id: '5', - title: "Observerships & Electives", - description: "4-8 week clinical blocks for national and international residents. Includes ATLS® course access and hands-on trauma experience. Application by email required.", - duration: "4-8 Weeks", - seats: 20, - category: "Training", - level: "Intermediate", - image: "https://images.unsplash.com/photo-1551601651-2a8555f1a136?w=400&h=300&fit=crop&crop=center", - instructor: "Clinical Faculty", - price: "N/A", - startDate: "2025-01-15", - eligibility: ["Medical residency status", "Valid medical credentials"], - objectives: ["Clinical observation skills", "Hands-on trauma experience", "ATLS certification"] - }, - { - id: '6', - title: "Nursing Skills Lab", - description: "Trauma-focused skills laboratory sessions open quarterly (Q2, Q4). Includes chest tube insertion, airway management drills, and EFAST simulation training.", - duration: "2 Days", - seats: 30, - category: "Workshop", - level: "Intermediate", - image: "https://images.unsplash.com/photo-1559757148-5c350d0d3c56?w=400&h=300&fit=crop&crop=center", - instructor: "Nursing Skills Faculty", - price: "N/A", - startDate: "2025-04-01", - eligibility: ["Licensed nurse", "Basic trauma knowledge"], - objectives: ["Chest tube insertion", "Airway management", "EFAST simulation"] - } - ]; - } -} + // Filter courses based on category and search + const filteredCourses = courses.filter(course => { + const matchesCategory = selectedCategory === 'All' || course.category === selectedCategory; + const matchesSearch = !searchQuery.trim() || + course.title.toLowerCase().includes(searchQuery.toLowerCase()) || + course.description.toLowerCase().includes(searchQuery.toLowerCase()) || + course.instructor.toLowerCase().includes(searchQuery.toLowerCase()); + + return matchesCategory && matchesSearch; + }); -export const educationService = new EducationService(); \ No newline at end of file + const handleSearchChange = (e: React.ChangeEvent) => { + setSearchQuery(e.target.value); + }; + + if (loading) { + return ( +
+
+
+

Loading courses...

+
+
+ ); + } + + return ( +
+ {/* Header Section */} +
+
+ {/* Breadcrumb */} + + + {/* Page Header */} +
+

+ Education & Training +

+

+ Advance your trauma care expertise with structured training programs for doctors, nurses, students, and community partners. +

+
+
+
+ + {/* Error Message */} + {error && ( +
+
+

{error}

+ +
+
+ )} + + {/* Upcoming Training Section with Dynamic Cards */} +
+
+

+ Upcoming Training Programs +

+
+ {upcomingEvents.length > 0 ? ( + upcomingEvents.map((event) => ( +
+
+
+

+ {event.title} +

+
+ + {event.schedule} +
+
+
+

+ {event.description} +

+
+ )) + ) : ( + // Fallback to static cards if no events are loaded + <> +
+
+
+

+ Simulation-based Team Drills +

+
+ + Q3 2025 +
+
+
+

+ Hands-on simulation training designed to improve team coordination and emergency response in high-pressure trauma situations. +

+
+ +
+
+
+

+ Online Webinar Series +

+
+ + Monthly Sessions +
+
+
+

+ Monthly online sessions covering trauma ethics, young doctor support, and professional development in emergency medicine. +

+
+ +
+
+
+

+ Community Education +

+
+ + Ongoing +
+
+
+

+ Road safety fairs and school education sessions to promote trauma prevention and basic first aid awareness in the community. +

+
+ + )} +
+
+
+ + {/* Filter & Search */} +
+
+
+ {/* Category Filter */} +
+ {categories.map((category) => ( + + ))} +
+ + {/* Search */} +
+ + +
+
+ + {/* Results Info */} + {(selectedCategory !== 'All' || searchQuery.trim()) && ( +
+

+ {filteredCourses.length === 0 + ? 'No courses found matching your criteria.' + : `Showing ${filteredCourses.length} of ${courses.length} course${courses.length !== 1 ? 's' : ''}` + } +

+
+ )} +
+
+ + {/* Courses Grid */} +
+
+ {filteredCourses.length === 0 && !error ? ( +
+

+ {searchQuery.trim() || selectedCategory !== 'All' + ? 'No courses match your search criteria.' + : 'No courses available at the moment.' + } +

+ {(searchQuery.trim() || selectedCategory !== 'All') && ( + + )} +
+ ) : ( +
+ {filteredCourses.map((course) => ( + + {/* Image */} +
+ {course.title} +
+ + {/* Content */} +
+
+

+ {course.title} +

+

+ {course.description} +

+
+ + {course.category} + +
+
+ +
+
+ + {course.instructor} + +
+
+
+ + {course.duration} +
+
+ + {course.seats} +
+
+
+
+ {course.startDate ? `Starts: ${new Date(course.startDate).toLocaleDateString()}` : 'Contact for dates'} +
+
+
+
+ + ))} +
+ )} +
+
+ + {/* CTA */} +
+
+ +

+ Ready to Advance Your Trauma Care Expertise? +

+

+ Join our structured training programs designed to empower healthcare professionals, nurses, and community responders with critical trauma care skills. +

+
+ + Contact Admissions + + + Download Brochure + +
+
+
+
+ ); +}; + +export default EducationTraining; \ No newline at end of file diff --git a/src/services/educationService.ts b/src/services/educationService.ts index 25a204e..3b0853b 100644 --- a/src/services/educationService.ts +++ b/src/services/educationService.ts @@ -48,9 +48,27 @@ export interface CourseApplicationData { class EducationService { private apiBaseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080'; + // Helper method to convert relative URLs to absolute URLs + private getFullImageUrl(imageUrl: string | undefined): string { + if (!imageUrl || imageUrl.trim() === '') { + return this.getRandomDefaultImage(); + } + + // If it's already a full URL, return as-is + if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) { + return imageUrl; + } + + // If it's a relative URL, prepend the backend API URL + const cleanUrl = imageUrl.startsWith('/') ? imageUrl.substring(1) : imageUrl; + return `${this.apiBaseUrl}/${cleanUrl}`; + } + async getActiveCourses(): Promise { try { - const response = await fetch(`${this.apiBaseUrl}/api/courses/active`); + const response = await fetch(`${this.apiBaseUrl}/api/courses/active`, { + cache: 'no-store' + }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -58,33 +76,33 @@ class EducationService { return this.transformApiCoursesToCourses(apiCourses); } catch (error) { console.error('Error fetching courses:', error); - return this.getFallbackCourses(); // Return fallback data if API fails + return this.getFallbackCourses(); } } async getCourseById(id: number): Promise { - try { - const token = localStorage.getItem('authToken'); // Or from cookies/session - const response = await fetch(`${this.apiBaseUrl}/api/courses/${id}`, { - headers: { - 'Content-Type': 'application/json', - ...(token ? { Authorization: `Bearer ${token}` } : {}), - }, - }); + try { + const token = typeof window !== 'undefined' ? localStorage.getItem('authToken') : null; + const response = await fetch(`${this.apiBaseUrl}/api/courses/${id}`, { + headers: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + cache: 'no-store' + }); - if (!response.ok) { - if (response.status === 404) return null; - throw new Error(`HTTP error! status: ${response.status}`); + if (!response.ok) { + if (response.status === 404) return null; + throw new Error(`HTTP error! status: ${response.status}`); + } + + const apiCourse: ApiCourse = await response.json(); + return this.transformApiCourseToCourse(apiCourse); + } catch (error) { + console.error(`Error fetching course ${id}:`, error); + return null; } - - const apiCourse: ApiCourse = await response.json(); - return this.transformApiCourseToCourse(apiCourse); - } catch (error) { - console.error(`Error fetching course ${id}:`, error); - return null; } -} - async submitApplication(applicationData: CourseApplicationData): Promise { try { @@ -119,29 +137,12 @@ class EducationService { instructor: apiCourse.instructor, price: apiCourse.price || 'N/A', startDate: apiCourse.startDate || '', - image: this.getImageUrl(apiCourse.imageUrl), + image: this.getFullImageUrl(apiCourse.imageUrl), eligibility: apiCourse.eligibility || [], objectives: apiCourse.objectives || [] }; } - private getImageUrl(imageUrl?: string): string { - if (imageUrl) { - // If imageUrl starts with /uploads/, prepend the full API path - if (imageUrl.startsWith('/uploads/')) { - return `${this.apiBaseUrl}/api/files${imageUrl}`; // This adds /api/files before /uploads/ - } - // If it's already a full URL, return as is - if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) { - return imageUrl; - } - // Otherwise, assume it's a relative path and prepend base URL with API path - return `${this.apiBaseUrl}/api/files/${imageUrl}`; - } - // Return random default image if no imageUrl provided - return this.getRandomDefaultImage(); -} - private getRandomDefaultImage(): string { const defaultImages = [ "https://images.unsplash.com/photo-1576091160550-2173dba999ef?w=400&h=200&fit=crop&crop=center", @@ -155,7 +156,6 @@ class EducationService { } private getFallbackCourses(): Course[] { - // Return the original hardcoded courses as fallback return [ { id: '1',