first commit

This commit is contained in:
2025-10-09 20:05:39 +05:30
commit d4fcb658e3
69 changed files with 13582 additions and 0 deletions

41
.gitignore vendored Normal file
View File

@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

37
DockerFile Normal file
View File

@ -0,0 +1,37 @@
# Use Node.js LTS Alpine for smaller image
FROM node:20-alpine AS base
# Set working directory
WORKDIR /app
# Install dependencies only when needed
FROM base AS deps
COPY package.json package-lock.json* ./
RUN npm install --frozen-lockfile || npm install
# Build the app
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Production image, copy only necessary files
FROM base AS runner
ENV NODE_ENV=production
# Create non-root user
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
WORKDIR /app
# Copy build output and node_modules
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
USER nextjs
EXPOSE 3000
CMD ["npm", "start"]

36
README.md Normal file
View File

@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

14
docker-compose.yml Normal file
View File

@ -0,0 +1,14 @@
version: "3.9"
services:
nextjs:
build: .
container_name: cmc_nextjs_pro
ports:
- "9012:3000"
environment:
- NODE_ENV=production
restart: unless-stopped
volumes:
- .:/app
- /app/node_modules

25
eslint.config.mjs Normal file
View File

@ -0,0 +1,25 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
{
ignores: [
"node_modules/**",
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
],
},
];
export default eslintConfig;

29
next.config.ts Normal file
View File

@ -0,0 +1,29 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com',
port: '',
pathname: '/**',
},
{
protocol: 'http',
hostname: 'localhost',
port: '8080',
pathname: '/api/files/images/**',
},
{
protocol: 'https',
hostname: 'yourproductiondomain.com', // Replace with your production domain
pathname: '/api/files/images/**',
}
],
domains: ['localhost'],
},
};
export default nextConfig;

6149
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "cmc_nextjs_pro",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build --turbopack",
"start": "next start",
"lint": "eslint"
},
"dependencies": {
"lucide-react": "^0.544.0",
"next": "15.5.3",
"react": "19.1.0",
"react-dom": "19.1.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.5.3",
"tailwindcss": "^4",
"typescript": "^5"
}
}

5
postcss.config.mjs Normal file
View File

@ -0,0 +1,5 @@
const config = {
plugins: ["@tailwindcss/postcss"],
};
export default config;

1
public/file.svg Normal file
View File

@ -0,0 +1 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 391 B

1
public/globe.svg Normal file
View File

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
public/images/Meta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
public/images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/images/footer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/images/hero.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
public/images/logo-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
public/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

1
public/next.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
public/vercel.svg Normal file
View File

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 128 B

1
public/window.svg Normal file
View File

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

After

Width:  |  Height:  |  Size: 385 B

33
src/app/about/page.tsx Normal file
View File

@ -0,0 +1,33 @@
import Header from "../../components/Layouts/Header"; // Adjust path based on your project structure
import { Footer } from "../../components/Layouts/Footer"
import Process from "../../components/about/process";
import Breadcrumb from "../../components/about/AboutBreadcrumb";
import Introduction from "../../components/about/Introduction";
import MissionVision from "../../components/about/MissionVision";
import Services from "../../components/about/Services";
import StatisticsTiles from "../../components/about/StatisticsTiles";
import PatientCareCards from "../../components/about/PatientCareCards";
export default function Home() {
const breadcrumbItems = [
{ label: "Home", href: "/" },
{ label: "About Us", isActive: true }
];
return (
<>
<Header />
<Breadcrumb
items={breadcrumbItems}
title="About Us"
description="Dedicated to excellence in trauma care at CMC Hospital, Ranipet campus—saving lives with compassion and expertise."
/>
<Introduction />
<MissionVision />
<Process />
<Services />
<StatisticsTiles />
<PatientCareCards />
<Footer />
</>
);
}

View File

@ -0,0 +1,14 @@
import Header from "../../../components/Layouts/Header"; // Adjust path based on your project structure
import { Footer } from "../../../components/Layouts/Footer"
import BlogDetail from "../../../components/blogs/BlogDetail";
export default function contact() {
return (
<>
<Header />
<BlogDetail/>
<Footer />
</>
);
}

14
src/app/blogs/page.tsx Normal file
View File

@ -0,0 +1,14 @@
import Header from "../../components/Layouts/Header"; // Adjust path based on your project structure
import { Footer } from "../../components/Layouts/Footer"
import BlogListing from '../../components/blogs/BlogListing';
export default function contact() {
return (
<>
<Header />
<BlogListing/>
<Footer />
</>
);
}

14
src/app/career/page.tsx Normal file
View File

@ -0,0 +1,14 @@
import Header from "../../components/Layouts/Header"; // Adjust path based on your project structure
import { Footer } from "../../components/Layouts/Footer"
import CareersComponent from "@/components/career/careerscomponent";
export default function contact() {
return (
<>
<Header />
<CareersComponent/>
<Footer />
</>
);
}

13
src/app/contact/page.tsx Normal file
View File

@ -0,0 +1,13 @@
import Header from "../../components/Layouts/Header"; // Adjust path based on your project structure
import { Footer } from "../../components/Layouts/Footer"
import ContactPage from '../../components/contact-us/ContactForm';
export default function contact() {
return (
<>
<Header />
<ContactPage />
<Footer />
</>
);
}

View File

@ -0,0 +1,14 @@
import Header from "../../../components/Layouts/Header"; // Adjust path based on your project structure
import { Footer } from "../../../components/Layouts/Footer"
import CourseDetail from "../../../components/education/CourseDetail";
export default function contact() {
return (
<>
<Header />
<CourseDetail/>
<Footer />
</>
);
}

View File

@ -0,0 +1,14 @@
import Header from "../../components/Layouts/Header"; // Adjust path based on your project structure
import { Footer } from "../../components/Layouts/Footer"
import EducationTraining from "../../components/education/EducationTraining";
export default function contact() {
return (
<>
<Header />
<EducationTraining/>
<Footer />
</>
);
}

View File

@ -0,0 +1,13 @@
import Header from "../../../components/Layouts/Header"; // Adjust path based on your project structure
import { Footer } from "../../../components/Layouts/Footer";
import EventDetail from "../../../components/events/EventDetail";
export default function faculty() {
return (
<>
<Header />
<EventDetail/>
<Footer />
</>
);
}

13
src/app/events/page.tsx Normal file
View File

@ -0,0 +1,13 @@
import Header from "../../components/Layouts/Header"; // Adjust path based on your project structure
import { Footer } from "../../components/Layouts/Footer";
import MedicalEventsComponent from "../../components/events/MedicalEventsComponent";
export default function faculty() {
return (
<>
<Header />
<MedicalEventsComponent/>
<Footer />
</>
);
}

View File

@ -0,0 +1,88 @@
// app/faculty/[id]/page.tsx
import Header from "../../../components/Layouts/Header";
import { Footer } from "../../../components/Layouts/Footer";
import TeamMemberDetail from "../../../components/faculty/TeamMemberDetail";
import { notFound } from 'next/navigation';
import { FacultyService } from '../../../lib/facultyData';
interface FacultyPageProps {
params: {
id: string;
};
}
export default async function FacultyPage({ params }: FacultyPageProps) {
const memberId = parseInt(params.id);
// Check if ID is valid number
if (isNaN(memberId)) {
notFound();
}
// Fetch member data from API
const memberData = await FacultyService.getFacultyById(memberId);
// If member not found, show 404
if (!memberData) {
notFound();
}
return (
<>
<Header />
<TeamMemberDetail memberId={memberId} memberData={memberData} />
<Footer />
</>
);
}
// Generate static params for all team members
export async function generateStaticParams() {
try {
const teamMembers = await FacultyService.getAllFaculty();
return teamMembers.map((member) => ({
id: member.id.toString(),
}));
} catch (error) {
console.error('Error generating static params:', error);
// Return empty array if data fetching fails during build
return [];
}
}
// Generate metadata for each team member page
export async function generateMetadata({ params }: FacultyPageProps) {
const memberId = parseInt(params.id);
if (isNaN(memberId)) {
return {
title: 'Faculty Member Not Found - CMC Vellore'
};
}
try {
const memberData = await FacultyService.getFacultyById(memberId);
if (!memberData) {
return {
title: 'Faculty Member Not Found - CMC Vellore'
};
}
return {
title: `${memberData.name} - ${memberData.designation} | CMC Vellore`,
description: memberData.description,
openGraph: {
title: `${memberData.name} - ${memberData.designation}`,
description: memberData.description,
images: [memberData.image],
},
};
} catch (error) {
console.error('Error generating metadata:', error);
return {
title: 'Faculty Member - CMC Vellore'
};
}
}

BIN
src/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

26
src/app/globals.css Normal file
View File

@ -0,0 +1,26 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}

34
src/app/layout.tsx Normal file
View File

@ -0,0 +1,34 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "CMC - Department of Trauma Surgery",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}

15
src/app/page.tsx Normal file
View File

@ -0,0 +1,15 @@
import Header from "../components/Layouts/Header"; // Adjust path based on your project structure
import { Footer } from "../components/Layouts/Footer";
import HeroSection from "../components/home/HeroSection";
import EventsSection from "../components/home/EventSection";
export default function faculty() {
return (
<div className="bg-white">
<Header />
<HeroSection/>
<EventsSection/>
<Footer />
</div>
);
}

14
src/app/research/page.tsx Normal file
View File

@ -0,0 +1,14 @@
import Header from "../../components/Layouts/Header"; // Adjust path based on your project structure
import { Footer } from "../../components/Layouts/Footer"
import ResearchComponent from "../../components/research/ResearchComponent";
export default function contact() {
return (
<>
<Header />
<ResearchComponent/>
<Footer />
</>
);
}

View File

@ -0,0 +1,13 @@
import Header from "../../components/Layouts/Header"; // Adjust path based on your project structure
import { Footer } from "../../components/Layouts/Footer";
import TeamListing from "../../components/faculty/TeamListing";
export default function faculty() {
return (
<>
<Header />
<TeamListing/>
<Footer />
</>
);
}

View 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>
);
}

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

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

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

View 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 Indiacombining 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;

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

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

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

View 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>
);
}

View 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&apos;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;

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

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

View 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&apos;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;

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

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

View 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&apos;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;

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

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

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

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

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

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

162
src/lib/api.ts Normal file
View File

@ -0,0 +1,162 @@
// lib/api.ts - API service for Next.js
export interface Event {
id: number;
code: string;
year: string;
subject: string;
title: string;
subTitle?: string;
description: string;
detail: string;
date: string;
mainImage?: string;
galleryImages?: string[];
venue?: Venue[];
highlights?: string[];
organisers?: string[];
fee?: Fee[]; // Updated to match backend structure
phone: string;
email: string;
isActive: boolean;
professors?: Professor[];
}
interface Venue {
title: string;
date: string;
address: string;
info: string;
}
interface Fee {
description: string; // Updated from 'desc' to 'description'
cost: number;
}
interface Professor {
id: number;
name: string;
}
class EventAPI {
private baseUrl: string;
constructor() {
// Use environment variable for API URL
this.baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080';
}
// File upload method
async uploadImage(file: File): Promise<{url: string, filename: string}> {
try {
const formData = new FormData();
formData.append('file', file);
const response = await fetch(`${this.baseUrl}/api/files/upload`, {
method: 'POST',
body: formData, // Don't set Content-Type header for FormData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error uploading image:', error);
throw error;
}
}
// Upload multiple images
async uploadMultipleImages(files: File[]): Promise<{url: string, filename: string}[]> {
try {
const uploadPromises = files.map(file => this.uploadImage(file));
return await Promise.all(uploadPromises);
} catch (error) {
console.error('Error uploading multiple images:', error);
throw error;
}
}
async getAllEvents(): Promise<Event[]> {
try {
const response = await fetch(`${this.baseUrl}/api/events`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error fetching events:', error);
throw error;
}
}
async getEventById(id: number): Promise<Event> {
try {
const response = await fetch(`${this.baseUrl}/api/events/${id}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error fetching event:', error);
throw error;
}
}
async getUpcomingEvents(): Promise<Event[]> {
try {
const events = await this.getAllEvents();
const currentDate = new Date();
return events
.filter(event => {
if (!event.isActive) return false;
// Parse the date string (assuming format like "28 September 2025")
const eventDate = new Date(event.date);
return eventDate >= currentDate;
})
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
} catch (error) {
console.error('Error fetching upcoming events:', error);
throw error;
}
}
async getPastEvents(): Promise<Event[]> {
try {
const events = await this.getAllEvents();
const currentDate = new Date();
return events
.filter(event => {
if (!event.isActive) return false;
const eventDate = new Date(event.date);
return eventDate < currentDate;
})
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
} catch (error) {
console.error('Error fetching past events:', error);
throw error;
}
}
}
export const eventAPI = new EventAPI();

237
src/lib/facultyData.ts Normal file
View File

@ -0,0 +1,237 @@
// lib/facultyData.ts
export interface TeamMember {
id: number;
professorId: string;
name: string;
firstName: string;
lastName: string;
position: string;
designation: string;
image: string;
profileUrl?: string;
phone: string;
email: string;
experience: string;
description: string;
specialty: string;
certification: string;
training: string;
workDays: string[];
skills: Skill[];
awards: Award[];
status?: string;
department?: string;
officeLocation?: string;
joinDate?: string;
category: string;
}
export interface Skill {
name: string;
level?: number;
}
export interface Award {
title: string;
year: string;
description: string;
image: string;
}
export interface FacultyApiResponse {
content: TeamMember[];
last: boolean;
first: boolean;
totalElements: number;
size: number;
numberOfElements: number;
number: number;
empty: boolean;
}
// API service class
export class FacultyService {
private static baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080';
static async getAllFaculty(): Promise<TeamMember[]> {
try {
const response = await fetch(`${this.baseUrl}/professor?size=100`, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: FacultyApiResponse = await response.json();
return this.transformProfessorsToTeamMembers(data.content);
} catch (error) {
console.error('Error fetching faculty data:', error);
return this.getFallbackData();
}
}
static async getFacultyById(id: number): Promise<TeamMember | null> {
try {
// Find by array index from getAllFaculty for now
// In production, you might want a direct API call
const allFaculty = await this.getAllFaculty();
return allFaculty.find(member => member.id === id) || null;
} catch (error) {
console.error('Error fetching faculty member:', error);
return null;
}
}
static async getFacultyByProfessorId(professorId: string): Promise<TeamMember | null> {
try {
const response = await fetch(`${this.baseUrl}/professor/${professorId}`, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const professor = await response.json();
const transformed = this.transformProfessorsToTeamMembers([professor]);
return transformed[0] || null;
} catch (error) {
console.error('Error fetching professor by ID:', error);
return null;
}
}
static async getFacultyByCategory(category: string): Promise<TeamMember[]> {
try {
const allFaculty = await this.getAllFaculty();
return allFaculty.filter(member => member.category === category);
} catch (error) {
console.error('Error fetching faculty by category:', error);
return [];
}
}
// Helper method to check if an image URL is accessible
static async checkImageExists(imageUrl: string): Promise<boolean> {
try {
const response = await fetch(imageUrl, { method: 'HEAD' });
return response.ok;
} catch (error) {
return false;
}
}
// Helper method to get a working image URL with fallback
static async getWorkingImageUrl(originalUrl: string, fallbackUrl: string = '/images/default-avatar.jpg'): Promise<string> {
const isAccessible = await this.checkImageExists(originalUrl);
return isAccessible ? originalUrl : fallbackUrl;
}
private static transformProfessorsToTeamMembers(professors: any[]): TeamMember[] {
return professors.map((prof, index) => {
// Create proper image URL - remove /api from baseUrl for images
const imageBaseUrl = this.baseUrl;
let imageUrl = '/images/default-avatar.jpg'; // Default fallback
if (prof.profileImageUrl) {
// If it's already a full URL, use it as is
if (prof.profileImageUrl.startsWith('http')) {
imageUrl = prof.profileImageUrl;
} else {
// If it's a relative URL, construct the full URL
imageUrl = `${imageBaseUrl}${prof.profileImageUrl}`;
}
}
return {
id: index + 1, // Generate sequential ID for UI
professorId: prof.professorId,
name: prof.name || `${prof.firstName} ${prof.lastName}`,
firstName: prof.firstName,
lastName: prof.lastName,
position: prof.position || 'Faculty Member',
designation: prof.designation || prof.position || 'Faculty Member',
image: imageUrl,
profileUrl: `/faculty/${index + 1}`,
phone: prof.phone || 'Not available',
email: prof.email,
experience: prof.experience || 'Not specified',
description: prof.description || `${prof.firstName} ${prof.lastName} is a dedicated member of our faculty.`,
specialty: prof.specialty || prof.department || 'General Medicine',
certification: prof.certification || 'Medical certification details not available',
training: prof.training || 'Professional training details not available',
workDays: prof.workDays || ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
skills: prof.skills?.map((skill: any) => ({
name: skill.name,
level: skill.level
})) || [
{ name: 'Clinical Practice', level: 90 },
{ name: 'Research', level: 85 },
{ name: 'Teaching', level: 88 }
],
awards: prof.awards?.map((award: any) => ({
title: award.title,
year: award.year,
description: award.description,
image: award.imageUrl || '/images/award-icon.png'
})) || [
{
title: 'Excellence in Medical Practice',
year: new Date().getFullYear().toString(),
description: 'Recognized for outstanding contribution to medical practice and patient care.',
image: '/images/award-icon.png'
}
],
status: prof.status,
department: prof.department,
officeLocation: prof.officeLocation,
joinDate: prof.joinDate,
category: prof.category || 'FACULTY'
};
});
}
private static getFallbackData(): TeamMember[] {
return [
{
id: 1,
professorId: 'fallback-1',
name: "Loading Faculty Data...",
firstName: "Loading",
lastName: "Data",
position: "Please wait while we fetch the latest faculty information",
designation: "System Message",
image: "/images/default-avatar.jpg",
phone: "Not available",
email: "support@institution.edu",
experience: "N/A",
description: "Faculty data is currently being loaded from the server.",
specialty: "N/A",
certification: "N/A",
training: "N/A",
workDays: [],
skills: [],
awards: [],
category: 'FACULTY'
}
];
}
}
// Helper function to get team members (for backward compatibility)
export const getTeamMembers = async (): Promise<TeamMember[]> => {
return await FacultyService.getAllFaculty();
};
// Helper function to get team member by ID
export const getTeamMemberById = async (id: number): Promise<TeamMember | null> => {
return await FacultyService.getFacultyById(id);
};

256
src/services/blogService.ts Normal file
View File

@ -0,0 +1,256 @@
// services/blogService.ts
export interface Professor {
id: number;
firstName?: string;
name?: string; // Fallback for different naming
}
export interface ApiBlog {
id: number;
title: string;
content: string;
professors: Professor[];
tags: string[];
posted: boolean; // From your Angular form
author?: string;
createdDate?: string;
updatedDate?: string;
publishDate?: string;
imageUrl?: string; // Added for uploaded images
}
export interface Blog {
id: number;
title: string;
excerpt: string;
tags: string[];
image: string;
publishDate: string;
readTime: string;
content?: string;
professors?: Professor[];
}
class BlogService {
private apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080';
async getAllBlogs(): Promise<Blog[]> {
try {
const response = await fetch(`${this.apiBaseUrl}/api/posts`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const apiBlogs: ApiBlog[] = await response.json();
return this.transformApiBlogsToBlogs(apiBlogs);
} catch (error) {
console.error('Error fetching blogs:', error);
return this.getFallbackBlogs(); // Return fallback data if API fails
}
}
async getBlogById(id: number): Promise<Blog | null> {
try {
const response = await fetch(`${this.apiBaseUrl}/api/posts/${id}`);
if (!response.ok) {
if (response.status === 404) {
return null;
}
throw new Error(`HTTP error! status: ${response.status}`);
}
const apiBlog: ApiBlog = await response.json();
return this.transformApiBlogToBlog(apiBlog);
} catch (error) {
console.error(`Error fetching blog ${id}:`, error);
return null;
}
}
// Get only posted blogs for public display
async getPostedBlogs(): Promise<Blog[]> {
try {
const response = await fetch(`${this.apiBaseUrl}/api/posts/posted`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const apiBlogs: ApiBlog[] = await response.json();
return this.transformApiBlogsToBlogs(apiBlogs);
} catch (error) {
console.error('Error fetching posted blogs:', error);
return this.getFallbackBlogs();
}
}
// Get blogs by tag
async getBlogsByTag(tag: string): Promise<Blog[]> {
try {
const response = await fetch(`${this.apiBaseUrl}/api/posts/tag/${encodeURIComponent(tag)}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const apiBlogs: ApiBlog[] = await response.json();
return this.transformApiBlogsToBlogs(apiBlogs);
} catch (error) {
console.error(`Error fetching blogs by tag ${tag}:`, error);
return [];
}
}
// Get tag counts
async getTagsWithCount(): Promise<{ [key: string]: number }> {
try {
const response = await fetch(`${this.apiBaseUrl}/api/posts/tags/count`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const tagCounts: { [key: string]: number } = await response.json();
return tagCounts;
} catch (error) {
console.error('Error fetching tag counts:', error);
return {};
}
}
private transformApiBlogsToBlogs(apiBlogs: ApiBlog[]): Blog[] {
return apiBlogs.map(apiBlog => this.transformApiBlogToBlog(apiBlog));
}
private transformApiBlogToBlog(apiBlog: ApiBlog): Blog {
return {
id: apiBlog.id,
title: apiBlog.title,
excerpt: this.generateExcerpt(apiBlog.content),
tags: apiBlog.tags || [],
image: this.getImageUrl(apiBlog.imageUrl), // Use uploaded image or default
publishDate: this.formatDate(apiBlog.publishDate || apiBlog.createdDate || new Date().toISOString()),
readTime: this.calculateReadTime(apiBlog.content),
content: apiBlog.content,
professors: apiBlog.professors
};
}
private generateExcerpt(content: string, maxLength: number = 150): string {
if (!content) return 'No content available.';
// Strip HTML tags and get plain text
const plainText = content.replace(/<[^>]*>/g, '');
if (plainText.length <= maxLength) {
return plainText;
}
// Find the last complete word within the limit
const truncated = plainText.substr(0, maxLength);
const lastSpaceIndex = truncated.lastIndexOf(' ');
if (lastSpaceIndex > 0) {
return truncated.substr(0, lastSpaceIndex) + '...';
}
return truncated + '...';
}
private calculateReadTime(content: string): string {
if (!content) return '1 min read';
// Strip HTML and count words
const plainText = content.replace(/<[^>]*>/g, '');
const wordCount = plainText.split(/\s+/).filter(word => word.length > 0).length;
// Average reading speed is 200-250 words per minute
const readingSpeed = 225;
const minutes = Math.ceil(wordCount / readingSpeed);
return `${minutes} min read`;
}
private formatDate(dateString: string): string {
try {
const date = new Date(dateString);
return date.toISOString().split('T')[0]; // Returns YYYY-MM-DD format
} catch (error) {
return new Date().toISOString().split('T')[0]; // Fallback to current date
}
}
private getImageUrl(imageUrl?: string): string {
if (imageUrl) {
// If the imageUrl is a full URL, return as is
if (imageUrl.startsWith('http')) {
return imageUrl;
}
// If it's a relative path from your backend, construct full URL
return `${this.apiBaseUrl}${imageUrl}`;
}
// Return default image when no image is uploaded
return this.getDefaultImage();
}
private getDefaultImage(): string {
// Return a single default image from your public folder
// Make sure to add this image to your Next.js public/images directory
return '/images/default-blog-image.jpg';
}
private getFallbackBlogs(): Blog[] {
// Return the original hardcoded blogs as fallback when API is unavailable
return [
{
id: 1,
title: "Understanding PTSD: Signs, Symptoms, and Treatment Options",
excerpt: "Post-traumatic stress disorder affects millions worldwide. Learn about the key symptoms, triggers, and evidence-based treatment approaches that can help individuals reclaim their lives.",
tags: ["PTSD", "Mental Health", "Treatment"],
image: "/images/default-blog-image.jpg", // Use default image for fallback
publishDate: "2024-01-15",
readTime: "8 min read"
},
{
id: 2,
title: "Building Resilience After Childhood Trauma",
excerpt: "Discover practical strategies and therapeutic approaches for healing from childhood trauma. Explore how resilience can be developed and nurtured throughout the recovery journey.",
tags: ["Childhood Trauma", "Resilience", "Healing"],
image: "/images/default-blog-image.jpg",
publishDate: "2024-01-12",
readTime: "6 min read"
},
{
id: 3,
title: "The Role of Family Support in Trauma Recovery",
excerpt: "Family support plays a crucial role in trauma recovery. Learn how loved ones can provide effective support and create a healing environment for trauma survivors.",
tags: ["Family Support", "Recovery", "Relationships"],
image: "/images/default-blog-image.jpg",
publishDate: "2024-01-10",
readTime: "5 min read"
},
{
id: 4,
title: "Trauma-Informed Care: A Comprehensive Approach",
excerpt: "Explore the principles of trauma-informed care and how healthcare providers can create safe, supportive environments for trauma survivors seeking treatment.",
tags: ["Trauma-Informed Care", "Healthcare", "Best Practices"],
image: "/images/default-blog-image.jpg",
publishDate: "2024-01-08",
readTime: "7 min read"
},
{
id: 5,
title: "Mindfulness and Meditation for Trauma Healing",
excerpt: "Discover how mindfulness practices and meditation techniques can be powerful tools in trauma recovery, helping to regulate emotions and reduce anxiety.",
tags: ["Mindfulness", "Meditation", "Coping Strategies"],
image: "/images/default-blog-image.jpg",
publishDate: "2024-01-05",
readTime: "6 min read"
},
{
id: 6,
title: "Workplace Trauma: Recognition and Response",
excerpt: "Understanding workplace trauma and its impact on employees. Learn about creating supportive work environments and implementing effective response strategies.",
tags: ["Workplace Trauma", "Employee Support", "Mental Health"],
image: "/images/default-blog-image.jpg",
publishDate: "2024-01-03",
readTime: "9 min read"
}
];
}
}
export const blogService = new BlogService();

View File

@ -0,0 +1,206 @@
// services/careerService.ts
export interface ApiJob {
id: number;
title: string;
department: string;
location: string;
type: string;
experience: string;
salary: string;
description: string;
requirements: string[];
responsibilities: string[];
isActive: boolean;
createdDate?: string;
updatedDate?: string;
}
export interface Job {
id: string;
title: string;
department: string;
location: string;
type: string;
experience: string;
salary: string;
description: string;
requirements: string[];
responsibilities: string[];
}
export interface JobApplicationData {
jobId: number;
fullName: string;
email: string;
phone: string;
experience: string;
coverLetter?: string;
resumeUrl?: string;
}
class CareerService {
private apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080';
async getActiveJobs(): Promise<Job[]> {
try {
const response = await fetch(`${this.apiBaseUrl}/api/jobs/active`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const apiJobs: ApiJob[] = await response.json();
return this.transformApiJobsToJobs(apiJobs);
} catch (error) {
console.error('Error fetching jobs:', error);
return this.getFallbackJobs(); // Return fallback data if API fails
}
}
async getJobById(id: number): Promise<Job | null> {
try {
const response = await fetch(`${this.apiBaseUrl}/api/jobs/${id}`);
if (!response.ok) {
if (response.status === 404) {
return null;
}
throw new Error(`HTTP error! status: ${response.status}`);
}
const apiJob: ApiJob = await response.json();
return this.transformApiJobToJob(apiJob);
} catch (error) {
console.error(`Error fetching job ${id}:`, error);
return null;
}
}
async submitApplication(applicationData: JobApplicationData): Promise<boolean> {
try {
const response = await fetch(`${this.apiBaseUrl}/api/job-applications`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(applicationData),
});
return response.ok;
} catch (error) {
console.error('Error submitting application:', error);
return false;
}
}
private transformApiJobsToJobs(apiJobs: ApiJob[]): Job[] {
return apiJobs.map(apiJob => this.transformApiJobToJob(apiJob));
}
private transformApiJobToJob(apiJob: ApiJob): Job {
return {
id: apiJob.id.toString(),
title: apiJob.title,
department: apiJob.department,
location: apiJob.location,
type: apiJob.type,
experience: apiJob.experience,
salary: apiJob.salary,
description: apiJob.description,
requirements: apiJob.requirements || [],
responsibilities: apiJob.responsibilities || []
};
}
private getFallbackJobs(): Job[] {
// Return the original hardcoded jobs as fallback
return [
{
id: 'trauma-registrar',
title: 'Trauma Registrar',
department: 'Trauma Surgery',
location: 'Vellore, India',
type: 'Rotational',
experience: 'MBBS + MS preferred',
salary: 'As per hospital norms',
description: 'Join our trauma surgery department as a registrar. Open year-round position with rotational duties in emergency trauma care.',
requirements: [
'MBBS degree required',
'MS qualification preferred',
'Experience in emergency medicine',
'Ability to work in high-pressure environments'
],
responsibilities: [
'Manage trauma cases in emergency situations',
'Assist in trauma surgeries',
'Participate in rotational duties',
'Maintain patient records and documentation'
]
},
{
id: 'trauma-nurse-coordinator',
title: 'Trauma Nurse Coordinator',
department: 'Trauma Surgery',
location: 'Vellore, India',
type: 'Full-time',
experience: 'BSc Nursing + ATCN or 2 years ICU',
salary: 'As per hospital norms',
description: 'Coordinate trauma nursing activities and ensure quality patient care in our trauma unit.',
requirements: [
'BSc Nursing degree required',
'ATCN certification OR 2 years ICU experience',
'Strong coordination and leadership skills',
'Knowledge of trauma protocols'
],
responsibilities: [
'Coordinate trauma nursing activities',
'Ensure quality patient care standards',
'Train and supervise nursing staff',
'Maintain trauma unit protocols'
]
},
{
id: 'clinical-research-fellow',
title: 'Clinical Research Fellow',
department: 'Trauma Research',
location: 'Vellore, India',
type: 'Contract (1 year)',
experience: 'Research background preferred',
salary: 'Fellowship stipend',
description: '1-year contract position for trauma study work. Ideal for those interested in clinical research in trauma medicine.',
requirements: [
'Medical degree or related field',
'Interest in clinical research',
'Data analysis skills',
'Good written and verbal communication'
],
responsibilities: [
'Conduct trauma-related clinical studies',
'Collect and analyze research data',
'Prepare research reports and publications',
'Collaborate with research team'
]
},
{
id: 'short-term-observer',
title: 'Short-Term Observer',
department: 'Trauma Surgery',
location: 'Vellore, India',
type: 'Observership (4-8 weeks)',
experience: 'Junior residents',
salary: 'Observership program',
description: '4-8 week observership slots for junior residents. Apply 2 months ahead for this learning opportunity.',
requirements: [
'Junior resident status',
'Interest in trauma surgery',
'Apply 2 months in advance',
'Valid medical credentials'
],
responsibilities: [
'Observe trauma surgery procedures',
'Learn trauma management protocols',
'Attend clinical rounds and discussions',
'Prepare observership reports'
]
}
];
}
}
export const careerService = new CareerService();

View File

@ -0,0 +1,254 @@
// services/educationService.ts
export interface ApiCourse {
id: number;
title: string;
description: string;
duration: string;
seats: number;
category: string;
level: string;
instructor: string;
price?: string;
startDate?: string;
imageUrl?: string;
eligibility: string[];
objectives: string[];
isActive: boolean;
createdDate?: string;
updatedDate?: string;
}
export interface Course {
id: string;
title: string;
description: string;
duration: string;
seats: number;
category: string;
level: string;
instructor: string;
price: string;
startDate: string;
image: string;
eligibility: string[];
objectives: string[];
}
export interface CourseApplicationData {
courseId: number;
fullName: string;
email: string;
phone: string;
qualification: string;
experience?: string;
coverLetter?: string;
resumeUrl?: string;
}
class EducationService {
private apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080';
async getActiveCourses(): Promise<Course[]> {
try {
const response = await fetch(`${this.apiBaseUrl}/api/courses/active`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const apiCourses: ApiCourse[] = await response.json();
return this.transformApiCoursesToCourses(apiCourses);
} catch (error) {
console.error('Error fetching courses:', error);
return this.getFallbackCourses(); // Return fallback data if API fails
}
}
async getCourseById(id: number): Promise<Course | null> {
try {
const token = localStorage.getItem('authToken'); // Or from cookies/session
const response = await fetch(`${this.apiBaseUrl}/api/courses/${id}`, {
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
});
if (!response.ok) {
if (response.status === 404) return null;
throw new Error(`HTTP error! status: ${response.status}`);
}
const apiCourse: ApiCourse = await response.json();
return this.transformApiCourseToCourse(apiCourse);
} catch (error) {
console.error(`Error fetching course ${id}:`, error);
return null;
}
}
async submitApplication(applicationData: CourseApplicationData): Promise<boolean> {
try {
const response = await fetch(`${this.apiBaseUrl}/api/course-applications`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(applicationData),
});
return response.ok;
} catch (error) {
console.error('Error submitting application:', error);
return false;
}
}
private transformApiCoursesToCourses(apiCourses: ApiCourse[]): Course[] {
return apiCourses.map(apiCourse => this.transformApiCourseToCourse(apiCourse));
}
private transformApiCourseToCourse(apiCourse: ApiCourse): Course {
return {
id: apiCourse.id.toString(),
title: apiCourse.title,
description: apiCourse.description,
duration: apiCourse.duration,
seats: apiCourse.seats,
category: apiCourse.category,
level: apiCourse.level,
instructor: apiCourse.instructor,
price: apiCourse.price || 'N/A',
startDate: apiCourse.startDate || '',
image: this.getImageUrl(apiCourse.imageUrl),
eligibility: apiCourse.eligibility || [],
objectives: apiCourse.objectives || []
};
}
private getImageUrl(imageUrl?: string): string {
if (imageUrl) {
// If imageUrl starts with /uploads/, prepend the full API path
if (imageUrl.startsWith('/uploads/')) {
return `${this.apiBaseUrl}/api/files${imageUrl}`; // This adds /api/files before /uploads/
}
// If it's already a full URL, return as is
if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
return imageUrl;
}
// Otherwise, assume it's a relative path and prepend base URL with API path
return `${this.apiBaseUrl}/api/files/${imageUrl}`;
}
// Return random default image if no imageUrl provided
return this.getRandomDefaultImage();
}
private getRandomDefaultImage(): string {
const defaultImages = [
"https://images.unsplash.com/photo-1576091160550-2173dba999ef?w=400&h=200&fit=crop&crop=center",
"https://images.unsplash.com/photo-1559757175-0eb30cd8c063?w=400&h=300&fit=crop&crop=center",
"https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=400&h=300&fit=crop&crop=center",
"https://images.unsplash.com/photo-1582750433449-648ed127bb54?w=400&h=300&fit=crop&crop=center",
"https://images.unsplash.com/photo-1551601651-2a8555f1a136?w=400&h=300&fit=crop&crop=center",
"https://images.unsplash.com/photo-1559757148-5c350d0d3c56?w=400&h=300&fit=crop&crop=center"
];
return defaultImages[Math.floor(Math.random() * defaultImages.length)];
}
private getFallbackCourses(): Course[] {
// Return the original hardcoded courses as fallback
return [
{
id: '1',
title: "ATLS® (Advanced Trauma Life Support)",
description: "Eligibility: MBBS + internship complete. Last Course: Aug 31 Sep 2, 2023 (60 doctors certified). Next Schedule: [#Incomplete Date TBD]",
duration: "3 Days",
seats: 60,
category: "Certification",
level: "Professional",
image: "https://images.unsplash.com/photo-1576091160550-2173dba999ef?w=400&h=200&fit=crop&crop=center",
instructor: "Trauma Faculty Team",
price: "N/A",
startDate: "2023-08-31",
eligibility: ["MBBS + internship complete"],
objectives: ["Advanced trauma life support skills", "Emergency trauma management"]
},
{
id: '2',
title: "ATCN® (Advanced Trauma Care for Nurses)",
description: "First Course: Apr 11-13, 2024 (manikin-based training). Participants: 40 critical care nurses from CMC and partner hospitals. Next Batch: [#Incomplete Date TBD]",
duration: "3 Days",
seats: 40,
category: "Training",
level: "Professional",
image: "https://images.unsplash.com/photo-1559757175-0eb30cd8c063?w=400&h=300&fit=crop&crop=center",
instructor: "Nursing Faculty Team",
price: "N/A",
startDate: "2024-04-11",
eligibility: ["Registered Nurse", "Critical care experience preferred"],
objectives: ["Advanced trauma nursing skills", "Manikin-based training proficiency"]
},
{
id: '3',
title: "Trauma First Responder Program",
description: "Partners: RCPSG Hope Foundation, local colleges. Locations: Walajapet, Auxilium College—250 students trained. Curriculum: CPR, airway support, bleeding control, scene assessment.",
duration: "Varies",
seats: 250,
category: "Workshop",
level: "Beginner",
image: "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=400&h=300&fit=crop&crop=center",
instructor: "Community Trainers",
price: "N/A",
startDate: "2023-01-01",
eligibility: ["Students", "Community members"],
objectives: ["CPR proficiency", "Basic trauma response", "Scene safety assessment"]
},
{
id: '4',
title: "FNB in Trauma Surgery",
description: "3-year structured training program in acute surgery, ICU management, and research. Open to MS-qualified surgeons seeking specialized trauma surgery expertise.",
duration: "3 Years",
seats: 8,
category: "Certification",
level: "Advanced",
image: "https://images.unsplash.com/photo-1582750433449-648ed127bb54?w=400&h=300&fit=crop&crop=center",
instructor: "Senior Trauma Surgeons",
price: "N/A",
startDate: "2025-07-01",
eligibility: ["MS qualification in Surgery", "Valid medical license"],
objectives: ["Advanced trauma surgery skills", "ICU management", "Research methodology"]
},
{
id: '5',
title: "Observerships & Electives",
description: "4-8 week clinical blocks for national and international residents. Includes ATLS® course access and hands-on trauma experience. Application by email required.",
duration: "4-8 Weeks",
seats: 20,
category: "Training",
level: "Intermediate",
image: "https://images.unsplash.com/photo-1551601651-2a8555f1a136?w=400&h=300&fit=crop&crop=center",
instructor: "Clinical Faculty",
price: "N/A",
startDate: "2025-01-15",
eligibility: ["Medical residency status", "Valid medical credentials"],
objectives: ["Clinical observation skills", "Hands-on trauma experience", "ATLS certification"]
},
{
id: '6',
title: "Nursing Skills Lab",
description: "Trauma-focused skills laboratory sessions open quarterly (Q2, Q4). Includes chest tube insertion, airway management drills, and EFAST simulation training.",
duration: "2 Days",
seats: 30,
category: "Workshop",
level: "Intermediate",
image: "https://images.unsplash.com/photo-1559757148-5c350d0d3c56?w=400&h=300&fit=crop&crop=center",
instructor: "Nursing Skills Faculty",
price: "N/A",
startDate: "2025-04-01",
eligibility: ["Licensed nurse", "Basic trauma knowledge"],
objectives: ["Chest tube insertion", "Airway management", "EFAST simulation"]
}
];
}
}
export const educationService = new EducationService();

View File

@ -0,0 +1,160 @@
// services/eventService.ts
export interface Event {
id: number;
title: string;
description: string;
date: string;
subtitle: string;
mainImage: string;
galleryImages: string[];
price?: number;
location?: string;
}
export interface ApiEvent {
id: number;
title: string;
description: string;
date: string;
subtitle?: string;
imageUrl?: string;
galleryImages?: string[];
price?: number;
location?: string;
// Add other fields that your Spring Boot API returns
}
class EventService {
private apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080';
async getAllEvents(): Promise<Event[]> {
try {
const response = await fetch(`${this.apiBaseUrl}/api/events`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const apiEvents: ApiEvent[] = await response.json();
return this.transformApiEventsToEvents(apiEvents);
} catch (error) {
console.error('Error fetching events:', error);
return this.getFallbackEvents(); // Return fallback data if API fails
}
}
async getEventById(id: number): Promise<Event | null> {
try {
const response = await fetch(`${this.apiBaseUrl}/api/events/${id}`);
if (!response.ok) {
if (response.status === 404) {
return null;
}
throw new Error(`HTTP error! status: ${response.status}`);
}
const apiEvent: ApiEvent = await response.json();
return this.transformApiEventToEvent(apiEvent);
} catch (error) {
console.error(`Error fetching event ${id}:`, error);
return null;
}
}
private transformApiEventsToEvents(apiEvents: ApiEvent[]): Event[] {
return apiEvents.map(apiEvent => this.transformApiEventToEvent(apiEvent));
}
private transformApiEventToEvent(apiEvent: ApiEvent): Event {
return {
id: apiEvent.id,
title: apiEvent.title,
description: apiEvent.description,
date: this.formatDate(apiEvent.date),
subtitle: apiEvent.subtitle || 'More details coming soon',
mainImage: apiEvent.imageUrl || this.getDefaultMainImage(),
galleryImages: apiEvent.galleryImages || this.getDefaultGalleryImages(),
price: apiEvent.price,
location: apiEvent.location
};
}
private formatDate(dateString: string): string {
try {
const date = new Date(dateString);
return date.toLocaleDateString('en-GB', {
day: 'numeric',
month: 'long',
year: 'numeric'
});
} catch (error) {
return dateString; // Return original if parsing fails
}
}
private getDefaultMainImage(): string {
const defaultImages = [
'https://images.unsplash.com/photo-1559757148-5c350d0d3c56?w=400&h=200&fit=crop',
'https://images.unsplash.com/photo-1559757175-0eb30cd8c063?w=400&h=200&fit=crop',
'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=400&h=200&fit=crop',
'https://images.unsplash.com/photo-1576091160550-2173dba999ef?w=400&h=200&fit=crop'
];
return defaultImages[Math.floor(Math.random() * defaultImages.length)];
}
private getDefaultGalleryImages(): string[] {
return [
'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-1576091160550-2173dba999ef?w=200&h=100&fit=crop'
];
}
private getFallbackEvents(): Event[] {
// Return the original hardcoded events as fallback
return [
{
id: 1,
date: '28 September 2025',
title: 'Advanced Cardiac Surgery Symposium',
description: 'Cutting-edge techniques in minimally invasive cardiac procedures',
subtitle: 'Learn from world-renowned cardiac surgeons about the latest innovations',
mainImage: 'https://images.unsplash.com/photo-1559757148-5c350d0d3c56?w=400&h=200&fit=crop',
galleryImages: [
'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'
]
},
{
id: 2,
date: '2 October 2025',
title: 'Pediatric Immunization Update Conference',
description: 'Latest developments in childhood vaccination protocols',
subtitle: 'Comprehensive review of new vaccine guidelines and safety data',
mainImage: 'https://images.unsplash.com/photo-1559757175-0eb30cd8c063?w=400&h=200&fit=crop',
galleryImages: [
'https://images.unsplash.com/photo-1584362917165-526a968579e8?w=200&h=100&fit=crop',
'https://images.unsplash.com/photo-1612349317150-e413f6a5b16d?w=200&h=100&fit=crop',
'https://images.unsplash.com/photo-1581056771107-24ca5f033842?w=200&h=100&fit=crop',
'https://images.unsplash.com/photo-1576091160550-2173dba999ef?w=200&h=100&fit=crop'
]
},
{
id: 3,
date: '8 October 2025',
title: 'Mental Health in Healthcare Workers',
description: 'Addressing burnout and psychological well-being in medical practice',
subtitle: 'Strategies for maintaining mental health in high-pressure environments',
mainImage: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=400&h=200&fit=crop',
galleryImages: [
'https://images.unsplash.com/photo-1582719508461-905c673771fd?w=200&h=100&fit=crop',
'https://images.unsplash.com/photo-1559757148-5c350d0d3c56?w=200&h=100&fit=crop',
'https://images.unsplash.com/photo-1576091160550-2173dba999ef?w=200&h=100&fit=crop',
'https://images.unsplash.com/photo-1582750433449-648ed127bb54?w=200&h=100&fit=crop'
]
}
];
}
}
export const eventService = new EventService();

View File

@ -0,0 +1,34 @@
// services/fileUploadService.ts
export interface FileUploadResponse {
url: string;
filename: string;
}
class FileUploadService {
private apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080';
async uploadFile(file: File): Promise<FileUploadResponse> {
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch(`${this.apiBaseUrl}/api/files/upload`, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
const result: FileUploadResponse = await response.json();
return result;
} catch (error) {
console.error('File upload error:', error);
throw error;
}
}
}
export const fileUploadService = new FileUploadService();

View File

@ -0,0 +1,72 @@
// services/upcomingEventsService.ts
export interface ApiUpcomingEvent {
id: number;
title: string;
description: string;
schedule: string;
eventDate?: string;
isActive: boolean;
createdDate?: string;
updatedDate?: string;
}
export interface UpcomingEvent {
id: string;
title: string;
description: string;
schedule: string;
eventDate?: string;
}
class UpcomingEventsService {
private apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080';
async getActiveUpcomingEvents(): Promise<UpcomingEvent[]> {
try {
const response = await fetch(`${this.apiBaseUrl}/api/upcoming-events/active`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const apiEvents: ApiUpcomingEvent[] = await response.json();
return this.transformApiEventsToEvents(apiEvents);
} catch (error) {
console.error('Error fetching upcoming events:', error);
return this.getFallbackEvents();
}
}
private transformApiEventsToEvents(apiEvents: ApiUpcomingEvent[]): UpcomingEvent[] {
return apiEvents.map(apiEvent => ({
id: apiEvent.id.toString(),
title: apiEvent.title,
description: apiEvent.description,
schedule: apiEvent.schedule,
eventDate: apiEvent.eventDate
}));
}
private getFallbackEvents(): UpcomingEvent[] {
return [
{
id: '1',
title: 'Simulation-based Team Drills',
description: 'Hands-on simulation training designed to improve team coordination and emergency response in high-pressure trauma situations.',
schedule: 'Q3 2025'
},
{
id: '2',
title: 'Online Webinar Series',
description: 'Monthly online sessions covering trauma ethics, young doctor support, and professional development in emergency medicine.',
schedule: 'Monthly Sessions'
},
{
id: '3',
title: 'Community Education',
description: 'Road safety fairs and school education sessions to promote trauma prevention and basic first aid awareness in the community.',
schedule: 'Ongoing'
}
];
}
}
export const upcomingEventsService = new UpcomingEventsService();

27
tsconfig.json Normal file
View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}