first commit
This commit is contained in:
136
src/components/Layouts/Footer.tsx
Normal file
136
src/components/Layouts/Footer.tsx
Normal file
@ -0,0 +1,136 @@
|
||||
"use client";
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="text-gray-100" style={{ backgroundColor: '#012068'}}>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 lg:py-16">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{/* Company Info */}
|
||||
<div className="lg:col-span-1">
|
||||
<Link href="/" className="inline-block mb-4">
|
||||
<div className="flex items-center">
|
||||
|
||||
<span className="text-2xl font-bold text-gray-100">Department of <br></br>Trauma Surgery</span>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="space-y-3 mb-6">
|
||||
<a
|
||||
href="mailto:traumasurg@cmcvellore.ac.in"
|
||||
className="block transition-colors duration-200 text-gray-100 hover:text-red-600"
|
||||
>
|
||||
traumasurg@cmcvellore.ac.in
|
||||
</a>
|
||||
<p className="text-sm leading-relaxed text-gray-300">
|
||||
Department of Trauma Surgery<br />
|
||||
Room A601, 6th Floor, A Block<br />
|
||||
CMC Vellore Ranipet Campus<br />
|
||||
Kilminnal Village, Ranipet – 632517,<br />
|
||||
Tamil Nadu
|
||||
</p>
|
||||
<p className="text-gray-100">0417-2224626</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Company Links */}
|
||||
<div className="md:col-span-1">
|
||||
<h3 className="font-semibold text-lg mb-4 text-gray-100">Company</h3>
|
||||
<ul className="space-y-3">
|
||||
{[
|
||||
{ label: 'About CMC', href: '/about' },
|
||||
{ label: 'Contact us', href: '/contact' },
|
||||
{ label: 'Events', href: '/events' },
|
||||
{ label: 'Education', href: '/education-training' },
|
||||
{ label: 'Career', href: '/career' },
|
||||
{ label: 'Team Member', href: '/teamMember' },
|
||||
].map((link) => (
|
||||
<li key={link.label}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="text-gray-300 hover:text-red-600 transition-all duration-200 text-sm"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Additional Links */}
|
||||
<div className="md:col-span-1">
|
||||
<h3 className="font-semibold text-lg mb-4 text-gray-100">Links</h3>
|
||||
<ul className="space-y-3">
|
||||
{[
|
||||
{ label: 'Help Center', href: '/contact' },
|
||||
{ label: 'Privacy Policy', href: '#' },
|
||||
{ label: 'Terms & Conditions', href: '#' },
|
||||
{ label: 'Blogs', href: '/blogs' },
|
||||
].map((link) => (
|
||||
<li key={link.label}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="text-gray-300 hover:text-red-600 transition-all duration-200 text-sm"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Newsletter */}
|
||||
<div className="md:col-span-2 lg:col-span-1">
|
||||
<h3 className="font-semibold text-lg mb-4 text-gray-100">Stay Updated</h3>
|
||||
<p className="text-sm mb-4 text-gray-300">
|
||||
Follow us on social media for the latest updates and news.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
className="flex-1 px-4 py-2 rounded-md bg-blue-800 bg-opacity-20 border border-blue-600 text-white placeholder-white transition-all"
|
||||
/>
|
||||
<button className="px-4 py-2 rounded-md bg-red-600 text-gray-100 hover:bg-red-700 transition-all duration-200 whitespace-nowrap">
|
||||
Subscribe
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Bottom */}
|
||||
<div className="border-t border-gray-600 border-opacity-30">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
|
||||
<p className="text-sm text-gray-300">
|
||||
© {new Date().getFullYear()} Copyright by{" "}
|
||||
<a
|
||||
href="#"
|
||||
className="text-red-600 hover:text-red-700"
|
||||
>
|
||||
CMC
|
||||
</a>
|
||||
. All Rights Reserved.
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center md:justify-end space-x-6">
|
||||
{[
|
||||
{ label: 'Privacy Policy', href: '#' },
|
||||
{ label: 'Terms of Service', href: '#' },
|
||||
{ label: 'Contact', href: '/contact' },
|
||||
].map((link) => (
|
||||
<Link
|
||||
key={link.label}
|
||||
href={link.href}
|
||||
className="text-gray-300 hover:text-red-600 transition-all duration-200 text-sm"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
238
src/components/Layouts/Header.tsx
Normal file
238
src/components/Layouts/Header.tsx
Normal file
@ -0,0 +1,238 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
|
||||
const Header = () => {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [openDropdown, setOpenDropdown] = useState<string | null>(null);
|
||||
|
||||
const toggleDropdown = (dropdownName: string) => {
|
||||
setOpenDropdown(current => current === dropdownName ? null : dropdownName);
|
||||
};
|
||||
|
||||
const closeAllMenus = () => {
|
||||
setIsMenuOpen(false);
|
||||
setOpenDropdown(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="sticky bg-white top-0 z-50 shadow-sm">
|
||||
<div className="container max-w-7xl mx-auto px-4 sm:px-6">
|
||||
<div className="flex justify-between items-center py-3 sm:py-4">
|
||||
{/* Logo */}
|
||||
<div className="flex-shrink-0">
|
||||
<Link href="/" onClick={closeAllMenus} className="flex items-center">
|
||||
<div className="relative w-80 h-18 mr-3 rounded overflow-hidden">
|
||||
<Image
|
||||
src="/images/logo.png" // Replace with your logo path
|
||||
alt="CMC Logo"
|
||||
fill
|
||||
className="object-fill"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden lg:flex items-start space-x-8">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-blue-900 hover:text-red-600 transition-colors font-medium"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Home
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/about"
|
||||
className="text-blue-900 hover:text-red-600 transition-colors font-medium"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
About
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/events"
|
||||
className="text-blue-900 hover:text-red-600 transition-colors font-medium"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Events
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/education-training"
|
||||
className="text-blue-900 hover:text-red-600 transition-colors font-medium"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Education
|
||||
</Link>
|
||||
<Link
|
||||
href="/research"
|
||||
className="text-blue-900 hover:text-red-600 transition-colors font-medium"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Research
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/blogs"
|
||||
className="text-blue-900 hover:text-red-600 transition-colors font-medium"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Blogs
|
||||
</Link>
|
||||
<Link
|
||||
href="/teamMember"
|
||||
className="text-blue-900 hover:text-red-600 transition-colors font-medium"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Team Member
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/career"
|
||||
className="text-blue-900 hover:text-red-600 transition-colors font-medium"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Career
|
||||
</Link>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="text-blue-900 hover:text-red-600 transition-colors font-medium"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Contact Us
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<button
|
||||
className="lg:hidden p-2 text-blue-900 hover:text-red-600 transition-colors"
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
{isMenuOpen ? (
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
) : (
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{isMenuOpen && (
|
||||
<div className="lg:hidden bg-gray-100 border-t border-blue-900">
|
||||
<nav className="px-4 py-4">
|
||||
<ul className="space-y-4">
|
||||
<li>
|
||||
<Link
|
||||
href="/"
|
||||
className="block font-medium py-2 text-blue-900 hover:text-red-600 transition-colors"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Home
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link
|
||||
href="/about"
|
||||
className="block font-medium py-2 text-blue-900 hover:text-red-600 transition-colors"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
About
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link
|
||||
href="/events"
|
||||
className="block font-medium py-2 text-blue-900 hover:text-red-600 transition-colors"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Events
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link
|
||||
href="/blogs"
|
||||
className="block font-medium py-2 text-blue-900 hover:text-red-600 transition-colors"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Blogs
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link
|
||||
href="/career"
|
||||
className="block font-medium py-2 text-blue-900 hover:text-red-600 transition-colors"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Career
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="block font-medium py-2 text-blue-900 hover:text-red-600 transition-colors"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Contact
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link
|
||||
href="/teamMember"
|
||||
className="block font-medium py-2 text-blue-900 hover:text-red-600 transition-colors"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Team Member
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
{/* Mobile Support Info */}
|
||||
<li className="pt-4 border-t border-blue-900">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Link
|
||||
href="/contact"
|
||||
className="inline-flex justify-center px-4 py-2 text-sm font-medium text-gray-100 bg-red-600 hover:bg-blue-900 rounded transition-colors"
|
||||
onClick={closeAllMenus}
|
||||
>
|
||||
Contact Us
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
74
src/components/about/AboutBreadcrumb.tsx
Normal file
74
src/components/about/AboutBreadcrumb.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
|
||||
'use client'
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
|
||||
interface BreadcrumbItem {
|
||||
label: string;
|
||||
href?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
interface BreadcrumbProps {
|
||||
items: BreadcrumbItem[];
|
||||
title: string;
|
||||
description?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Breadcrumb: React.FC<BreadcrumbProps> = ({
|
||||
items,
|
||||
title,
|
||||
description,
|
||||
className = ""
|
||||
}) => {
|
||||
return (
|
||||
<section className={`py-4 ${className}`} style={{ backgroundColor: '#f4f4f4' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
{/* Breadcrumb Navigation */}
|
||||
<nav className="flex items-center space-x-2 text-sm">
|
||||
{items.map((item, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{item.href && !item.isActive ? (
|
||||
<Link
|
||||
href={item.href}
|
||||
className="hover:opacity-70 transition-opacity duration-200"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
) : (
|
||||
<span
|
||||
className={item.isActive ? "font-medium" : ""}
|
||||
style={{ color: item.isActive ? '#e64838' : '#012068' }}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
)}
|
||||
{index < items.length - 1 && (
|
||||
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Page Header */}
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center mb-4">
|
||||
<h1 className="text-3xl font-bold" style={{ color: '#012068' }}>
|
||||
{title}
|
||||
</h1>
|
||||
</div>
|
||||
{description && (
|
||||
<p className="text-base max-w-4xl leading-relaxed"style={{ color: '#333' }}>
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Breadcrumb;
|
||||
22
src/components/about/Introduction.tsx
Normal file
22
src/components/about/Introduction.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
const Introduction = () => {
|
||||
return (
|
||||
<section className="py-8 sm:py-12 bg-[#012068]">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<p className="text-base sm:text-lg leading-relaxed text-white ">
|
||||
Learn about our mission, vision, and the dedicated team behind the Trauma Care Center
|
||||
at CMC Hospital, Ranipet campus. We are committed to delivering world-class emergency
|
||||
and trauma services that focus on saving lives, reducing recovery time, and restoring
|
||||
hope for patients and their families. Our center integrates advanced medical technology,
|
||||
skilled professionals, and compassionate care to respond swiftly and effectively to
|
||||
critical injuries and emergencies. With a holistic approach that spans emergency response,
|
||||
surgical intervention, and rehabilitation, we strive to set new benchmarks in trauma
|
||||
management and patient-centered care.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Introduction;
|
||||
40
src/components/about/MissionVision.tsx
Normal file
40
src/components/about/MissionVision.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { Target, Eye } from 'lucide-react';
|
||||
|
||||
const MissionVision = () => {
|
||||
return (
|
||||
<section className="py-8 sm:py-12 bg-white">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
|
||||
{/* Vision */}
|
||||
<div className="rounded-lg p-6 border-l-4" style={{ backgroundColor: '#f4f4f4', borderColor: '#012068' }}>
|
||||
<div className="flex items-center mb-4">
|
||||
<div className="w-10 h-10 rounded-full flex items-center justify-center mr-3" style={{ backgroundColor: '#012068' }}>
|
||||
<Eye className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold" style={{ color: '#012068' }}>Our Vision</h2>
|
||||
</div>
|
||||
<p className="leading-relaxed text-base"style={{ color: '#333' }}>
|
||||
To stand as a centre of excellence for trauma care in South India—combining world-class clinical service with teaching and outreach grounded in faith.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Mission */}
|
||||
<div className="rounded-lg p-6 border-l-4" style={{ backgroundColor: '#f4f4f4', borderColor: '#012068' }}>
|
||||
<div className="flex items-center mb-4">
|
||||
<div className="w-10 h-10 rounded-full flex items-center justify-center mr-3" style={{ backgroundColor: '#012068' }}>
|
||||
<Target className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold" style={{ color: '#012068' }}>Our Mission</h2>
|
||||
</div>
|
||||
<p className="leading-relaxed text-base"style={{ color: '#333' }}>
|
||||
To reduce trauma-related deaths and lifelong disabilities by providing integrated, compassionate, evidence-based care.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default MissionVision;
|
||||
64
src/components/about/PatientCareCards.tsx
Normal file
64
src/components/about/PatientCareCards.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import { Building2, Home } from 'lucide-react';
|
||||
|
||||
const PatientCareCards = () => {
|
||||
return (
|
||||
<section className="py-8 sm:py-12" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
|
||||
{/* Inpatient Services */}
|
||||
<div className="bg-white rounded-lg p-6 border-l-4" style={{ borderColor: '#012068' }}>
|
||||
<div className="flex items-center mb-4">
|
||||
<div
|
||||
className="w-10 h-10 rounded-full flex items-center justify-center mr-3"
|
||||
style={{ backgroundColor: '#012068' }}
|
||||
>
|
||||
<Building2 className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold" style={{ color: '#012068' }}>
|
||||
INPATIENT
|
||||
</h2>
|
||||
</div>
|
||||
<p className="leading-relaxed mb-3 text-base" style={{ color: '#333' }}>
|
||||
Different facilities available include Trauma Intensive Care Unit, General ward, Semiprivate accommodation
|
||||
(with or without AC) and Private - single (with or without AC) & Deluxe rooms.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Outpatient Services */}
|
||||
<div className="bg-white rounded-lg p-6 border-l-4" style={{ borderColor: '#012068' }}>
|
||||
<div className="flex items-center mb-4">
|
||||
<div
|
||||
className="w-10 h-10 rounded-full flex items-center justify-center mr-3"
|
||||
style={{ backgroundColor: '#012068' }}
|
||||
>
|
||||
<Home className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold" style={{ color: '#012068' }}>
|
||||
OUTPATIENT
|
||||
</h2>
|
||||
</div>
|
||||
<ul className="text-sm space-y-2">
|
||||
<li className="flex items-start" style={{ color: '#333' }}>
|
||||
<span
|
||||
className="w-2 h-2 rounded-full mr-3 mt-2"
|
||||
style={{ backgroundColor: '#012068'}}
|
||||
></span>
|
||||
Trauma Surgery - Every Monday and Friday are OP days.
|
||||
</li>
|
||||
<li className="flex items-start" style={{ color: '#333' }}>
|
||||
<span
|
||||
className="w-2 h-2 rounded-full mr-3 mt-2"
|
||||
style={{ backgroundColor: '#012068'}}
|
||||
></span>
|
||||
Acute Care Surgery Follow-up Clinic - Every Monday and Friday are OP days.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default PatientCareCards;
|
||||
69
src/components/about/Services.tsx
Normal file
69
src/components/about/Services.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import { Leaf, Car, Activity, Globe } from 'lucide-react';
|
||||
|
||||
const Services = () => {
|
||||
const services = [
|
||||
{
|
||||
icon: <Leaf className="w-6 h-6" />,
|
||||
title: "Multi-system Polytrauma",
|
||||
description: "Comprehensive care for patients with multiple severe injuries requiring urgent intervention."
|
||||
},
|
||||
{
|
||||
icon: <Car className="w-6 h-6" />,
|
||||
title: "Road Traffic Injuries",
|
||||
description: "Expert trauma management for accidents involving motorbikes, cars, and other vehicles."
|
||||
},
|
||||
{
|
||||
icon: <Activity className="w-6 h-6" />,
|
||||
title: "Falls & Accidents",
|
||||
description: "Specialized treatment for injuries from falls, industrial incidents, and agricultural accidents."
|
||||
},
|
||||
{
|
||||
icon: <Globe className="w-6 h-6" />,
|
||||
title: "Referrals",
|
||||
description: "Providing trauma care support for referrals from Tamil Nadu, Andhra Pradesh, Karnataka, and overseas."
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-8 sm:py-12" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<h2 className="text-2xl sm:text-3xl font-semibold text-center mb-2" style={{ color: '#012068' }}>
|
||||
Our Services
|
||||
</h2>
|
||||
<h2 className="text-md sm:text-xl font-normal text-center mb-8 sm:mb-12">
|
||||
We provide urgent care for
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{services.map((service, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-white rounded-lg p-6 border border-gray-300 hover:shadow-lg transition-shadow duration-300"
|
||||
>
|
||||
<div className="flex items-start space-x-4">
|
||||
<div
|
||||
className="w-12 h-12 rounded-lg flex items-center justify-center flex-shrink-0"
|
||||
style={{ backgroundColor: '#012068' }}
|
||||
>
|
||||
<div className="text-white">
|
||||
{service.icon}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-medium mb-2" style={{ color: '#012068' }}>
|
||||
{service.title}
|
||||
</h3>
|
||||
<p className="text-base leading-relaxed"style={{ color: '#333' }}>
|
||||
{service.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Services;
|
||||
70
src/components/about/StatisticsTiles.tsx
Normal file
70
src/components/about/StatisticsTiles.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import { ShieldPlus, UserCheck, Hospital, BookOpen } from 'lucide-react';
|
||||
|
||||
const StatisticsTiles = () => {
|
||||
const tiles = [
|
||||
{
|
||||
icon: <ShieldPlus className="w-6 h-6" style={{ color: '#012068' }} />,
|
||||
title: "Primary Trauma Care",
|
||||
description: "Specialized care for Priority One trauma patients in close coordination with the Emergency Department team."
|
||||
},
|
||||
{
|
||||
icon: <UserCheck className="w-6 h-6" style={{ color: '#012068' }} />,
|
||||
title: "24×7 Trauma Surgeon",
|
||||
description: "Round-the-clock availability of trauma surgeons ensures immediate surgical intervention when needed."
|
||||
},
|
||||
{
|
||||
icon: <Hospital className="w-6 h-6" style={{ color: '#012068' }} />,
|
||||
title: "Trauma Intensive & Ward Care",
|
||||
description: "Comprehensive trauma intensive care and dedicated trauma ward services for critical and recovering patients."
|
||||
},
|
||||
{
|
||||
icon: <BookOpen className="w-6 h-6" style={{ color: '#012068' }} />,
|
||||
title: "Trauma Education",
|
||||
description: "Focused education and counseling for patients and families to enhance recovery and awareness."
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-8 sm:py-12 bg-white">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<h2
|
||||
className="text-2xl sm:text-3xl font-semibold text-center mb-8 sm:mb-12"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
Our Trauma Care Services
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 lg:gap-6">
|
||||
{tiles.map((tile, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="border border-gray-300 rounded-lg p-5 h-full hover:shadow-lg transition-shadow duration-300"
|
||||
style={{ backgroundColor: '#f4f4f4' }}
|
||||
>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex items-center mb-3">
|
||||
<div className="flex-shrink-0 mr-3">
|
||||
{tile.icon}
|
||||
</div>
|
||||
<h3
|
||||
className="text-base sm:text-lg font-medium"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
{tile.title}
|
||||
</h3>
|
||||
</div>
|
||||
<p
|
||||
className="text-sm sm:text-base leading-relaxed flex-grow" style={{ color: '#333' }}
|
||||
>
|
||||
{tile.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatisticsTiles;
|
||||
178
src/components/about/process.tsx
Normal file
178
src/components/about/process.tsx
Normal file
@ -0,0 +1,178 @@
|
||||
'use client'
|
||||
import React, { useState } from "react";
|
||||
|
||||
export default function Process() {
|
||||
const [activeStep, setActiveStep] = useState(-1);
|
||||
|
||||
const CalendarIcon = (
|
||||
<svg
|
||||
width={34}
|
||||
height={34}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
|
||||
<line x1="16" y1="2" x2="16" y2="6" />
|
||||
<line x1="8" y1="2" x2="8" y2="6" />
|
||||
<line x1="3" y1="10" x2="21" y2="10" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const steps = [
|
||||
{
|
||||
number: "1",
|
||||
title: "2020",
|
||||
description: "Department of Trauma Surgery inaugurated at Town Campus",
|
||||
icon: CalendarIcon,
|
||||
},
|
||||
{
|
||||
number: "2",
|
||||
title: "2022",
|
||||
description: "Ranipet Campus opens as Level-1 Trauma Facility",
|
||||
icon: CalendarIcon,
|
||||
},
|
||||
{
|
||||
number: "3",
|
||||
title: "Nov 2022",
|
||||
description: "Trauma centre begins operations with full emergency suite",
|
||||
icon: CalendarIcon,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="bg-white py-12 sm:py-20">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-16">
|
||||
{/* Left Column */}
|
||||
<div className="flex flex-col justify-start">
|
||||
<div className="mb-8 lg:mb-16">
|
||||
<div className="text-xl mb-4" style={{ color: "#e64838" }}>
|
||||
Milestones
|
||||
</div>
|
||||
<h2
|
||||
className="text-3xl sm:text-4xl md:text-5xl font-bold leading-tight mb-6"
|
||||
style={{ color: "#012068" }}
|
||||
>
|
||||
Our Journey in Trauma Care
|
||||
</h2>
|
||||
<p className="text-base sm:text-lg leading-relaxed mb-8" style={{ color: '#333' }}>
|
||||
From the inauguration of our Department of Trauma Surgery to
|
||||
establishing a Level-1 Trauma Facility, we continue to expand our
|
||||
emergency care services with dedication and excellence.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column - Steps */}
|
||||
<div className="relative">
|
||||
{steps.map((step, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`relative group cursor-pointer transition-all duration-500 ${
|
||||
index !== 0 ? "mt-8 sm:mt-12" : ""
|
||||
}`}
|
||||
onMouseEnter={() => setActiveStep(index)}
|
||||
onMouseLeave={() => setActiveStep(-1)}
|
||||
>
|
||||
{/* Connecting Line */}
|
||||
{index !== 0 && (
|
||||
<div className="absolute left-6 -top-8 sm:-top-12 w-0.5 h-8 sm:h-12 bg-gray-300 overflow-hidden">
|
||||
<div
|
||||
className={`w-full transition-all duration-700 ease-out origin-bottom ${
|
||||
activeStep >= index ||
|
||||
(activeStep === -1 && index === 0)
|
||||
? "h-full scale-y-100"
|
||||
: "h-0 scale-y-0"
|
||||
}`}
|
||||
style={{ backgroundColor: "#012068" }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step Content */}
|
||||
<div className="flex items-start gap-4 sm:gap-6">
|
||||
{/* Number Circle */}
|
||||
<div
|
||||
className={`relative z-10 flex-shrink-0 w-12 h-12 rounded-full flex items-center justify-center text-white font-semibold text-lg transition-all duration-500 transform ${
|
||||
activeStep === index ||
|
||||
(activeStep === -1 && index === 0)
|
||||
? "scale-110 shadow-lg ring-4"
|
||||
: "scale-100 hover:scale-105"
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor:
|
||||
activeStep === index ||
|
||||
(activeStep === -1 && index === 0)
|
||||
? "#012068"
|
||||
: "#333", // Black when inactive
|
||||
"--tw-ring-color":
|
||||
activeStep === index ||
|
||||
(activeStep === -1 && index === 0)
|
||||
? "#012068" + "33"
|
||||
: "transparent",
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
{step.number}
|
||||
</div>
|
||||
|
||||
{/* Step Card */}
|
||||
<div
|
||||
className={`flex-1 p-4 sm:p-6 rounded-lg border transition-all duration-500 transform ${
|
||||
activeStep === index ||
|
||||
(activeStep === -1 && index === 0)
|
||||
? "border-transparent text-white shadow-2xl scale-105 -translate-y-2"
|
||||
: "border-gray-300 hover:shadow-lg hover:-translate-y-1"
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor:
|
||||
activeStep === index ||
|
||||
(activeStep === -1 && index === 0)
|
||||
? "#012068"
|
||||
: "#f4f4f4",
|
||||
color:
|
||||
activeStep === index ||
|
||||
(activeStep === -1 && index === 0)
|
||||
? "white"
|
||||
: "#333", // Black text when inactive
|
||||
}}
|
||||
>
|
||||
{/* Icon */}
|
||||
<div
|
||||
className={`mb-4 transition-all duration-500 transform ${
|
||||
activeStep === index ||
|
||||
(activeStep === -1 && index === 0)
|
||||
? "text-white"
|
||||
: "scale-100 group-hover:scale-105"
|
||||
}`}
|
||||
style={{
|
||||
color:
|
||||
activeStep === index ||
|
||||
(activeStep === -1 && index === 0)
|
||||
? "white"
|
||||
: "black", // Black icon when inactive
|
||||
}}
|
||||
>
|
||||
{step.icon}
|
||||
</div>
|
||||
|
||||
{/* Text */}
|
||||
<h3 className="text-lg sm:text-xl font-semibold mb-3 transition-all duration-300">
|
||||
{step.title}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base leading-relaxed transition-all duration-500">
|
||||
{step.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
421
src/components/blogs/BlogDetail.tsx
Normal file
421
src/components/blogs/BlogDetail.tsx
Normal file
@ -0,0 +1,421 @@
|
||||
'use client';
|
||||
import { useState, useEffect } from 'react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { ChevronRight, Clock, Calendar, Share2, ArrowLeft, Facebook, Twitter, Linkedin } from 'lucide-react';
|
||||
import { blogService, Blog } from '../../services/blogService'; // Adjust path as needed
|
||||
|
||||
const BlogDetail: React.FC = () => {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const blogId = params.id as string;
|
||||
|
||||
const [blogData, setBlogData] = useState<Blog | null>(null);
|
||||
const [relatedPosts, setRelatedPosts] = useState<Blog[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchBlogData = async () => {
|
||||
if (!blogId) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// Convert string ID to number for API call
|
||||
const numericId = parseInt(blogId, 10);
|
||||
if (isNaN(numericId)) {
|
||||
throw new Error('Invalid blog ID');
|
||||
}
|
||||
|
||||
console.log('Fetching blog with ID:', numericId);
|
||||
|
||||
// Fetch the specific blog and related posts
|
||||
const [blog, allBlogs] = await Promise.all([
|
||||
blogService.getBlogById(numericId),
|
||||
blogService.getPostedBlogs()
|
||||
]);
|
||||
|
||||
if (!blog) {
|
||||
setError('Blog not found');
|
||||
return;
|
||||
}
|
||||
|
||||
setBlogData(blog);
|
||||
|
||||
// Get related posts (same tags, exclude current blog)
|
||||
const related = allBlogs
|
||||
.filter(b => b.id !== blog.id && b.tags.some(tag => blog.tags.includes(tag)))
|
||||
.slice(0, 3);
|
||||
|
||||
// If not enough related posts with same tags, fill with other recent posts
|
||||
if (related.length < 3) {
|
||||
const otherPosts = allBlogs
|
||||
.filter(b => b.id !== blog.id && !related.some(r => r.id === b.id))
|
||||
.slice(0, 3 - related.length);
|
||||
related.push(...otherPosts);
|
||||
}
|
||||
|
||||
setRelatedPosts(related);
|
||||
} catch (err) {
|
||||
console.error('Error fetching blog:', err);
|
||||
setError('Failed to load blog. Please try again later.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (mounted) {
|
||||
fetchBlogData();
|
||||
}
|
||||
}, [blogId, mounted]);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
const handleGoBack = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
const handleShare = async (platform?: string) => {
|
||||
if (!blogData || typeof window === 'undefined') return;
|
||||
|
||||
const url = window.location.href;
|
||||
const title = blogData.title;
|
||||
|
||||
try {
|
||||
if (platform === 'facebook') {
|
||||
window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`, '_blank');
|
||||
} else if (platform === 'twitter') {
|
||||
window.open(`https://twitter.com/intent/tweet?url=${encodeURIComponent(url)}&text=${encodeURIComponent(title)}`, '_blank');
|
||||
} else if (platform === 'linkedin') {
|
||||
window.open(`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`, '_blank');
|
||||
} else {
|
||||
// Generic share or copy link
|
||||
if (navigator.share) {
|
||||
await navigator.share({
|
||||
title: title,
|
||||
url: url,
|
||||
});
|
||||
} else {
|
||||
await navigator.clipboard.writeText(url);
|
||||
alert('Blog link copied to clipboard!');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Share failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleImageError = (event: React.SyntheticEvent<HTMLImageElement>) => {
|
||||
const target = event.target as HTMLImageElement;
|
||||
target.src = '/images/default-blog-image.jpg';
|
||||
};
|
||||
|
||||
const getAuthorName = (blog: Blog) => {
|
||||
if (blog.professors && blog.professors.length > 0) {
|
||||
return blog.professors.map(prof => prof.firstName || prof.name).join(', ');
|
||||
}
|
||||
return 'Medical Team';
|
||||
};
|
||||
|
||||
const getAuthorBio = (blog: Blog) => {
|
||||
if (blog.professors && blog.professors.length > 0) {
|
||||
return `Medical professional${blog.professors.length > 1 ? 's' : ''} specializing in trauma care and mental health support.`;
|
||||
}
|
||||
return 'Our medical team consists of experienced professionals dedicated to trauma care and mental health support.';
|
||||
};
|
||||
|
||||
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 blog...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !blogData) {
|
||||
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 mb-4">
|
||||
<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="/blogs"
|
||||
className="hover:opacity-70 transition-opacity duration-200"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
Trauma Care Resources
|
||||
</Link>
|
||||
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
|
||||
<span className="font-medium" style={{ color: '#e64838' }}>
|
||||
Blog Not Found
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
<button
|
||||
onClick={handleGoBack}
|
||||
className="inline-flex items-center space-x-2 text-sm hover:opacity-70 transition-opacity duration-200"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span>Back to Resources</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="py-6">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<div className="bg-white shadow-lg rounded-lg p-4 md:p-8 text-center">
|
||||
<h1 className="text-xl md:text-2xl font-medium mb-4" style={{ color: '#012068' }}>
|
||||
{error || 'Blog Not Found'}
|
||||
</h1>
|
||||
<button
|
||||
onClick={handleGoBack}
|
||||
className="px-6 py-2 text-sm rounded-lg hover:opacity-90 transition-opacity"
|
||||
style={{ backgroundColor: '#012068', color: '#f4f4f4' }}
|
||||
>
|
||||
Go Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 mb-4">
|
||||
<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="/blogs"
|
||||
className="hover:opacity-70 transition-opacity duration-200"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
Trauma Care Resources
|
||||
</Link>
|
||||
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
|
||||
<span className="font-medium truncate" style={{ color: '#e64838' }}>
|
||||
{blogData.title}
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
{/* Back Button */}
|
||||
<button
|
||||
onClick={handleGoBack}
|
||||
className="inline-flex items-center space-x-2 text-sm hover:opacity-70 transition-opacity duration-200"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span>Back to Resources</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Article Content */}
|
||||
<article className="max-w-7xl mx-auto px-4 py-8">
|
||||
{/* Article Header */}
|
||||
<header className="mb-8">
|
||||
{/* Tags */}
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
{blogData.tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-3 py-1 text-sm font-medium rounded"
|
||||
style={{
|
||||
backgroundColor: '#f4f4f4',
|
||||
color: '#e64838'
|
||||
}}
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h1
|
||||
className="text-3xl md:text-4xl font-bold mb-4 leading-tight"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
{blogData.title}
|
||||
</h1>
|
||||
|
||||
{/* Meta Information */}
|
||||
<div className="flex flex-wrap items-center gap-4 text-sm" style={{ color: '#666' }}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Calendar className="w-4 h-4" />
|
||||
<span>{new Date(blogData.publishDate).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>{blogData.readTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Author Info */}
|
||||
<div className="flex items-start space-x-4 mt-6 p-4 rounded-lg" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
<div className="w-15 h-15 bg-gray-300 rounded-full flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-2xl font-medium" style={{ color: '#012068' }}>
|
||||
{getAuthorName(blogData).charAt(0)}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium" style={{ color: '#012068' }}>
|
||||
{getAuthorName(blogData)}
|
||||
</h3>
|
||||
<p className="text-sm mt-1" style={{ color: '#666' }}>
|
||||
{getAuthorBio(blogData)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Share Buttons */}
|
||||
<div className="flex items-center space-x-4 mb-8 pb-6 border-b border-gray-200">
|
||||
<span className="text-sm font-medium" style={{ color: '#012068' }}>Share:</span>
|
||||
<button
|
||||
onClick={() => handleShare('facebook')}
|
||||
className="p-2 rounded hover:bg-gray-100 transition-colors duration-200"
|
||||
>
|
||||
<Facebook className="w-5 h-5" style={{ color: '#1877f2' }} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleShare('twitter')}
|
||||
className="p-2 rounded hover:bg-gray-100 transition-colors duration-200"
|
||||
>
|
||||
<Twitter className="w-5 h-5" style={{ color: '#1da1f2' }} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleShare('linkedin')}
|
||||
className="p-2 rounded hover:bg-gray-100 transition-colors duration-200"
|
||||
>
|
||||
<Linkedin className="w-5 h-5" style={{ color: '#0077b5' }} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleShare()}
|
||||
className="p-2 rounded hover:bg-gray-100 transition-colors duration-200"
|
||||
>
|
||||
<Share2 className="w-5 h-5" style={{ color: '#666' }} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Article Content */}
|
||||
<div
|
||||
className="prose prose-lg max-w-none mb-12"
|
||||
style={{
|
||||
'--tw-prose-headings': '#012068',
|
||||
'--tw-prose-body': '#333',
|
||||
'--tw-prose-links': '#e64838',
|
||||
color:'#333'
|
||||
} as React.CSSProperties}
|
||||
dangerouslySetInnerHTML={{ __html: blogData.content || blogData.excerpt }}
|
||||
/>
|
||||
</article>
|
||||
|
||||
{/* Related Posts */}
|
||||
{relatedPosts.length > 0 && (
|
||||
<section className="py-12" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<h2 className="text-2xl font-bold mb-8" style={{ color: '#012068' }}>
|
||||
Related Articles
|
||||
</h2>
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
{relatedPosts.map((post) => (
|
||||
<div
|
||||
key={post.id}
|
||||
className="group bg-white rounded-lg overflow-hidden border border-gray-300 hover:shadow-lg transition-all duration-300"
|
||||
>
|
||||
<Link href={`/blog-detail/${post.id}`} className="block">
|
||||
<div className="relative h-40 overflow-hidden">
|
||||
<Image
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
fill
|
||||
className="object-cover transition-transform duration-300 group-hover:scale-105"
|
||||
sizes="(max-width: 768px) 100vw, 33vw"
|
||||
onError={handleImageError}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<h3
|
||||
className="font-medium text-sm line-clamp-2 mb-2 group-hover:opacity-70 transition-opacity duration-300"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
{post.title}
|
||||
</h3>
|
||||
<div className="flex items-center space-x-2 text-xs" style={{ color: '#666' }}>
|
||||
<Clock className="w-3 h-3" />
|
||||
<span>{post.readTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-12">
|
||||
<div className="max-w-4xl mx-auto px-4 text-center">
|
||||
<div className="p-8 rounded-lg" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
<h2 className="text-2xl font-bold mb-4" style={{ color: '#012068' }}>
|
||||
Need Professional Support?
|
||||
</h2>
|
||||
<p className="text-base mb-6 max-w-2xl mx-auto" style={{ color: '#666' }}>
|
||||
If you or someone you know is struggling with trauma, don't hesitate to reach out for professional help. Our team is here to support you on your healing journey.
|
||||
</p>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="inline-block px-6 py-3 text-sm font-medium rounded hover:opacity-90 transition-opacity duration-300"
|
||||
style={{
|
||||
backgroundColor: '#012068',
|
||||
color: '#f4f4f4'
|
||||
}}
|
||||
>
|
||||
Get Support Today
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlogDetail;
|
||||
372
src/components/blogs/BlogListing.tsx
Normal file
372
src/components/blogs/BlogListing.tsx
Normal file
@ -0,0 +1,372 @@
|
||||
// components/BlogListing.tsx
|
||||
'use client';
|
||||
import { useState, useEffect } from 'react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { blogService, Blog } from '../../services/blogService'; // Adjust path as needed
|
||||
|
||||
const BlogListing: React.FC = () => {
|
||||
const [blogs, setBlogs] = useState<Blog[]>([]);
|
||||
const [filteredBlogs, setFilteredBlogs] = useState<Blog[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [selectedCategory, setSelectedCategory] = useState('All Categories');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [displayCount, setDisplayCount] = useState(6);
|
||||
const [tagCounts, setTagCounts] = useState<{ [key: string]: number }>({});
|
||||
|
||||
// Get unique categories from blogs with their counts
|
||||
const getCategories = () => {
|
||||
const allTags = blogs.flatMap(blog => blog.tags);
|
||||
const uniqueTags = Array.from(new Set(allTags));
|
||||
return ['All Categories', ...uniqueTags];
|
||||
};
|
||||
|
||||
// Filter blogs based on category and search query
|
||||
const filterBlogs = () => {
|
||||
let filtered = blogs;
|
||||
|
||||
// Filter by category
|
||||
if (selectedCategory !== 'All Categories') {
|
||||
filtered = filtered.filter(blog =>
|
||||
blog.tags.some(tag =>
|
||||
tag.toLowerCase().includes(selectedCategory.toLowerCase())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Filter by search query
|
||||
if (searchQuery.trim()) {
|
||||
const query = searchQuery.toLowerCase().trim();
|
||||
filtered = filtered.filter(blog =>
|
||||
blog.title.toLowerCase().includes(query) ||
|
||||
blog.excerpt.toLowerCase().includes(query) ||
|
||||
blog.tags.some(tag => tag.toLowerCase().includes(query)) ||
|
||||
(blog.professors && blog.professors.some(prof =>
|
||||
prof.firstName?.toLowerCase().includes(query) ||
|
||||
prof.name?.toLowerCase().includes(query)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
setFilteredBlogs(filtered);
|
||||
};
|
||||
|
||||
// Load blogs from API
|
||||
useEffect(() => {
|
||||
const loadBlogs = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Load both posted blogs and tag counts
|
||||
const [fetchedBlogs, fetchedTagCounts] = await Promise.all([
|
||||
blogService.getPostedBlogs(),
|
||||
blogService.getTagsWithCount()
|
||||
]);
|
||||
|
||||
setBlogs(fetchedBlogs);
|
||||
setFilteredBlogs(fetchedBlogs);
|
||||
setTagCounts(fetchedTagCounts);
|
||||
} catch (err) {
|
||||
setError('Failed to load blogs. Please try again later.');
|
||||
console.error('Error loading blogs:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (mounted) {
|
||||
loadBlogs();
|
||||
}
|
||||
}, [mounted]);
|
||||
|
||||
// Filter blogs when category or search changes
|
||||
useEffect(() => {
|
||||
if (mounted && blogs.length > 0) {
|
||||
filterBlogs();
|
||||
}
|
||||
}, [selectedCategory, searchQuery, blogs, mounted]);
|
||||
|
||||
// Handle mount
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
const handleCategoryChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setSelectedCategory(e.target.value);
|
||||
setDisplayCount(6);
|
||||
};
|
||||
|
||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchQuery(e.target.value);
|
||||
setDisplayCount(6);
|
||||
};
|
||||
|
||||
const handleLoadMore = () => {
|
||||
setDisplayCount(prev => prev + 6);
|
||||
};
|
||||
|
||||
const handleImageError = (event: React.SyntheticEvent<HTMLImageElement>) => {
|
||||
// Fallback to default image if the uploaded image fails to load
|
||||
const target = event.target as HTMLImageElement;
|
||||
target.src = '/images/default-blog-image.jpg';
|
||||
};
|
||||
|
||||
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 blogs...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-center text-red-600">
|
||||
<p className="text-xl mb-4">⚠️ {error}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="px-4 py-2 bg-blue-900 text-white rounded hover:bg-blue-800"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const categories = getCategories();
|
||||
const blogsToShow = filteredBlogs.slice(0, displayCount);
|
||||
const hasMoreBlogs = filteredBlogs.length > displayCount;
|
||||
|
||||
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' }} />
|
||||
<span className="font-medium" style={{ color: '#e64838' }}>
|
||||
Trauma Care Resources
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
{/* Page Header */}
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center mb-4">
|
||||
<h1 className="text-3xl font-bold" style={{ color: '#012068' }}>
|
||||
Trauma Care Resources
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-base max-w-2xl leading-relaxed" style={{ color: '#333' }}>
|
||||
Expert insights, healing strategies, and support resources for trauma recovery
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Filters Section */}
|
||||
<div className="flex justify-end items-center gap-4 max-w-7xl mx-auto px-4 py-8" style={{ backgroundColor: '#fff' }}>
|
||||
<div className="relative">
|
||||
<select
|
||||
value={selectedCategory}
|
||||
onChange={handleCategoryChange}
|
||||
className="appearance-none bg-gray-100 text-sm border border-blue-900 rounded-lg px-4 py-2 pr-8 focus:outline-none focus:border-blue-900"
|
||||
style={{ color: '#333' }}
|
||||
>
|
||||
{categories.map((category, index) => (
|
||||
<option key={index} value={category}>
|
||||
{category}
|
||||
{category !== 'All Categories' && tagCounts[category] ? ` (${tagCounts[category]})` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
|
||||
<svg className="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
placeholder="Search blogs..."
|
||||
className="border border-blue-900 rounded-lg px-4 py-2 pl-4 pr-10 text-sm focus:outline-none focus:border-blue-900 w-64 text-gray-700"
|
||||
/>
|
||||
<button className="absolute inset-y-0 right-0 flex items-center px-3 bg-blue-900 rounded-r-lg">
|
||||
<svg className="w-4 h-4 text-gray-100" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Results Info */}
|
||||
{(selectedCategory !== 'All Categories' || searchQuery.trim()) && (
|
||||
<div className="max-w-7xl mx-auto px-4 pb-4">
|
||||
<p className="text-sm text-gray-600">
|
||||
{filteredBlogs.length === 0
|
||||
? 'No blogs found matching your criteria.'
|
||||
: `Showing ${blogsToShow.length} of ${filteredBlogs.length} blog${filteredBlogs.length !== 1 ? 's' : ''}`
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Blog Grid Section */}
|
||||
<section className="py-4" style={{ backgroundColor: '#fff' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
{blogsToShow.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-500 text-lg mb-4">
|
||||
{searchQuery.trim() || selectedCategory !== 'All Categories'
|
||||
? 'No blogs match your search criteria.'
|
||||
: 'No blogs available at the moment.'
|
||||
}
|
||||
</p>
|
||||
{(searchQuery.trim() || selectedCategory !== 'All Categories') && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchQuery('');
|
||||
setSelectedCategory('All Categories');
|
||||
}}
|
||||
className="px-4 py-2 text-sm border border-blue-900 text-blue-900 rounded hover:bg-blue-50"
|
||||
>
|
||||
Clear Filters
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{blogsToShow.map((blog) => (
|
||||
<div
|
||||
key={blog.id}
|
||||
className="group relative bg-white border border-gray-300 rounded-lg overflow-hidden hover:shadow-lg transition-all duration-300 flex flex-col"
|
||||
>
|
||||
{/* All cards redirect to blog detail page with ID */}
|
||||
<Link
|
||||
href={`/blog-detail/${blog.id}`}
|
||||
className="absolute top-0 left-0 h-full w-full z-10"
|
||||
aria-label={`Read article: ${blog.title}`}
|
||||
/>
|
||||
|
||||
{/* Blog Image */}
|
||||
<div className="relative h-48 w-full overflow-hidden">
|
||||
<Image
|
||||
src={blog.image}
|
||||
alt={blog.title}
|
||||
fill
|
||||
className="object-cover transition-transform duration-300 group-hover:scale-105"
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
onError={handleImageError}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Blog Content */}
|
||||
<div className="p-6 flex flex-col flex-1">
|
||||
{/* Tags */}
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
{blog.tags.slice(0, 3).map((tag, tagIndex) => (
|
||||
<span
|
||||
key={tagIndex}
|
||||
className="px-2 py-1 text-xs font-medium rounded"
|
||||
style={{
|
||||
backgroundColor: '#f4f4f4',
|
||||
color: '#e64838'
|
||||
}}
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{blog.tags.length > 3 && (
|
||||
<span
|
||||
className="px-2 py-1 text-xs font-medium rounded"
|
||||
style={{
|
||||
backgroundColor: '#f4f4f4',
|
||||
color: '#666'
|
||||
}}
|
||||
>
|
||||
+{blog.tags.length - 3}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3
|
||||
className="text-lg font-medium mb-2 line-clamp-2 group-hover:opacity-70 transition-opacity duration-300"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
{blog.title}
|
||||
</h3>
|
||||
|
||||
{/* Excerpt */}
|
||||
<p
|
||||
className="text-xs leading-relaxed line-clamp-3 mb-4 flex-1"
|
||||
style={{ opacity: 0.8, color: "#333" }}
|
||||
>
|
||||
{blog.excerpt}
|
||||
</p>
|
||||
|
||||
{/* Authors (if available) */}
|
||||
{blog.professors && blog.professors.length > 0 && (
|
||||
<div className="mb-2">
|
||||
<p className="text-xs text-gray-600">
|
||||
By: {blog.professors.map(prof => prof.firstName || prof.name).join(', ')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Meta Information */}
|
||||
<div className="flex items-center justify-between text-xs mt-auto" style={{ color: '#333' }}>
|
||||
<span>{new Date(blog.publishDate).toLocaleDateString()}</span>
|
||||
<span>{blog.readTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Load More Button */}
|
||||
{hasMoreBlogs && (
|
||||
<div className="text-center mt-12">
|
||||
<button
|
||||
onClick={handleLoadMore}
|
||||
className="px-6 py-2 text-sm font-medium rounded hover:opacity-90 transition-opacity duration-300 focus:outline-none focus:ring-2 focus:ring-offset-2"
|
||||
style={{
|
||||
backgroundColor: '#012068',
|
||||
color: '#f4f4f4',
|
||||
'--tw-ring-color': '#012068'
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
Load More Articles ({filteredBlogs.length - displayCount} remaining)
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlogListing;
|
||||
482
src/components/career/careerscomponent.tsx
Normal file
482
src/components/career/careerscomponent.tsx
Normal 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;
|
||||
383
src/components/contact-us/ContactForm.tsx
Normal file
383
src/components/contact-us/ContactForm.tsx
Normal file
@ -0,0 +1,383 @@
|
||||
'use client'
|
||||
import React, { useState, ChangeEvent, MouseEvent } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
|
||||
interface FormData {
|
||||
name: string;
|
||||
phone: string;
|
||||
service: string;
|
||||
email: string;
|
||||
organization: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const ContactForm: React.FC = () => {
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
name: '',
|
||||
phone: '',
|
||||
service: '',
|
||||
email: '',
|
||||
organization: '',
|
||||
description: ''
|
||||
});
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: MouseEvent<HTMLButtonElement>): Promise<void> => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
// Simulate API call
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
|
||||
console.log('Form submitted:', formData);
|
||||
|
||||
// Reset form after successful submission
|
||||
setFormData({
|
||||
name: '',
|
||||
phone: '',
|
||||
service: '',
|
||||
email: '',
|
||||
organization: '',
|
||||
description: ''
|
||||
});
|
||||
|
||||
alert('Message sent successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error submitting form:', error);
|
||||
alert('Error sending message. Please try again.');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
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' }}>
|
||||
Contact Us
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
{/* Page Header */}
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center mb-4">
|
||||
<h1 className="text-3xl font-bold" style={{ color: '#012068' }}>
|
||||
Get in Touch
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-base max-w-2xl leading-relaxed"style={{ color: '#333' }}>
|
||||
We're here to help with your research and innovation needs. Reach out to our team for personalized support.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="py-8 px-4"style={{ backgroundColor: '#fff' }}>
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 items-start">
|
||||
|
||||
{/* Left side - Content */}
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-5">
|
||||
<h2 className="text-2xl lg:text-3xl font-bold leading-tight" style={{ color: '#012068' }}>
|
||||
Ready to collaborate on your next breakthrough?
|
||||
</h2>
|
||||
<p className="text-base leading-relaxed" style={{ color: '#333' }}>
|
||||
Whether you need genomics analysis, research consultation, or laboratory services, our expert team is ready to support your scientific endeavors.
|
||||
</p>
|
||||
|
||||
{/* Contact Features */}
|
||||
<div className="space-y-3 mt-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div
|
||||
className="w-7 h-7 rounded-full flex items-center justify-center"
|
||||
style={{ backgroundColor: '#012068' }}
|
||||
>
|
||||
<svg className="w-3.5 h-3.5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-md"style={{ color: '#333' }}>Quick response within 24 hours</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div
|
||||
className="w-7 h-7 rounded-full flex items-center justify-center"
|
||||
style={{ backgroundColor: '#012068' }}
|
||||
>
|
||||
<svg className="w-3.5 h-3.5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-md" style={{ color: '#333' }}>Expert consultation available</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div
|
||||
className="w-7 h-7 rounded-full flex items-center justify-center"
|
||||
style={{ backgroundColor: '#012068' }}
|
||||
>
|
||||
<svg className="w-3.5 h-3.5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-md" style={{ color: '#333' }}>Secure and confidential</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Information */}
|
||||
<div className="rounded-lg border border-gray-300 p-6"style={{ backgroundColor: '#f4f4f4' }}>
|
||||
<h3 className="text-lg font-bold mb-1" style={{ color: '#012068' }}>
|
||||
Contact Information
|
||||
</h3>
|
||||
<div className="w-9 h-0.5 rounded-lg mb-4" style={{ backgroundColor: '#e64838' }}></div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div
|
||||
className="w-7 h-7 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5"
|
||||
style={{ backgroundColor: '#012068' }}
|
||||
>
|
||||
<svg className="w-3.5 h-3.5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-sm mb-1" style={{ color: '#012068' }}>Address</h4>
|
||||
<p className="text-xs leading-relaxed" style={{ color: '#333' }}>
|
||||
Department of Trauma Surgery<br />
|
||||
Room A601, 6th Floor, A Block<br />
|
||||
CMC Vellore Ranipet Campus<br />
|
||||
Kilminnal Village, Ranipet - 632517<br />
|
||||
Tamil Nadu, India
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<div
|
||||
className="w-7 h-7 rounded-full flex items-center justify-center flex-shrink-0"
|
||||
style={{ backgroundColor: '#012068' }}
|
||||
>
|
||||
<svg className="w-3.5 h-3.5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-sm mb-1" style={{ color: '#012068' }}>Phone</h4>
|
||||
<p className="text-xs" style={{ color: '#333' }}>
|
||||
0417-2224626
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<div
|
||||
className="w-7 h-7 rounded-full flex items-center justify-center flex-shrink-0"
|
||||
style={{ backgroundColor: '#012068' }}
|
||||
>
|
||||
<svg className="w-3.5 h-3.5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-sm mb-1" style={{ color: '#012068' }}>Email</h4>
|
||||
<p className="text-xs" style={{ color: '#333' }}>
|
||||
traumasurg@cmcvellore.ac.in
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right side - Contact form */}
|
||||
<div className="bg-white rounded-lg border border-gray-300 p-6 lg:p-8">
|
||||
<div className="mb-6">
|
||||
<h3 className="text-xl font-bold mb-3" style={{ color: '#012068' }}>
|
||||
Send us a message
|
||||
</h3>
|
||||
<div className="w-12 h-1 rounded-lg" style={{ backgroundColor: '#e64838' }}></div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Name and Phone row */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
placeholder="Your Name"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:border-transparent transition-all duration-200 text-base"
|
||||
style={{ '--tw-ring-color': '#012068', color:'#333' } as React.CSSProperties}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="tel"
|
||||
name="phone"
|
||||
placeholder="Your Phone"
|
||||
value={formData.phone}
|
||||
onChange={handleChange}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:border-transparent transition-all duration-200 text-base"
|
||||
style={{ '--tw-ring-color': '#012068', color:'#333' } as React.CSSProperties}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Service field */}
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="service"
|
||||
placeholder="Service and Product of Interest"
|
||||
value={formData.service}
|
||||
onChange={handleChange}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:border-transparent transition-all duration-200 text-base"
|
||||
style={{ '--tw-ring-color': '#012068', color:'#333' } as React.CSSProperties}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Email and Organization row */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Your Email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:border-transparent transition-all duration-200 text-base"
|
||||
style={{ '--tw-ring-color': '#012068', color:'#333' } as React.CSSProperties}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
name="organization"
|
||||
placeholder="Organization"
|
||||
value={formData.organization}
|
||||
onChange={handleChange}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:border-transparent transition-all duration-200 text-base"
|
||||
style={{ '--tw-ring-color': '#012068', color:'#333' } as React.CSSProperties}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Project description */}
|
||||
<div>
|
||||
<textarea
|
||||
name="description"
|
||||
placeholder="Project description"
|
||||
value={formData.description}
|
||||
onChange={handleChange}
|
||||
rows={5}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:border-transparent transition-all duration-200 text-base resize-none"
|
||||
style={{ '--tw-ring-color': '#012068', color:'#333' } as React.CSSProperties}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Submit button */}
|
||||
<div className="pt-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitting}
|
||||
className={`w-full font-semibold py-3 px-6 rounded-lg transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 text-base ${
|
||||
isSubmitting
|
||||
? 'cursor-not-allowed opacity-60'
|
||||
: 'hover:opacity-90'
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor: isSubmitting ? '#6b7280' : '#012068',
|
||||
color: '#f4f4f4',
|
||||
'--tw-ring-color': '#012068'
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<div className="flex items-center justify-center space-x-2">
|
||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
||||
<span>Sending...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center space-x-2">
|
||||
<span>Send Message</span>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact info */}
|
||||
<div className="mt-6 pt-6 border-t border-gray-300">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
||||
<div className="flex items-center space-x-2">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" style={{ color: '#e64838' }}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span style={{ color: '#012068' }}>Quick response guaranteed</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" style={{ color: '#e64838' }}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
<span style={{ color: '#012068' }}>Your data is secure</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Map Section */}
|
||||
<div className="mt-12">
|
||||
<div className="bg-white rounded-lg border border-gray-300 overflow-hidden">
|
||||
<div className="h-96 relative">
|
||||
<iframe
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3888.1234567890123!2d79.2376943!3d12.9384508!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3bad374304ae1249:0x77617f161cfad670!2sChristian+Medical+College+and+Hospital,+Ranipet+Campus+-+Vellore!5e0!3m2!1sen!2sin!4v1724612345678!5m2!1sen!2sin"
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ border: 0 }}
|
||||
allowFullScreen
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
title="Location Map"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactForm;
|
||||
402
src/components/education/CourseDetail.tsx
Normal file
402
src/components/education/CourseDetail.tsx
Normal file
@ -0,0 +1,402 @@
|
||||
// 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="https://admissions.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' }}
|
||||
>
|
||||
https://admissions.cmcvellore.ac.in/
|
||||
<ExternalLink className="w-4 h-4 ml-1" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CourseDetail;
|
||||
417
src/components/education/EducationTraining.tsx
Normal file
417
src/components/education/EducationTraining.tsx
Normal file
@ -0,0 +1,417 @@
|
||||
// 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, Globe, GraduationCap } from 'lucide-react';
|
||||
import { educationService, Course } from '../../services/educationService';
|
||||
import { upcomingEventsService, UpcomingEvent } from '../../services/upcomingEventsService';
|
||||
|
||||
const EducationTraining: React.FC = () => {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [courses, setCourses] = useState<Course[]>([]);
|
||||
const [upcomingEvents, setUpcomingEvents] = useState<UpcomingEvent[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>('All');
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
|
||||
const categories = ['All', 'Certification', 'Training', 'Workshop', 'Fellowship'];
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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;
|
||||
});
|
||||
|
||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchQuery(e.target.value);
|
||||
};
|
||||
|
||||
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 courses...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
{/* Header Section */}
|
||||
<section
|
||||
className="py-4 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' }} />
|
||||
<span className="font-medium" style={{ color: '#e64838' }}>
|
||||
Education & Training
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
{/* Page Header */}
|
||||
<div className="mb-2">
|
||||
<h1 className="text-4xl font-bold mb-4" style={{ color: '#012068' }}>
|
||||
Education & Training
|
||||
</h1>
|
||||
<p className="text-lg max-w-3xl leading-relaxed" style={{ color: '#333' }}>
|
||||
Advance your trauma care expertise with structured training programs for doctors, nurses, students, and community partners.
|
||||
</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={loadData}
|
||||
className="text-red-600 underline text-sm mt-2"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Upcoming Training Section with Dynamic Cards */}
|
||||
<section className="py-8" style={{ backgroundColor: '#fff' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<h2 className="text-3xl font-bold mb-8 text-center" style={{ color: '#012068' }}>
|
||||
Upcoming Training Programs
|
||||
</h2>
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{upcomingEvents.length > 0 ? (
|
||||
upcomingEvents.map((event) => (
|
||||
<div key={event.id} className="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl transition-shadow duration-300 border-l-4" style={{ borderLeftColor: '#012068' }}>
|
||||
<div className="flex items-center mb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold" style={{ color: '#012068' }}>
|
||||
{event.title}
|
||||
</h3>
|
||||
<div className="flex items-center text-sm">
|
||||
<Calendar className="w-4 h-4 mr-1" style={{ color: '#e64838' }} />
|
||||
<span style={{ color: '#666' }}>{event.schedule}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm leading-relaxed" style={{ color: '#666' }}>
|
||||
{event.description}
|
||||
</p>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
// Fallback to static cards if no events are loaded
|
||||
<>
|
||||
<div className="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl transition-shadow duration-300 border-l-4" style={{ borderLeftColor: '#012068' }}>
|
||||
<div className="flex items-center mb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold" style={{ color: '#012068' }}>
|
||||
Simulation-based Team Drills
|
||||
</h3>
|
||||
<div className="flex items-center text-sm">
|
||||
<Calendar className="w-4 h-4 mr-1" style={{ color: '#e64838' }} />
|
||||
<span>Q3 2025</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm leading-relaxed" style={{ color: '#666' }}>
|
||||
Hands-on simulation training designed to improve team coordination and emergency response in high-pressure trauma situations.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl transition-shadow duration-300 border-l-4" style={{ borderLeftColor: '#012068' }}>
|
||||
<div className="flex items-center mb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold" style={{ color: '#012068' }}>
|
||||
Online Webinar Series
|
||||
</h3>
|
||||
<div className="flex items-center text-sm">
|
||||
<Calendar className="w-4 h-4 mr-1" style={{ color: '#e64838' }} />
|
||||
<span>Monthly Sessions</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm leading-relaxed" style={{ color: '#666' }}>
|
||||
Monthly online sessions covering trauma ethics, young doctor support, and professional development in emergency medicine.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl transition-shadow duration-300 border-l-4" style={{ borderLeftColor: '#012068' }}>
|
||||
<div className="flex items-center mb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold" style={{ color: '#012068' }}>
|
||||
Community Education
|
||||
</h3>
|
||||
<div className="flex items-center text-sm">
|
||||
<Calendar className="w-4 h-4 mr-1" style={{ color: '#e64838' }} />
|
||||
<span>Ongoing</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm leading-relaxed" style={{ color: '#666' }}>
|
||||
Road safety fairs and school education sessions to promote trauma prevention and basic first aid awareness in the community.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Filter & Search */}
|
||||
<section className="py-8" style={{ backgroundColor: '#fff' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-8">
|
||||
{/* Category Filter */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{categories.map((category) => (
|
||||
<button
|
||||
key={category}
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors duration-200 ${selectedCategory === category
|
||||
? 'text-white'
|
||||
: 'border-2'
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor: selectedCategory === category ? '#012068' : 'transparent',
|
||||
borderColor: '#012068',
|
||||
color: selectedCategory === category ? 'white' : '#012068'
|
||||
}}
|
||||
>
|
||||
{category}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search programs..."
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
className="border-2 rounded-lg px-4 py-2 pl-4 pr-10 text-sm focus:outline-none w-64"
|
||||
style={{ borderColor: '#012068', color:'#333' }}
|
||||
/>
|
||||
<button className="absolute inset-y-0 right-0 flex items-center px-3 rounded-lg"
|
||||
style={{ backgroundColor: '#012068' }}>
|
||||
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Results Info */}
|
||||
{(selectedCategory !== 'All' || searchQuery.trim()) && (
|
||||
<div className="mb-4">
|
||||
<p className="text-sm text-gray-600">
|
||||
{filteredCourses.length === 0
|
||||
? 'No courses found matching your criteria.'
|
||||
: `Showing ${filteredCourses.length} of ${courses.length} course${courses.length !== 1 ? 's' : ''}`
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Courses Grid */}
|
||||
<section className="pb-12" style={{ backgroundColor: '#fff' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
{filteredCourses.length === 0 && !error ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-500 text-lg mb-4">
|
||||
{searchQuery.trim() || selectedCategory !== 'All'
|
||||
? 'No courses match your search criteria.'
|
||||
: 'No courses available at the moment.'
|
||||
}
|
||||
</p>
|
||||
{(searchQuery.trim() || selectedCategory !== 'All') && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchQuery('');
|
||||
setSelectedCategory('All');
|
||||
}}
|
||||
className="px-4 py-2 text-sm border border-blue-900 text-blue-900 rounded hover:bg-blue-50"
|
||||
>
|
||||
Clear Filters
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3 auto-rows-fr">
|
||||
{filteredCourses.map((course) => (
|
||||
<Link
|
||||
key={course.id}
|
||||
href={`/education-training/course-detail?id=${course.id}`}
|
||||
className="group bg-white rounded-lg overflow-hidden border border-gray-300 hover:shadow-xl transition-all duration-300 flex flex-col h-full cursor-pointer"
|
||||
>
|
||||
{/* Image */}
|
||||
<div className="relative h-48 overflow-hidden flex-shrink-0">
|
||||
<Image
|
||||
src={course.image}
|
||||
alt={course.title}
|
||||
fill
|
||||
className="object-cover transition-transform duration-300 group-hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex flex-col flex-grow">
|
||||
<div className="p-6 flex-grow flex flex-col">
|
||||
<h3
|
||||
className="text-lg font-semibold mb-3 group-hover:opacity-70 transition-opacity duration-300 h-14 overflow-hidden"
|
||||
style={{
|
||||
color: '#012068',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical'
|
||||
}}
|
||||
>
|
||||
{course.title}
|
||||
</h3>
|
||||
<p
|
||||
className="text-sm leading-relaxed mb-4 flex-grow overflow-hidden"
|
||||
style={{
|
||||
color: '#666',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 4,
|
||||
WebkitBoxOrient: 'vertical'
|
||||
}}
|
||||
>
|
||||
{course.description}
|
||||
</p>
|
||||
<div className="mt-auto">
|
||||
<span
|
||||
className="inline-block px-3 py-1 text-xs font-medium rounded-full border-2"
|
||||
style={{
|
||||
color: '#e64838',
|
||||
backgroundColor: 'transparent'
|
||||
}}
|
||||
>
|
||||
{course.category}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-6 pb-6 pt-2 border-t border-gray-100 flex-shrink-0">
|
||||
<div className="flex items-end mb-3">
|
||||
<span className="text-sm font-medium truncate" style={{ color: '#012068' }}>
|
||||
{course.instructor}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-3 text-sm" style={{ color: '#666' }}>
|
||||
<div className="flex items-center">
|
||||
<Clock className="w-4 h-4 mr-1 flex-shrink-0" />
|
||||
<span className="truncate">{course.duration}</span>
|
||||
</div>
|
||||
<div className="flex items-center ml-2">
|
||||
<Users className="w-4 h-4 mr-1 flex-shrink-0" />
|
||||
<span>{course.seats}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-sm" style={{ color: '#666' }}>
|
||||
{course.startDate ? `Starts: ${new Date(course.startDate).toLocaleDateString()}` : 'Contact for dates'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA */}
|
||||
<section className="py-16" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
<div className="max-w-4xl mx-auto px-4 text-center">
|
||||
<Award className="w-12 h-12 mx-auto mb-6" style={{ color: '#e64838' }} />
|
||||
<h2 className="text-3xl font-bold mb-4" style={{ color: '#012068' }}>
|
||||
Ready to Advance Your Trauma Care Expertise?
|
||||
</h2>
|
||||
<p className="text-lg mb-8 max-w-2xl mx-auto" style={{ color: '#666' }}>
|
||||
Join our structured training programs designed to empower healthcare professionals, nurses, and community responders with critical trauma care skills.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link
|
||||
href="/contact"
|
||||
className="inline-block px-6 py-3 text-sm font-medium rounded hover:opacity-90 transition-opacity duration-300"
|
||||
style={{
|
||||
backgroundColor: '#012068',
|
||||
color: 'white'
|
||||
}}
|
||||
>
|
||||
Contact Admissions
|
||||
</Link>
|
||||
<Link
|
||||
href="/brochure"
|
||||
className="inline-block px-6 py-3 text-sm font-medium rounded border-2 hover:opacity-70 transition-opacity duration-300"
|
||||
style={{
|
||||
borderColor: '#012068',
|
||||
color: '#012068'
|
||||
}}
|
||||
>
|
||||
Download Brochure
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EducationTraining;
|
||||
513
src/components/events/EventDetail.tsx
Normal file
513
src/components/events/EventDetail.tsx
Normal file
@ -0,0 +1,513 @@
|
||||
'use client'
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Calendar, MapPin, Clock, Users, Star, Share2, ArrowLeft, ChevronRight } from 'lucide-react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import Link from "next/link";
|
||||
import { eventAPI, Event } from '../../lib/api'; // Adjust path as needed
|
||||
|
||||
const EventDetail = () => {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const eventId = params.id as string;
|
||||
|
||||
const [eventData, setEventData] = useState<Event | null>(null);
|
||||
const [isBookmarked, setIsBookmarked] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [bookingStatus, setBookingStatus] = useState<'idle' | 'booking' | 'success' | 'error'>('idle');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchEventData = async () => {
|
||||
if (!eventId) {
|
||||
console.log('No event ID provided');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Fetching event with ID:', eventId);
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// Convert string ID to number for API call
|
||||
const numericId = parseInt(eventId, 10);
|
||||
if (isNaN(numericId)) {
|
||||
console.error('Invalid event ID:', eventId);
|
||||
throw new Error('Invalid event ID');
|
||||
}
|
||||
console.log('Converted to numeric ID:', numericId);
|
||||
|
||||
const event = await eventAPI.getEventById(numericId);
|
||||
console.log('Fetched event:', event);
|
||||
setEventData(event);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch event data:', error);
|
||||
setEventData(null);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchEventData();
|
||||
}, [eventId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (eventData && typeof window !== 'undefined') {
|
||||
try {
|
||||
const bookmarkedEvents = JSON.parse(localStorage.getItem('bookmarkedEvents') || '[]');
|
||||
setIsBookmarked(bookmarkedEvents.includes(eventData.id.toString()));
|
||||
} catch (error) {
|
||||
console.error('Error accessing localStorage:', error);
|
||||
}
|
||||
}
|
||||
}, [eventData]);
|
||||
|
||||
const handleBookmark = () => {
|
||||
if (!eventData || typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
const bookmarkedEvents = JSON.parse(localStorage.getItem('bookmarkedEvents') || '[]');
|
||||
const eventIdStr = eventData.id.toString();
|
||||
let updatedBookmarks;
|
||||
if (isBookmarked) {
|
||||
updatedBookmarks = bookmarkedEvents.filter((id: string) => id !== eventIdStr);
|
||||
} else {
|
||||
updatedBookmarks = [...bookmarkedEvents, eventIdStr];
|
||||
}
|
||||
localStorage.setItem('bookmarkedEvents', JSON.stringify(updatedBookmarks));
|
||||
setIsBookmarked(!isBookmarked);
|
||||
} catch (error) {
|
||||
console.error('Error updating bookmarks:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBookSeat = async () => {
|
||||
if (!eventData) return;
|
||||
setBookingStatus('booking');
|
||||
try {
|
||||
// Replace with actual booking API call
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
setBookingStatus('success');
|
||||
setTimeout(() => setBookingStatus('idle'), 2000);
|
||||
} catch (error) {
|
||||
setBookingStatus('error');
|
||||
setTimeout(() => setBookingStatus('idle'), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
const handleShare = async () => {
|
||||
if (!eventData || typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
if (navigator.share) {
|
||||
await navigator.share({
|
||||
title: eventData.title,
|
||||
url: window.location.href,
|
||||
});
|
||||
} else {
|
||||
await navigator.clipboard.writeText(window.location.href);
|
||||
alert('Event link copied to clipboard!');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Share failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoBack = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
const getBookingButtonText = () => {
|
||||
switch (bookingStatus) {
|
||||
case 'booking': return 'Booking...';
|
||||
case 'success': return 'Booked!';
|
||||
case 'error': return 'Try Again';
|
||||
default: return 'Book Your Seat';
|
||||
}
|
||||
};
|
||||
|
||||
const getBookingButtonStyle = () => {
|
||||
switch (bookingStatus) {
|
||||
case 'booking': return 'bg-gray-600 cursor-not-allowed';
|
||||
case 'success': return 'bg-green-600 hover:bg-green-700';
|
||||
case 'error': return 'bg-red-600 hover:bg-red-700';
|
||||
default: return 'hover:opacity-90';
|
||||
}
|
||||
};
|
||||
|
||||
// Helper functions to format API data for display
|
||||
const formatPrice = (event: Event) => {
|
||||
if (event.fee && event.fee.length > 0) {
|
||||
return `₹${event.fee[0].cost} per seat`;
|
||||
}
|
||||
return '₹1,800 per seat';
|
||||
};
|
||||
|
||||
const getPrimaryVenue = (event: Event) => {
|
||||
if (event.venue && event.venue.length > 0) {
|
||||
return {
|
||||
name: event.venue[0].title || 'Medical College Auditorium',
|
||||
address: event.venue[0].address || 'Chennai, Tamil Nadu'
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: 'Medical College Auditorium',
|
||||
address: 'Chennai, Tamil Nadu'
|
||||
};
|
||||
};
|
||||
|
||||
const getSafeImageUrl = (imageUrl: string | undefined, fallback: string) => {
|
||||
return imageUrl && imageUrl.trim() !== '' ? imageUrl : fallback;
|
||||
};
|
||||
|
||||
const getGalleryImages = (galleryImages: string[] | undefined) => {
|
||||
const fallbackImages = [
|
||||
'https://images.unsplash.com/photo-1576091160550-2173dba999ef?w=400&h=200&fit=crop',
|
||||
'https://images.unsplash.com/photo-1582750433449-648ed127bb54?w=400&h=200&fit=crop'
|
||||
];
|
||||
|
||||
if (!galleryImages || galleryImages.length === 0) {
|
||||
return fallbackImages;
|
||||
}
|
||||
|
||||
const validImages = galleryImages.filter(img => img && img.trim() !== '');
|
||||
if (validImages.length === 0) {
|
||||
return fallbackImages;
|
||||
}
|
||||
|
||||
// Ensure we have at least 2 images for the layout
|
||||
while (validImages.length < 2) {
|
||||
validImages.push(fallbackImages[validImages.length % fallbackImages.length]);
|
||||
}
|
||||
|
||||
return validImages.slice(0, 2);
|
||||
};
|
||||
|
||||
// Format description data for display
|
||||
const getFormattedDescription = (event: Event) => {
|
||||
return {
|
||||
overview: event.description || 'Join leading medical professionals for this comprehensive event focusing on the latest developments in healthcare.',
|
||||
highlights: event.highlights && event.highlights.length > 0 ? event.highlights : [
|
||||
'Expert presentations and case studies',
|
||||
'Latest medical innovations and techniques',
|
||||
'Hands-on workshops and demonstrations',
|
||||
'Networking sessions with industry leaders'
|
||||
],
|
||||
includes: [
|
||||
'CME Credits',
|
||||
'Course materials and resources',
|
||||
'Networking breaks and lunch',
|
||||
'Certificate of attendance'
|
||||
],
|
||||
targetAudience: event.subject ? [event.subject] : [
|
||||
'Medical professionals',
|
||||
'Healthcare practitioners',
|
||||
'Medical students and residents',
|
||||
'Healthcare administrators'
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
{/* 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' }} />
|
||||
<a
|
||||
href="/events"
|
||||
className="hover:opacity-70 transition-opacity duration-200"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
Medical Events
|
||||
</a>
|
||||
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
|
||||
<span className="font-medium" style={{ color: '#e64838' }}>
|
||||
Loading...
|
||||
</span>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="py-6">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<div className="bg-white shadow-lg rounded-lg overflow-hidden">
|
||||
<div className="animate-pulse">
|
||||
<div className="h-64 md:h-80 bg-gray-200"></div>
|
||||
<div className="p-4 md:p-8">
|
||||
<div className="h-4 bg-gray-200 rounded mb-4"></div>
|
||||
<div className="h-6 md:h-8 bg-gray-200 rounded mb-6"></div>
|
||||
<div className="space-y-3">
|
||||
<div className="h-4 bg-gray-200 rounded"></div>
|
||||
<div className="h-4 bg-gray-200 rounded"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!eventData) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
{/* 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' }} />
|
||||
<a
|
||||
href="/events"
|
||||
className="hover:opacity-70 transition-opacity duration-200"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
Medical Events
|
||||
</a>
|
||||
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
|
||||
<span className="font-medium" style={{ color: '#e64838' }}>
|
||||
Event Not Found
|
||||
</span>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="py-6">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<div className="bg-white shadow-lg rounded-lg p-4 md:p-8 text-center">
|
||||
<h1 className="text-xl md:text-2xl font-medium mb-4" style={{ color: '#012068' }}>
|
||||
Event Not Found
|
||||
</h1>
|
||||
<button
|
||||
onClick={handleGoBack}
|
||||
className="px-6 py-2 text-sm rounded-lg hover:opacity-90 transition-opacity"
|
||||
style={{ backgroundColor: '#012068', color: '#f4f4f4' }}
|
||||
>
|
||||
Go Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const venue = getPrimaryVenue(eventData);
|
||||
const description = getFormattedDescription(eventData);
|
||||
const galleryImages = getGalleryImages(eventData.galleryImages);
|
||||
const mainImage = getSafeImageUrl(eventData.mainImage, 'https://images.unsplash.com/photo-1559757148-5c350d0d3c56?w=800&h=400&fit=crop');
|
||||
|
||||
return (
|
||||
<div className="min-h-screen" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
{/* 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' }} />
|
||||
<a
|
||||
href="/events"
|
||||
className="hover:opacity-70 transition-opacity duration-200"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
Medical Events
|
||||
</a>
|
||||
<ChevronRight className="w-4 h-4" style={{ color: '#012068' }} />
|
||||
<span className="font-medium truncate" style={{ color: '#e64838' }}>
|
||||
{eventData.title}
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
{/* Back Button */}
|
||||
<div className="mt-4">
|
||||
<button
|
||||
onClick={handleGoBack}
|
||||
className="flex items-center hover:opacity-70 text-sm transition-opacity duration-200"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
Back to Events
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="py-6">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
{/* Image Section */}
|
||||
<div className="bg-white shadow-lg rounded-md overflow-hidden mb-6" style={{ borderColor: '#012068' }}>
|
||||
<div className="relative">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-1 h-64 md:h-80">
|
||||
<div className="md:col-span-2 relative overflow-hidden">
|
||||
<img
|
||||
src={mainImage}
|
||||
alt={eventData.title}
|
||||
className="w-full h-full object-cover hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden md:block space-y-1">
|
||||
{galleryImages.map((image, index) => (
|
||||
<div key={index} className="h-1/2 relative overflow-hidden">
|
||||
<img
|
||||
src={image}
|
||||
alt={`Gallery ${index + 1}`}
|
||||
className="w-full h-full object-cover hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Event Details Section */}
|
||||
<div className="bg-white shadow-lg rounded-lg overflow-hidden" style={{ borderColor: '#012068' }}>
|
||||
<div className="p-4 md:p-8">
|
||||
{/* Header Section */}
|
||||
<div className="flex flex-col sm:flex-row justify-between items-start gap-4 mb-6">
|
||||
<div className="flex-1">
|
||||
<div className="text-xs font-medium mb-2" style={{ color: '#e64838' }}>
|
||||
{eventData.date}
|
||||
</div>
|
||||
<h1 className="text-2xl md:text-3xl font-medium leading-tight" style={{ color: '#012068' }}>
|
||||
{eventData.title}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="text-left sm:text-right w-full sm:w-auto">
|
||||
<div className="text-xs mb-1" style={{ color: '#012068', opacity: 0.8 }}>From</div>
|
||||
<div className="text-lg font-medium mb-3" style={{ color: '#e64838' }}>
|
||||
{formatPrice(eventData)}
|
||||
</div>
|
||||
<button
|
||||
onClick={handleBookSeat}
|
||||
disabled={bookingStatus === 'booking'}
|
||||
className={`w-full sm:w-auto px-6 py-2 text-sm rounded-lg transition-all duration-200 ${getBookingButtonStyle()}`}
|
||||
style={{
|
||||
backgroundColor: bookingStatus === 'idle' ? '#012068' : undefined,
|
||||
color: '#f4f4f4'
|
||||
}}
|
||||
>
|
||||
{getBookingButtonText()}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Event Info Grid */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
|
||||
<div className="flex items-center" style={{ color: '#012068' }}>
|
||||
<Calendar className="w-4 h-4 mr-3 flex-shrink-0" style={{ color: '#e64838' }} />
|
||||
<div>
|
||||
<div className="text-sm font-medium">{eventData.date}</div>
|
||||
<div className="text-xs opacity-80">Full Day Event</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center" style={{ color: '#012068' }}>
|
||||
<Clock className="w-4 h-4 mr-3 flex-shrink-0" style={{ color: '#e64838' }} />
|
||||
<div>
|
||||
<div className="text-sm font-medium">9:00 AM - 5:00 PM</div>
|
||||
<div className="text-xs opacity-80">8 hours</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center sm:col-span-2 lg:col-span-1" style={{ color: '#012068' }}>
|
||||
<MapPin className="w-4 h-4 mr-3 flex-shrink-0" style={{ color: '#e64838' }} />
|
||||
<div>
|
||||
<div className="text-sm font-medium">{venue.name}</div>
|
||||
<div className="text-xs opacity-80">{venue.address}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* About Section */}
|
||||
<div className="space-y-4 mb-8">
|
||||
<h3 className="text-lg font-medium" style={{ color: '#012068' }}>About This Event</h3>
|
||||
<p className="text-md leading-relaxed" style={{ color: '#333' }}>{description.overview}</p>
|
||||
|
||||
{description.highlights.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-md font-medium mb-2" style={{ color: '#012068' }}>Event Highlights</h4>
|
||||
<ul className="text-sm space-y-1" style={{ color: '#333' }}>
|
||||
{description.highlights.map((highlight, index) => (
|
||||
<li key={index}>• {highlight}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Attendees Info */}
|
||||
<div className="mb-8 p-4 rounded-lg" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||
<div className="flex items-center" style={{ color: '#012068' }}>
|
||||
<Users className="w-4 h-4 mr-2 flex-shrink-0" style={{ color: '#e64838' }} />
|
||||
<span className="text-xs">Medical professionals attending</span>
|
||||
</div>
|
||||
<div className="text-xs" style={{ color: '#012068', opacity: 0.8 }}>
|
||||
Event ID: #{eventData.id}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info Cards */}
|
||||
<div className="mb-8 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="p-4 border rounded-lg" style={{ borderColor: '#012068' }}>
|
||||
<h4 className="text-sm font-medium mb-2" style={{ color: '#012068' }}>What's Included</h4>
|
||||
<ul className="text-xs space-y-1" style={{ color: '#333' }}>
|
||||
{description.includes.map((item, index) => (
|
||||
<li key={index}>• {item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="p-4 border rounded-lg" style={{ borderColor: '#012068' }}>
|
||||
<h4 className="text-sm font-medium mb-2" style={{ color: '#012068' }}>Target Audience</h4>
|
||||
<ul className="text-xs space-y-1" style={{ color: '#333' }}>
|
||||
{description.targetAudience.map((audience, index) => (
|
||||
<li key={index}>• {audience}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Actions */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-4">
|
||||
<button
|
||||
onClick={handleShare}
|
||||
className="flex items-center text-xs hover:underline"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
<Share2 className="w-3 h-3 mr-1" />
|
||||
Share Event
|
||||
</button>
|
||||
<span className="text-gray-400 hidden sm:inline">|</span>
|
||||
<span className="text-xs" style={{ color: '#012068', opacity: 0.8 }}>
|
||||
📍 {venue.name}, {venue.address}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventDetail;
|
||||
415
src/components/events/MedicalEventsComponent.tsx
Normal file
415
src/components/events/MedicalEventsComponent.tsx
Normal file
@ -0,0 +1,415 @@
|
||||
'use client'
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import Link from "next/link";
|
||||
import { eventAPI, Event } from '../../lib/api'; // Adjust path as needed
|
||||
|
||||
const MedicalEventsComponent = () => {
|
||||
const [selectedPeriod, setSelectedPeriod] = useState('Upcoming Events');
|
||||
const [activeTab, setActiveTab] = useState('Upcoming Events');
|
||||
const [upcomingEvents, setUpcomingEvents] = useState<Event[]>([]);
|
||||
const [pastEvents, setPastEvents] = useState<Event[]>([]);
|
||||
const [nextEvent, setNextEvent] = useState<Event | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
loadEvents();
|
||||
}, []);
|
||||
|
||||
const loadEvents = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [upcoming, past] = await Promise.all([
|
||||
eventAPI.getUpcomingEvents(),
|
||||
eventAPI.getPastEvents()
|
||||
]);
|
||||
|
||||
setUpcomingEvents(upcoming);
|
||||
setPastEvents(past);
|
||||
|
||||
// Set the next event (first upcoming event)
|
||||
if (upcoming.length > 0) {
|
||||
setNextEvent(upcoming[0]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading events:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Filter events based on search term
|
||||
const filteredEvents = () => {
|
||||
const events = activeTab === 'Upcoming Events' ? upcomingEvents : pastEvents;
|
||||
if (!searchTerm) return events;
|
||||
|
||||
return events.filter(event =>
|
||||
event.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
event.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
event.subject.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
};
|
||||
|
||||
// Navigation function for App Router
|
||||
const navigateToEventDetail = (eventId: string | number) => {
|
||||
router.push(`/event-detail/${eventId}`);
|
||||
};
|
||||
|
||||
// Format price display from fees
|
||||
const formatPrice = (event: Event) => {
|
||||
if (event.fee && event.fee.length > 0) {
|
||||
return `₹${event.fee[0].cost} per seat`;
|
||||
}
|
||||
return '₹1,800 per seat'; // fallback price
|
||||
};
|
||||
|
||||
// Get safe image URL with fallback
|
||||
const getSafeImageUrl = (imageUrl: string | undefined, fallback: string) => {
|
||||
return imageUrl && imageUrl.trim() !== '' ? imageUrl : fallback;
|
||||
};
|
||||
|
||||
// Get gallery images with fallbacks
|
||||
const getGalleryImages = (galleryImages: string[] | undefined) => {
|
||||
const fallbackImages = [
|
||||
'https://images.unsplash.com/photo-1551601651-2a8555f1a136?w=200&h=100&fit=crop',
|
||||
'https://images.unsplash.com/photo-1582750433449-648ed127bb54?w=200&h=100&fit=crop',
|
||||
'https://images.unsplash.com/photo-1638202993928-7267aad84c31?w=200&h=100&fit=crop',
|
||||
'https://images.unsplash.com/photo-1551601651-2a8555f1a136?w=200&h=100&fit=crop'
|
||||
];
|
||||
|
||||
if (!galleryImages || galleryImages.length === 0) {
|
||||
return fallbackImages;
|
||||
}
|
||||
|
||||
// Fill missing images with fallbacks
|
||||
const validImages = galleryImages.filter(img => img && img.trim() !== '');
|
||||
while (validImages.length < 4 && validImages.length < fallbackImages.length) {
|
||||
validImages.push(fallbackImages[validImages.length]);
|
||||
}
|
||||
|
||||
return validImages.slice(0, 4);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-lg" style={{ color: '#012068' }}>Loading events...</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' }}>
|
||||
Medical Events
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
{/* Page Header */}
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center mb-4">
|
||||
<h1 className="text-2xl md:text-3xl font-bold" style={{ color: '#012068' }}>
|
||||
Medical Events
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-sm md:text-base max-w-2xl leading-relaxed" style={{ color: '#333' }}>
|
||||
Discover upcoming medical conferences, workshops, and professional development events
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="py-6" style={{ backgroundColor: '#fff' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
{/* Header with dropdown and search */}
|
||||
<div className="flex flex-col sm:flex-row justify-end items-start sm:items-center gap-4 mb-6">
|
||||
<div className="relative w-full sm:w-auto">
|
||||
<select
|
||||
value={selectedPeriod}
|
||||
onChange={(e) => setSelectedPeriod(e.target.value)}
|
||||
className="appearance-none bg-white text-sm border rounded-lg px-4 py-2 pr-8 focus:outline-none w-full sm:w-auto"
|
||||
style={{
|
||||
borderColor: '#012068',
|
||||
color: '#333'
|
||||
}}
|
||||
>
|
||||
<option>Upcoming Events</option>
|
||||
<option>This Week</option>
|
||||
<option>This Month</option>
|
||||
<option>This Year</option>
|
||||
</select>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
|
||||
<svg className="w-4 h-4" style={{ color: '#012068' }} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative w-full sm:w-64">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search an event..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="border rounded-lg px-4 py-2 pl-4 pr-10 text-sm focus:outline-none w-full"
|
||||
style={{
|
||||
borderColor: '#012068',
|
||||
color: '#012068'
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
className="absolute inset-y-0 right-0 flex items-center px-3 rounded-r-lg"
|
||||
style={{ backgroundColor: '#012068' }}
|
||||
>
|
||||
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Next Event Hero Section */}
|
||||
{nextEvent && (
|
||||
<div className="mb-8">
|
||||
<h2 className="text-lg font-medium mb-4" style={{ color: '#012068' }}>Next Event</h2>
|
||||
<div
|
||||
className="bg-white border border-gray-100 rounded-lg overflow-hidden cursor-pointer hover:shadow-lg transition-shadow"
|
||||
onClick={() => navigateToEventDetail(nextEvent.id)}
|
||||
>
|
||||
<div className="relative h-48 md:h-64">
|
||||
<img
|
||||
src={getSafeImageUrl(nextEvent.mainImage, "https://images.unsplash.com/photo-1576091160550-2173dba999ef?w=800&h=300&fit=crop")}
|
||||
alt={nextEvent.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex space-x-2">
|
||||
<div className="w-2 h-2 bg-white rounded-full shadow"></div>
|
||||
<div className="w-2 h-2 bg-gray-400 rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-gray-400 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 md:p-6">
|
||||
<div className="flex flex-col lg:flex-row lg:justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="text-xs font-medium mb-3" style={{ color: '#e64838' }}>{nextEvent.date}</div>
|
||||
<div className="text-lg md:text-xl font-medium mb-2" style={{ color: '#012068' }}>
|
||||
{nextEvent.title}
|
||||
</div>
|
||||
<div className="text-xs leading-relaxed mb-1" style={{ color: '#333' }}>
|
||||
{nextEvent.description}
|
||||
</div>
|
||||
<div className="text-xs leading-relaxed mb-4" style={{ color: '#333' }}>
|
||||
{nextEvent.detail}
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
|
||||
<div
|
||||
className="text-xs cursor-pointer hover:underline"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
Share Event
|
||||
</div>
|
||||
<div className="text-xs" style={{ color: '#012068' }}>
|
||||
📍 {nextEvent.venue?.[0]?.address || 'Convention Center, Medical District'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-left lg:text-right">
|
||||
<button
|
||||
className="w-full lg:w-auto px-6 py-2 text-sm rounded-lg mb-2 transition-colors hover:opacity-90"
|
||||
style={{
|
||||
backgroundColor: '#012068',
|
||||
color: '#f4f4f4'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
console.log('Book seat clicked');
|
||||
}}
|
||||
>
|
||||
Book Your Seat
|
||||
</button>
|
||||
<div className="text-sm font-medium" style={{ color: '#e64838' }}>{formatPrice(nextEvent)}</div>
|
||||
<div className="text-xs mt-1" style={{ color: '#333' }}>
|
||||
Early bird discount available
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-8">
|
||||
{/* Main Content */}
|
||||
<div className="flex-1">
|
||||
{/* Tabs */}
|
||||
<div className="border-b mb-6" style={{ borderColor: '#666666ff' }}>
|
||||
<div className="flex space-x-4 md:space-x-8">
|
||||
<button
|
||||
onClick={() => setActiveTab('Upcoming Events')}
|
||||
className={`pb-3 text-sm ${
|
||||
activeTab === 'Upcoming Events'
|
||||
? 'border-b-2 font-medium'
|
||||
: 'hover:opacity-70'
|
||||
}`}
|
||||
style={{
|
||||
borderColor: activeTab === 'Upcoming Events' ? '#e64838' : 'transparent',
|
||||
color: activeTab === 'Upcoming Events' ? '#012068' : '#012068'
|
||||
}}
|
||||
>
|
||||
Upcoming Events ({upcomingEvents.length})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('Past Events')}
|
||||
className={`pb-3 text-sm ${
|
||||
activeTab === 'Past Events'
|
||||
? 'border-b-2 font-medium'
|
||||
: 'hover:opacity-70'
|
||||
}`}
|
||||
style={{
|
||||
borderColor: activeTab === 'Past Events' ? '#e64838' : 'transparent',
|
||||
color: activeTab === 'Past Events' ? '#012068' : '#012068'
|
||||
}}
|
||||
>
|
||||
Past Events ({pastEvents.length})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Event Rows */}
|
||||
<div className="space-y-6">
|
||||
{filteredEvents().length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-500">No events found.</p>
|
||||
</div>
|
||||
) : (
|
||||
filteredEvents().map((event) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className="bg-white border border-gray-100 rounded-lg p-4 md:p-6 cursor-pointer hover:shadow-lg transition-shadow"
|
||||
onClick={() => navigateToEventDetail(event.id)}
|
||||
>
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
{/* Images Section */}
|
||||
<div className="flex flex-col sm:flex-row gap-1 md:w-auto">
|
||||
{/* Main image */}
|
||||
<div className="w-full sm:w-48 h-32 md:h-30 flex-shrink-0 rounded-xs overflow-hidden">
|
||||
<img
|
||||
src={getSafeImageUrl(event.mainImage, "https://images.unsplash.com/photo-1559757148-5c350d0d3c56?w=400&h=200&fit=crop")}
|
||||
alt={event.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Gallery grid */}
|
||||
<div className="grid grid-cols-2 gap-1 w-full sm:w-60 h-32 md:h-30">
|
||||
{getGalleryImages(event.galleryImages).map((img, index) => (
|
||||
<div key={index} className="rounded-xs overflow-hidden">
|
||||
<img
|
||||
src={img}
|
||||
alt={`${event.title} gallery ${index + 1}`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Event details */}
|
||||
<div className="flex-1 text-sm">
|
||||
<div className="mb-1 font-medium text-xs" style={{ color: '#e64838' }}>
|
||||
{event.date}
|
||||
</div>
|
||||
<div className="font-medium mb-2 text-lg md:text-xl" style={{ color: '#012068' }}>
|
||||
{event.title}
|
||||
</div>
|
||||
<div className="text-xs leading-relaxed mb-1" style={{ color: '#333' }}>
|
||||
{event.description}
|
||||
</div>
|
||||
<div className="text-xs leading-relaxed mb-4" style={{ color: '#333' }}>
|
||||
{event.detail}
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
|
||||
<button
|
||||
className="text-xs hover:underline text-left"
|
||||
style={{ color: '#012068' }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigateToEventDetail(event.id);
|
||||
}}
|
||||
>
|
||||
View Details
|
||||
</button>
|
||||
<span className="text-gray-400 hidden sm:inline">|</span>
|
||||
<span className="text-xs font-medium" style={{ color: '#e64838' }}>
|
||||
{formatPrice(event)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Sidebar */}
|
||||
<div className="w-full lg:w-64 lg:border-l lg:pl-4" style={{ borderColor: '#666666ff' }}>
|
||||
<h3 className="text-lg font-medium mb-6" style={{ color: '#012068' }}>
|
||||
Recent Past Events
|
||||
</h3>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-1 gap-6">
|
||||
{pastEvents.slice(0, 2).map((event) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className="cursor-pointer hover:bg-gray-50 p-2 rounded-lg transition-colors"
|
||||
onClick={() => navigateToEventDetail(event.id)}
|
||||
>
|
||||
<div className="w-full h-32 lg:h-28 mb-3 overflow-hidden rounded-xs">
|
||||
<img
|
||||
src={getSafeImageUrl(event.mainImage, "https://images.unsplash.com/photo-1551601651-2a8555f1a136?w=300&h=200&fit=crop")}
|
||||
alt={event.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<div className="mb-1 font-medium text-xs" style={{ color: '#e64838' }}>
|
||||
{event.date}
|
||||
</div>
|
||||
<div className="font-medium mb-1" style={{ color: '#012068' }}>
|
||||
{event.title}
|
||||
</div>
|
||||
<div className="text-xs leading-relaxed mb-1" style={{ color: '#333' }}>
|
||||
{event.description}
|
||||
</div>
|
||||
<div className="text-xs leading-relaxed" style={{ color: '#333' }}>
|
||||
{event.detail}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MedicalEventsComponent;
|
||||
286
src/components/faculty/TeamListing.tsx
Normal file
286
src/components/faculty/TeamListing.tsx
Normal file
@ -0,0 +1,286 @@
|
||||
'use client'
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { FacultyService, TeamMember } from '../../lib/facultyData';
|
||||
|
||||
interface TeamListingProps {
|
||||
title?: string;
|
||||
onMemberClick?: (member: TeamMember) => void;
|
||||
}
|
||||
|
||||
const TeamListing: React.FC<TeamListingProps> = ({
|
||||
title = "Our Faculty",
|
||||
onMemberClick
|
||||
}) => {
|
||||
const [teamMembers, setTeamMembers] = useState<TeamMember[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadFacultyData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const faculty = await FacultyService.getAllFaculty();
|
||||
setTeamMembers(faculty);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
console.error('Failed to load faculty data:', err);
|
||||
setError('Failed to load faculty data. Please try again later.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadFacultyData();
|
||||
}, []);
|
||||
|
||||
// Filter members by category
|
||||
const facultyMembers = teamMembers.filter(member => member.category === 'FACULTY');
|
||||
const supportTeam = teamMembers.filter(member => member.category === 'SUPPORT_TEAM');
|
||||
const traineesAndFellows = teamMembers.filter(member => member.category === 'TRAINEE_FELLOW');
|
||||
|
||||
const handleMemberClick = (member: TeamMember) => {
|
||||
if (onMemberClick) {
|
||||
onMemberClick(member);
|
||||
} else {
|
||||
window.location.href = `/faculty/${member.id}`;
|
||||
}
|
||||
};
|
||||
|
||||
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 data...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<p className="text-red-600 mb-4">{error}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const TeamMemberCard: React.FC<{ member: TeamMember }> = ({ member }) => {
|
||||
const [imageError, setImageError] = useState(false);
|
||||
const [imageLoading, setImageLoading] = useState(true);
|
||||
|
||||
const handleImageError = (e: React.SyntheticEvent<HTMLImageElement>) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
console.error('Image failed to load:', member.image);
|
||||
console.error('Member:', member.name, 'Professor ID:', member.professorId);
|
||||
|
||||
if (!imageError) {
|
||||
setImageError(true);
|
||||
target.src = '/images/default-avatar.jpg';
|
||||
}
|
||||
};
|
||||
|
||||
const handleImageLoad = () => {
|
||||
console.log('Image loaded successfully:', member.image);
|
||||
setImageLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="group cursor-pointer bg-white rounded-lg border border-gray-300 overflow-hidden hover:shadow-lg transition-all duration-300"
|
||||
onClick={() => handleMemberClick(member)}
|
||||
>
|
||||
<div className="relative aspect-square overflow-hidden">
|
||||
{imageLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-gray-100">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<img
|
||||
src={member.image}
|
||||
alt={member.name}
|
||||
className={`w-full h-full object-cover transition-transform duration-300 group-hover:scale-105 ${
|
||||
imageLoading ? 'opacity-0' : 'opacity-100'
|
||||
}`}
|
||||
onError={handleImageError}
|
||||
onLoad={handleImageLoad}
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
{imageError && (
|
||||
<div className="absolute top-2 right-2 bg-red-500 text-white text-xs px-2 py-1 rounded">
|
||||
Default
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-4 sm:p-6">
|
||||
<h3 className="text-lg font-medium mb-2 group-hover:opacity-70 transition-opacity" style={{ color: '#012068' }}>
|
||||
{member.name}
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed" style={{ color: '#333' }}>
|
||||
{member.position}
|
||||
</p>
|
||||
{member.department && (
|
||||
<p className="text-xs mt-1" style={{ color: '#666' }}>
|
||||
{member.department}
|
||||
</p>
|
||||
)}
|
||||
{member.specialty && (
|
||||
<p className="text-xs mt-1 font-medium" style={{ color: '#e64838' }}>
|
||||
{member.specialty}
|
||||
</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' }}>
|
||||
Faculty
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center mb-4">
|
||||
<h1 className="text-3xl font-bold" style={{ color: '#012068' }}>
|
||||
{title}
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-base max-w-3xl leading-relaxed" style={{ color: '#333' }}>
|
||||
Meet our dedicated team of medical professionals, researchers, and support staff committed to advancing healthcare, education, and patient outcomes at Christian Medical College, Vellore
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Faculty Section */}
|
||||
{facultyMembers.length > 0 && (
|
||||
<section className="py-8" style={{ backgroundColor: '#fff' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-2xl font-bold mb-2" style={{ color: '#012068' }}>
|
||||
Faculty Members
|
||||
</h2>
|
||||
<p className="text-sm" style={{ color: '#666' }}>
|
||||
Our experienced faculty members leading medical education and research
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{facultyMembers.map((member) => (
|
||||
<TeamMemberCard key={member.id} member={member} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Support Team Section */}
|
||||
{supportTeam.length > 0 && (
|
||||
<section className="py-8" style={{ backgroundColor: '#f9f9f9' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<div className="mb-8">
|
||||
<h2 className="text-2xl font-bold mb-4" style={{ color: '#012068' }}>
|
||||
Support Team
|
||||
</h2>
|
||||
<p className="text-base leading-relaxed mb-2" style={{ color: '#012068' }}>
|
||||
<strong>Clinical Support Staff</strong> - Essential team members providing specialized support services
|
||||
</p>
|
||||
<p className="text-sm" style={{ color: '#012068' }}>
|
||||
<strong>Administrative & Technical Support</strong> - Dedicated professionals ensuring smooth operations
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{supportTeam.map((member) => (
|
||||
<TeamMemberCard key={member.id} member={member} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Trainees & Fellows Section */}
|
||||
{traineesAndFellows.length > 0 && (
|
||||
<section className="py-8" style={{ backgroundColor: '#fff' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<div className="mb-8">
|
||||
<h2 className="text-2xl font-bold mb-4" style={{ color: '#012068' }}>
|
||||
Trainees & Fellows
|
||||
</h2>
|
||||
<p className="text-sm" style={{ color: '#666' }}>
|
||||
Medical trainees, residents, and fellows advancing their skills and contributing to patient care
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{traineesAndFellows.map((member) => (
|
||||
<TeamMemberCard key={member.id} member={member} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Show message if no faculty data */}
|
||||
{teamMembers.length === 0 && !loading && (
|
||||
<section className="py-16 text-center">
|
||||
<div className="max-w-2xl mx-auto px-4">
|
||||
<h2 className="text-2xl font-bold mb-4" style={{ color: '#012068' }}>
|
||||
No Faculty Data Available
|
||||
</h2>
|
||||
<p className="text-gray-600 mb-6">
|
||||
Faculty information is currently being updated. Please check back later or contact administration.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Refresh Page
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Show message if all categories are empty but we have data */}
|
||||
{teamMembers.length > 0 && facultyMembers.length === 0 && supportTeam.length === 0 && traineesAndFellows.length === 0 && (
|
||||
<section className="py-16 text-center">
|
||||
<div className="max-w-2xl mx-auto px-4">
|
||||
<h2 className="text-2xl font-bold mb-4" style={{ color: '#012068' }}>
|
||||
No Categorized Faculty Available
|
||||
</h2>
|
||||
<p className="text-gray-600 mb-6">
|
||||
Faculty members need to be assigned to categories. Please contact administration.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamListing;
|
||||
391
src/components/faculty/TeamMemberDetail.tsx
Normal file
391
src/components/faculty/TeamMemberDetail.tsx
Normal file
@ -0,0 +1,391 @@
|
||||
'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);
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
// Create a comprehensive description from the member's details
|
||||
const getFullDescription = () => {
|
||||
return member.description || `${member.name} is a dedicated member of our faculty with expertise in ${member.specialty?.toLowerCase() || 'medical practice'}. They bring valuable experience in 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;
|
||||
};
|
||||
|
||||
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>
|
||||
</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 */}
|
||||
<div className="aspect-square relative overflow-hidden">
|
||||
<img
|
||||
src={member.image}
|
||||
alt={member.name}
|
||||
className="w-full h-full object-cover transform hover:scale-105 transition-transform duration-300"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.src = '/images/default-avatar.jpg';
|
||||
}}
|
||||
/>
|
||||
</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.description || `${member.name} is a dedicated 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 && (
|
||||
<span className="text-xs" style={{ color: '#666' }}>
|
||||
{skill.level}% proficiency
|
||||
</span>
|
||||
)}
|
||||
</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;
|
||||
258
src/components/home/EventSection.tsx
Normal file
258
src/components/home/EventSection.tsx
Normal file
@ -0,0 +1,258 @@
|
||||
'use client'
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { eventAPI, Event } from '../../lib/api';
|
||||
|
||||
const EventsSection = () => {
|
||||
const router = useRouter();
|
||||
const [upcomingEvents, setUpcomingEvents] = useState<Event[]>([]);
|
||||
const [pastEvents, setPastEvents] = useState<Event[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadEvents();
|
||||
}, []);
|
||||
|
||||
const loadEvents = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [upcoming, past] = await Promise.all([
|
||||
eventAPI.getUpcomingEvents(),
|
||||
eventAPI.getPastEvents()
|
||||
]);
|
||||
setUpcomingEvents(upcoming);
|
||||
setPastEvents(past);
|
||||
} catch (error) {
|
||||
console.error('Error loading events:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Navigation function for App Router
|
||||
const navigateToEventDetail = (eventId: string | number) => {
|
||||
router.push(`/event-detail/${eventId}`);
|
||||
};
|
||||
|
||||
const navigateToAllEvents = () => {
|
||||
router.push('/events');
|
||||
};
|
||||
|
||||
// Format price display from fees
|
||||
const formatPrice = (event: Event) => {
|
||||
if (event.fee && event.fee.length > 0) {
|
||||
return `₹${event.fee[0].cost} per ticket`;
|
||||
}
|
||||
return '₹1,800 per ticket';
|
||||
};
|
||||
|
||||
// Top section events (first 4 events for the grid)
|
||||
const topEvents = upcomingEvents.slice(0, 4);
|
||||
|
||||
// Featured event (first event)
|
||||
const featuredEvent = upcomingEvents.length > 0 ? upcomingEvents[0] : null;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="py-12 px-6 sm:px-8 md:px-6 lg:px-6 xl:px-6 bg-white max-w-7xl mx-auto">
|
||||
<div className="text-center" style={{ color: '#012068' }}>Loading events...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-12 px-6 sm:px-8 md:px-6 lg:px-6 xl:px-6 bg-white max-w-7xl mx-auto">
|
||||
{/* Section Header */}
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<h2 className="text-2xl md:text-3xl font-bold" style={{ color: '#012068' }}>
|
||||
Our Events
|
||||
</h2>
|
||||
<button
|
||||
className="text-sm font-medium hover:opacity-70 transition-opacity duration-200"
|
||||
style={{ color: '#e64838' }}
|
||||
onClick={navigateToAllEvents}
|
||||
>
|
||||
View All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Events Grid - Top Section with Event Details */}
|
||||
{topEvents.length === 0 ? (
|
||||
<div className="text-center py-8 mb-12">
|
||||
<p className="text-gray-500">No upcoming events.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
|
||||
{topEvents.map((event, index) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className={`relative rounded-lg h-48 overflow-hidden cursor-pointer hover:shadow-lg transition-shadow ${
|
||||
index === 2 ? 'lg:hidden xl:block' : ''
|
||||
} ${index === 3 ? 'md:col-span-2 lg:col-span-1' : ''}`}
|
||||
onClick={() => navigateToEventDetail(event.id)}
|
||||
>
|
||||
<img
|
||||
src={event.mainImage}
|
||||
alt={event.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{/* Gradient overlay for better text readability */}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent"></div>
|
||||
|
||||
{/* Event details overlay */}
|
||||
<div className="absolute bottom-0 left-0 right-0 p-4 text-white">
|
||||
<div className="text-xs font-medium mb-1" style={{ color: '#e64838' }}>
|
||||
{event.date}
|
||||
</div>
|
||||
<h4 className="font-medium mb-2 text-sm leading-tight">
|
||||
{event.title}
|
||||
</h4>
|
||||
<div className="text-xs font-medium">
|
||||
{formatPrice(event)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Featured Events Section */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Left - Large Featured Event */}
|
||||
<div className="lg:col-span-2 px-4 py-4 rounded-lg" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
<h3 className="text-xl font-semibold mb-4" style={{ color: '#012068' }}>Upcoming Events</h3>
|
||||
{!featuredEvent ? (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-500">No upcoming events.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="bg-white border border-gray-100 rounded-lg overflow-hidden cursor-pointer hover:shadow-lg transition-shadow"
|
||||
onClick={() => navigateToEventDetail(featuredEvent.id)}
|
||||
>
|
||||
<div className="relative h-48 md:h-64">
|
||||
<img
|
||||
src={featuredEvent.mainImage}
|
||||
alt={featuredEvent.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{/* Pagination dots */}
|
||||
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex space-x-2">
|
||||
<div className="w-2 h-2 bg-white rounded-full shadow"></div>
|
||||
<div className="w-2 h-2 bg-gray-400 rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-gray-400 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 md:p-6">
|
||||
<div className="flex flex-col lg:flex-row lg:justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="text-xs font-medium mb-3" style={{ color: '#e64838' }}>
|
||||
{featuredEvent.date}
|
||||
</div>
|
||||
<h4 className="text-lg md:text-xl font-medium mb-2" style={{ color: '#012068' }}>
|
||||
{featuredEvent.title}
|
||||
</h4>
|
||||
<div className="text-xs leading-relaxed mb-1" style={{ color: '#333' }}>
|
||||
{featuredEvent.description}
|
||||
</div>
|
||||
<div className="text-xs leading-relaxed mb-4" style={{ color: '#333' }}>
|
||||
{featuredEvent.detail}
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
|
||||
<div
|
||||
className="text-xs cursor-pointer hover:underline"
|
||||
style={{ color: '#012068' }}
|
||||
>
|
||||
Share Event
|
||||
</div>
|
||||
<div className="text-xs" style={{ color: '#012068' }}>
|
||||
📍 {featuredEvent.venue?.[0]?.address || 'Convention Center, Medical District'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-left lg:text-right">
|
||||
<button
|
||||
className="w-full lg:w-auto px-6 py-2 text-sm rounded-lg mb-2 transition-colors hover:opacity-90"
|
||||
style={{
|
||||
backgroundColor: '#e64838',
|
||||
color: 'white'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
console.log('Book seat clicked');
|
||||
}}
|
||||
>
|
||||
Book Your Seat
|
||||
</button>
|
||||
<div className="text-sm font-medium" style={{ color: '#e64838' }}>
|
||||
{formatPrice(featuredEvent)}
|
||||
</div>
|
||||
<div className="text-xs mt-1" style={{ color: '#012068', opacity: 0.8 }}>
|
||||
Early bird discount available
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right - Event List with Scroller */}
|
||||
<div className="lg:col-span-1">
|
||||
<h3 className="text-xl font-semibold mb-4" style={{ color: '#012068' }}>Past Events</h3>
|
||||
{pastEvents.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-500">No past events.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-96 overflow-y-auto pr-2 space-y-4 scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100">
|
||||
{pastEvents.map((event) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className="bg-white border border-gray-100 rounded-lg overflow-hidden cursor-pointer hover:shadow-lg transition-shadow flex-shrink-0"
|
||||
onClick={() => navigateToEventDetail(event.id)}
|
||||
>
|
||||
<div className="h-24">
|
||||
<img
|
||||
src={event.mainImage}
|
||||
alt={event.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="text-xs font-medium mb-1" style={{ color: '#e64838' }}>
|
||||
{event.date}
|
||||
</div>
|
||||
<h4 className="font-medium mb-1 text-sm" style={{ color: '#012068' }}>
|
||||
{event.title}
|
||||
</h4>
|
||||
<p className="text-xs leading-relaxed" style={{ color: '#333' }}>
|
||||
{event.description}
|
||||
</p>
|
||||
<div className="mt-2 flex justify-between items-center">
|
||||
<button
|
||||
className="text-xs hover:underline"
|
||||
style={{ color: '#012068' }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigateToEventDetail(event.id);
|
||||
}}
|
||||
>
|
||||
View Details
|
||||
</button>
|
||||
<span className="text-xs font-medium" style={{ color: '#e64838' }}>
|
||||
{formatPrice(event)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventsSection;
|
||||
44
src/components/home/HeroSection.tsx
Normal file
44
src/components/home/HeroSection.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import Image from 'next/image';
|
||||
|
||||
const HeroSection = () => {
|
||||
return (
|
||||
<div className="relative h-80 md:h-96 lg:h-[500px] min-h-80 md:min-h-96 lg:min-h-[450px] overflow-hidden">
|
||||
{/* Background Image using Next.js Image with fill */}
|
||||
<Image
|
||||
src="/images/hero.png"
|
||||
alt="CMC Vellore 125 years celebration"
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="relative z-10 flex items-center justify-center text-left h-full px-4 sm:px-6 md:px-8 lg:px-12 xl:px-16 2xl:px-20 py-8 sm:py-12 md:py-16">
|
||||
<div className="w-full max-w-5xl">
|
||||
{/* Main Heading */}
|
||||
<h1 className="text-white mb-6 sm:mb-8 md:mb-10 text-left">
|
||||
<div className="text-lg sm:text-xl md:text-2xl lg:text-3xl xl:text-4xl 2xl:text-5xl font-bold leading-tight mb-1 sm:mb-2 md:mb-3 lg:mb-4">
|
||||
This year, we celebrate
|
||||
</div>
|
||||
<div className="text-lg sm:text-xl md:text-2xl lg:text-3xl xl:text-4xl 2xl:text-5xl font-bold leading-tight mb-1 sm:mb-2 md:mb-3 lg:mb-4">
|
||||
125 years of CMC Vellore
|
||||
</div>
|
||||
<div className="text-base sm:text-lg md:text-xl lg:text-2xl xl:text-3xl 2xl:text-4xl font-semibold text-gray-200">
|
||||
1900 - 2025
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
{/* CTA Button */}
|
||||
<button
|
||||
className="bg-red-600 hover:bg-red-700 text-white px-6 py-3 sm:px-8 sm:py-4 md:px-10 md:py-4 text-sm sm:text-base md:text-lg font-semibold rounded-sm transition-all duration-300 shadow-lg hover:shadow-xl transform hover:scale-105 focus:outline-none focus:ring-4 focus:ring-red-300"
|
||||
style={{ backgroundColor: '#e64838' }}
|
||||
>
|
||||
Discover
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeroSection;
|
||||
198
src/components/research/ResearchComponent.tsx
Normal file
198
src/components/research/ResearchComponent.tsx
Normal file
@ -0,0 +1,198 @@
|
||||
import { Calendar, Users, BookOpen, MapPin, Award, ChevronRight } from "lucide-react";
|
||||
import Link from 'next/link';
|
||||
|
||||
const ResearchComponent = () => {
|
||||
const ongoingProjects = [
|
||||
{
|
||||
icon: <BookOpen className="w-6 h-6" />,
|
||||
title: "Chest Trauma Outcomes",
|
||||
description: "Comprehensive analysis of mortality rates, ventilation requirements, and rib fixation effectiveness in chest trauma patients."
|
||||
},
|
||||
{
|
||||
icon: <Users className="w-6 h-6" />,
|
||||
title: "Rib Fixation Study",
|
||||
description: "Comparative study analyzing recovery outcomes between operative and non-operative treatment approaches for rib fractures."
|
||||
},
|
||||
{
|
||||
icon: <Calendar className="w-6 h-6" />,
|
||||
title: "Pre-hospital Time Study",
|
||||
description: "Investigating the impact of Golden Hour protocols versus standard transport timelines on patient outcomes."
|
||||
}
|
||||
];
|
||||
|
||||
const milestones = [
|
||||
{
|
||||
year: "2023",
|
||||
event: "ACTraM 2023",
|
||||
description: "Presented 3 comprehensive audits covering surgical intervention times, ICU stay duration, and patient outcome metrics",
|
||||
position: "left"
|
||||
},
|
||||
{
|
||||
year: "2022",
|
||||
event: "Q4 Resource Audit",
|
||||
description: "Completed comprehensive audit on trauma patient overstay patterns and healthcare resource utilization optimization",
|
||||
position: "right"
|
||||
}
|
||||
];
|
||||
|
||||
const collaborators = [
|
||||
{ name: "Royal College of Physicians & Surgeons", location: "Glasgow", short: "RCPS" },
|
||||
{ name: "All India Institute of Medical Sciences", location: "New Delhi", short: "AIIMS" },
|
||||
{ name: "Post Graduate Institute", location: "Chandigarh", short: "PGI" },
|
||||
{ name: "All India Institute of Medical Sciences", location: "Jodhpur", short: "AIIMS" }
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="bg-white min-h-screen">
|
||||
{/* Breadcrumb Section */}
|
||||
<section className="py-4" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<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' }}>
|
||||
Research
|
||||
</span>
|
||||
</nav>
|
||||
|
||||
{/* Page Header */}
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center mb-4">
|
||||
<h1 className="text-3xl font-bold" style={{ color: '#012068' }}>
|
||||
Research
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-base max-w-2xl leading-relaxed">
|
||||
Advancing trauma care through innovative research and evidence-based medical practices
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
|
||||
{/* Ongoing Projects Section */}
|
||||
<div className="mb-20">
|
||||
<h2 className="text-3xl font-semibold mb-12 text-center" style={{color: '#012068'}}>
|
||||
Ongoing Projects
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8" >
|
||||
{ongoingProjects.map((project, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="rounded-lg p-6 hover:shadow-lg transition-shadow duration-300"
|
||||
style={{backgroundColor:'#f4f4f4'}}
|
||||
>
|
||||
<div className="flex items-center mb-4">
|
||||
<div
|
||||
className="p-3 rounded-lg mr-4"
|
||||
style={{color: '#012068'}}
|
||||
>
|
||||
{project.icon}
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold" style={{color: '#012068'}}>
|
||||
{project.title}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
{project.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Past Work Section */}
|
||||
<div className="mb-6 p-8"style={{backgroundColor:'#f4f4f4'}}>
|
||||
<h2 className="text-3xl font-semibold mb-4 text-center" style={{color: '#012068'}}>
|
||||
Past Work
|
||||
</h2>
|
||||
<div className="relative">
|
||||
{/* Timeline line */}
|
||||
<div
|
||||
className="absolute left-1/2 h-full w-0.5"
|
||||
style={{backgroundColor: '#012068'}}
|
||||
></div>
|
||||
|
||||
{milestones.map((milestone, index) => (
|
||||
<div key={index} className={`flex items-center mb-12 ${milestone.position === 'left' ? 'flex-row-reverse' : ''}`}>
|
||||
<div className={`w-1/2 ${milestone.position === 'left' ? 'pr-8 text-right' : 'pl-8'}`}>
|
||||
<div
|
||||
className="p-6 rounded-lg shadow-sm group"
|
||||
style={{backgroundColor: 'white', borderColor: '#f4f4f4'}}
|
||||
>
|
||||
<div className="flex items-center mb-3">
|
||||
<span
|
||||
className="text-2xl font-bold mr-3 group-hover:scale-110 transition-transform duration-300"
|
||||
style={{color: '#e64838'}}
|
||||
>
|
||||
{milestone.year}
|
||||
</span>
|
||||
<Award className="w-5 h-5 group-hover:rotate-12 transition-transform duration-300" style={{color: '#012068'}} />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2 group-hover:text-opacity-80 transition-all duration-300" style={{color: '#012068'}}>
|
||||
{milestone.event}
|
||||
</h3>
|
||||
<p className="text-gray-700 group-hover:text-gray-600 transition-colors duration-300">
|
||||
{milestone.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timeline dot */}
|
||||
<div
|
||||
className="absolute left-1/2 transform -translate-x-1/2 w-4 h-4 rounded-full border-4 border-white shadow-lg hover:scale-125 transition-transform duration-300"
|
||||
style={{backgroundColor: '#e64838'}}
|
||||
></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Collaborators Section */}
|
||||
<div>
|
||||
<h2 className="text-3xl font-semibold mb-4 text-center" style={{color: '#012068'}}>
|
||||
Collaborators
|
||||
</h2>
|
||||
<p className="text-center text-gray-600 mb-12">
|
||||
Our research partnerships span leading medical institutions worldwide
|
||||
</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{collaborators.map((collaborator, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="text-center p-6 rounded-lg border hover:shadow-md transition-shadow duration-300"
|
||||
style={{backgroundColor: '#f4f4f4', borderColor: '#f4f4f4'}}
|
||||
>
|
||||
<div
|
||||
className="w-20 h-20 mx-auto mb-4 rounded-full flex items-center justify-center text-2xl font-bold"
|
||||
style={{backgroundColor: '#012068', color: 'white'}}
|
||||
>
|
||||
{collaborator.short.substring(0, 2)}
|
||||
</div>
|
||||
<h3 className="font-semibold mb-2" style={{color: '#012068'}}>
|
||||
{collaborator.short}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 mb-1">
|
||||
{collaborator.name}
|
||||
</p>
|
||||
<div className="flex items-center justify-center text-sm text-gray-500">
|
||||
<MapPin className="w-4 h-4 mr-1" />
|
||||
{collaborator.location}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResearchComponent;
|
||||
Reference in New Issue
Block a user