425 lines
20 KiB
TypeScript
425 lines
20 KiB
TypeScript
'use client'
|
|
import React, { useState, useEffect } from 'react';
|
|
import Link from 'next/link';
|
|
import {
|
|
Phone,
|
|
Mail,
|
|
Share,
|
|
ChevronRight,
|
|
Check,
|
|
} from 'lucide-react';
|
|
|
|
import { TeamMember, FacultyService } from '../../lib/facultyData';
|
|
|
|
interface TeamMemberDetailProps {
|
|
memberId: number;
|
|
memberData?: TeamMember;
|
|
}
|
|
|
|
const TeamMemberDetail: React.FC<TeamMemberDetailProps> = ({ memberId, memberData }) => {
|
|
const [member, setMember] = useState<TeamMember | null>(memberData || null);
|
|
const [loading, setLoading] = useState(!memberData);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [showSocialShare, setShowSocialShare] = useState(false);
|
|
const [imageError, setImageError] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const loadMemberData = async () => {
|
|
if (memberData) return; // Already have data
|
|
|
|
try {
|
|
setLoading(true);
|
|
const fetchedMember = await FacultyService.getFacultyById(memberId);
|
|
if (fetchedMember) {
|
|
setMember(fetchedMember);
|
|
} else {
|
|
setError('Faculty member not found');
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to load faculty member:', err);
|
|
setError('Failed to load faculty member data');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
loadMemberData();
|
|
}, [memberId, memberData]);
|
|
|
|
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-600 mx-auto mb-4"></div>
|
|
<p>Loading faculty member details...</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error || !member) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<div className="text-center">
|
|
<p className="text-red-600 mb-4">{error || 'Faculty member not found'}</p>
|
|
<Link
|
|
href="/teamMember"
|
|
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
|
|
>
|
|
Back to Faculty
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const isTrainee = member.category === 'TRAINEE_FELLOW';
|
|
|
|
// Check if image URL is valid and not a default placeholder
|
|
const hasValidImage = member.image &&
|
|
member.image.trim() !== '' &&
|
|
!member.image.includes('default-avatar') &&
|
|
!member.image.includes('placeholder.') &&
|
|
!member.image.includes('robot') &&
|
|
// Only filter if URL ENDS with /profile-image (backend default endpoint)
|
|
!(member.image.endsWith('/profile-image') ||
|
|
member.image.includes('/profile-image?') ||
|
|
member.image === 'https://via.placeholder.com/400');
|
|
|
|
const shouldShowImage = !isTrainee && hasValidImage && !imageError;
|
|
|
|
// Create a comprehensive description from the member's details
|
|
const getFullDescription = () => {
|
|
return member.description || `${member.name} is a dedicated member of our ${isTrainee ? 'trainee program' : 'faculty'} with expertise in ${member.specialty?.toLowerCase() || 'medical practice'}. They bring valuable experience in ${isTrainee ? 'clinical training' : 'surgical education'}, patient care, and clinical research to Christian Medical College, Vellore.`;
|
|
};
|
|
|
|
// Parse phone numbers if multiple are provided
|
|
const getFormattedPhone = () => {
|
|
if (!member.phone || member.phone === 'Not available') {
|
|
return 'Contact through main office';
|
|
}
|
|
return member.phone.includes(',')
|
|
? member.phone.split(',').map(p => p.trim()).join(' / ')
|
|
: member.phone;
|
|
};
|
|
|
|
const handleImageError = (e: React.SyntheticEvent<HTMLImageElement>) => {
|
|
console.error('Image failed to load:', member.image);
|
|
setImageError(true);
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-white">
|
|
{/* 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' }} />
|
|
<Link
|
|
href="/teamMember"
|
|
className="hover:opacity-70 transition-opacity duration-200"
|
|
style={{ color: '#012068' }}
|
|
>
|
|
Faculty
|
|
</Link>
|
|
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
|
|
<span className="font-medium" style={{ color: '#e64838' }}>
|
|
{member.name}
|
|
</span>
|
|
</nav>
|
|
|
|
{/* Page Header */}
|
|
<div className="mt-6">
|
|
<div className="flex items-center mb-4">
|
|
<h1 className="text-3xl font-bold" style={{ color: '#012068' }}>
|
|
{member.name}
|
|
</h1>
|
|
{isTrainee && (
|
|
<span className="ml-4 px-3 py-1 text-xs font-medium rounded-full bg-gray-200 text-gray-700">
|
|
Trainee/Fellow
|
|
</span>
|
|
)}
|
|
</div>
|
|
<p className="text-base max-w-2xl leading-relaxed" style={{ color: '#333' }}>
|
|
{member.designation || member.position} {member.experience && `with ${member.experience} of experience`} at CMC Vellore
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Main Content */}
|
|
<div className="max-w-7xl mx-auto px-4 py-8 bg-white">
|
|
<div className="grid grid-cols-1 xl:grid-cols-4 gap-6 lg:gap-8">
|
|
{/* Sidebar */}
|
|
<div className="xl:col-span-1">
|
|
<div className="bg-white rounded-md border border-gray-300 overflow-hidden sticky top-8">
|
|
{/* Profile Image or Avatar */}
|
|
<div className="aspect-square relative overflow-hidden bg-gray-50">
|
|
{shouldShowImage ? (
|
|
<img
|
|
src={member.image}
|
|
alt={member.name}
|
|
className="w-full h-full object-cover transform hover:scale-105 transition-transform duration-300"
|
|
onError={handleImageError}
|
|
/>
|
|
) : (
|
|
<div
|
|
className="w-full h-full flex items-center justify-center"
|
|
style={{ backgroundColor: '#e0e5eb' }}
|
|
>
|
|
<svg
|
|
className="w-48 h-48 text-gray-400"
|
|
fill="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
|
|
</svg>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Profile Info */}
|
|
<div className="p-4 sm:p-6">
|
|
<div className="mb-6">
|
|
<div
|
|
className="inline-flex items-center py-1 text-sm font-medium mb-3"
|
|
style={{ color: '#012068' }}
|
|
>
|
|
{member.designation || member.position}
|
|
</div>
|
|
<h2 className="text-xl font-bold mb-2" style={{ color: '#012068' }}>{member.name}</h2>
|
|
</div>
|
|
|
|
<p className="text-sm mb-6 leading-relaxed" style={{ color: '#333' }}>
|
|
{member.specialty || `${member.name} is a dedicated ${isTrainee ? 'trainee' : 'faculty member'} at CMC Vellore.`}
|
|
</p>
|
|
|
|
<div className="space-y-4 mb-6">
|
|
<div className="flex items-start">
|
|
<Phone className="w-4 h-4 mr-3 mt-0.5 flex-shrink-0" style={{ color: '#e64838' }} />
|
|
<div>
|
|
<div className="text-sm font-medium mb-1" style={{ color: '#012068' }}>Phone</div>
|
|
<div className="text-sm" style={{ color: '#333' }}>{getFormattedPhone()}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-start">
|
|
<Mail className="w-4 h-4 mr-3 mt-0.5 flex-shrink-0" style={{ color: '#e64838' }} />
|
|
<div>
|
|
<div className="text-sm font-medium mb-1" style={{ color: '#012068' }}>Email</div>
|
|
<a href={`mailto:${member.email}`} className="text-sm hover:underline transition-colors" style={{ color: '#e64838' }}>
|
|
{member.email}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{member.experience && (
|
|
<div className="flex items-start">
|
|
<div
|
|
className="w-4 h-4 rounded-full mr-3 mt-0.5 flex-shrink-0 flex items-center justify-center"
|
|
style={{ backgroundColor: '#e64838' }}
|
|
>
|
|
<div className="w-2 h-2 bg-white rounded-full"></div>
|
|
</div>
|
|
<div>
|
|
<div className="text-sm font-medium mb-1" style={{ color: '#012068' }}>Experience</div>
|
|
<div className="text-sm" style={{ color: '#333' }}>{member.experience}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{member.department && (
|
|
<div className="flex items-start">
|
|
<div
|
|
className="w-4 h-4 rounded-full mr-3 mt-0.5 flex-shrink-0 flex items-center justify-center"
|
|
style={{ backgroundColor: '#012068' }}
|
|
>
|
|
<div className="w-2 h-2 bg-white rounded-full"></div>
|
|
</div>
|
|
<div>
|
|
<div className="text-sm font-medium mb-1" style={{ color: '#012068' }}>Department</div>
|
|
<div className="text-sm" style={{ color: '#333' }}>{member.department}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{member.officeLocation && (
|
|
<div className="flex items-start">
|
|
<div
|
|
className="w-4 h-4 rounded-full mr-3 mt-0.5 flex-shrink-0 flex items-center justify-center"
|
|
style={{ backgroundColor: '#012068' }}
|
|
>
|
|
<div className="w-2 h-2 bg-white rounded-full"></div>
|
|
</div>
|
|
<div>
|
|
<div className="text-sm font-medium mb-1" style={{ color: '#012068' }}>Office Location</div>
|
|
<div className="text-sm" style={{ color: '#333' }}>{member.officeLocation}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Social Share */}
|
|
<div className="relative">
|
|
<button
|
|
onClick={() => setShowSocialShare(!showSocialShare)}
|
|
className="flex items-center justify-center w-full px-4 py-3 rounded-lg transition-opacity font-medium text-sm hover:opacity-90"
|
|
style={{ backgroundColor: '#f4f4f4', color: '#012068' }}
|
|
>
|
|
<Share className="w-4 h-4 mr-2" />
|
|
Share Profile
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Content */}
|
|
<div className="xl:col-span-3">
|
|
<div className="bg-white rounded-md border border-gray-300 overflow-hidden">
|
|
{/* Personal Info */}
|
|
<div className="p-6 sm:p-8">
|
|
<div className="mb-8">
|
|
<div className="flex items-center mb-6">
|
|
<h3 className="text-xl font-semibold" style={{ color: '#012068' }}>About</h3>
|
|
</div>
|
|
|
|
<div className="prose prose-lg max-w-none mb-8 leading-relaxed">
|
|
<p className="first-letter:text-4xl first-letter:font-semibold first-letter:float-left first-letter:mr-2 first-letter:leading-none first-letter:mt-1 text-base first-letter:text-blue-900 text-gray-700">
|
|
{getFullDescription()}
|
|
</p>
|
|
|
|
<p className="mt-6 text-base text-gray-700">
|
|
As a dedicated member of the Department of {member.department || 'Medicine'} at Christian Medical College, Vellore,
|
|
{member.name.split(' ')[1] || member.name} contributes to advancing medical education, patient care, and
|
|
clinical research. Their commitment to excellence in healthcare and medical education makes
|
|
them an invaluable asset to the medical community and the patients they serve.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Details Grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div className="space-y-6">
|
|
<div className="rounded-lg p-4 border border-gray-300" style={{ backgroundColor: '#f4f4f4' }}>
|
|
<div className="font-medium mb-2 text-sm" style={{ color: '#012068' }}>Clinical Focus & Specialty</div>
|
|
<div className="text-sm leading-relaxed" style={{ color: '#333' }}>{member.specialty || 'General Medicine'}</div>
|
|
</div>
|
|
|
|
<div className="rounded-lg p-4 border border-gray-300" style={{ backgroundColor: '#f4f4f4' }}>
|
|
<div className="font-medium mb-2 text-sm" style={{ color: '#012068' }}>Education & Certification</div>
|
|
<div className="text-sm leading-relaxed" style={{ color: '#333' }}>{member.certification || 'Medical certification details available upon request'}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
<div className="rounded-lg p-4 border border-gray-300" style={{ backgroundColor: '#f4f4f4' }}>
|
|
<div className="font-medium mb-2 text-sm" style={{ color: '#012068' }}>Training & Professional Development</div>
|
|
<div className="text-sm leading-relaxed" style={{ color: '#333' }}>{member.training || 'Professional training details available upon request'}</div>
|
|
</div>
|
|
|
|
{member.workDays && member.workDays.length > 0 && (
|
|
<div className="rounded-lg p-4 border border-gray-300" style={{ backgroundColor: '#f4f4f4' }}>
|
|
<div className="font-medium mb-3 text-sm" style={{ color: '#012068' }}>Clinical Days</div>
|
|
<div className="space-y-2">
|
|
{member.workDays.map((day, index) => (
|
|
<div key={index} className="flex items-center">
|
|
<div
|
|
className="w-4 h-4 rounded flex items-center justify-center mr-3"
|
|
style={{ backgroundColor: '#e64838' }}
|
|
>
|
|
<Check className="w-3 h-3 text-white" />
|
|
</div>
|
|
<span className="text-sm font-medium" style={{ color: '#012068' }}>{day}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Skills and Awards */}
|
|
<div className="grid grid-cols-1 xl:grid-cols-2 gap-8 lg:gap-12">
|
|
{/* Research Interests & Skills */}
|
|
{member.skills && member.skills.length > 0 && (
|
|
<div>
|
|
<div className="flex items-center mb-6">
|
|
<h3 className="text-xl font-semibold" style={{ color: '#012068' }}>Research Interests & Expertise</h3>
|
|
</div>
|
|
|
|
<p className="text-sm mb-6 leading-relaxed" style={{ color: '#333' }}>
|
|
Areas of clinical expertise, research focus, and professional competencies that contribute to advancing medical practice and education at CMC Vellore.
|
|
</p>
|
|
|
|
<div className="space-y-4">
|
|
{member.skills.map((skill, index) => (
|
|
<div key={index} className="rounded-lg p-4 border border-gray-300" style={{ backgroundColor: '#f4f4f4' }}>
|
|
<div className="flex justify-between items-center">
|
|
<span className="font-medium text-sm" style={{ color: '#012068' }}>{skill.name}</span>
|
|
{skill.level}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Awards */}
|
|
{member.awards && member.awards.length > 0 && (
|
|
<div>
|
|
<div className="flex items-center mb-6">
|
|
<h3 className="text-xl font-semibold" style={{ color: '#012068' }}>Awards and Recognition</h3>
|
|
</div>
|
|
|
|
<p className="text-sm mb-6 leading-relaxed" style={{ color: '#333' }}>
|
|
Professional achievements, awards, and recognition received for excellence in medical practice, research, and education.
|
|
</p>
|
|
|
|
<div className="space-y-6">
|
|
{member.awards.map((award, index) => (
|
|
<div key={index} className="rounded-lg p-4 border border-gray-300" style={{ backgroundColor: '#f4f4f4' }}>
|
|
<div className="flex items-start space-x-3">
|
|
<img
|
|
src={award.image}
|
|
alt={award.title}
|
|
className="w-12 h-12 rounded object-cover border border-gray-300 flex-shrink-0"
|
|
onError={(e) => {
|
|
const target = e.target as HTMLImageElement;
|
|
target.src = '/images/award-icon.png';
|
|
}}
|
|
/>
|
|
<div className="flex-1">
|
|
<div
|
|
className="inline-flex items-center px-2 py-1 rounded text-xs font-medium mb-2"
|
|
style={{ backgroundColor: '#e64838', color: 'white' }}
|
|
>
|
|
{award.year}
|
|
</div>
|
|
<h4 className="text-base font-medium mb-2" style={{ color: '#012068' }}>{award.title}</h4>
|
|
<p className="text-sm leading-relaxed" style={{ color: '#333' }}>{award.description}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default TeamMemberDetail; |