Hero image and service admin upload updated
This commit is contained in:
@ -1,30 +1,80 @@
|
|||||||
import React from 'react';
|
'use client';
|
||||||
import { ShieldPlus, UserCheck, Hospital, BookOpen } from 'lucide-react';
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface ServiceTile {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
isActive: boolean;
|
||||||
|
displayOrder: number;
|
||||||
|
createdDate: string;
|
||||||
|
lastModified: string;
|
||||||
|
}
|
||||||
|
|
||||||
const StatisticsTiles = () => {
|
const StatisticsTiles = () => {
|
||||||
const tiles = [
|
const [tiles, setTiles] = useState<ServiceTile[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
|
// Default fallback data
|
||||||
|
const defaultTiles = [
|
||||||
{
|
{
|
||||||
icon: <ShieldPlus className="w-6 h-6" style={{ color: '#012068' }} />,
|
|
||||||
title: "Primary Trauma Care",
|
title: "Primary Trauma Care",
|
||||||
description: "Specialized care for Priority One trauma patients in close coordination with the Emergency Department team."
|
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: "24x7 Trauma Surgeon",
|
||||||
title: "24×7 Trauma Surgeon",
|
|
||||||
description: "Round-the-clock availability of trauma surgeons ensures immediate surgical intervention when needed."
|
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",
|
title: "Trauma Intensive & Ward Care",
|
||||||
description: "Comprehensive trauma intensive care and dedicated trauma ward services for critical and recovering patients."
|
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",
|
title: "Trauma Education",
|
||||||
description: "Focused education and counseling for patients and families to enhance recovery and awareness."
|
description: "Focused education and counseling for patients and families to enhance recovery and awareness."
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchServiceTiles = async () => {
|
||||||
|
try {
|
||||||
|
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080';
|
||||||
|
const response = await fetch(`${apiUrl}/service-tiles/active`);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data: ServiceTile[] = await response.json();
|
||||||
|
// Filter active tiles and sort by display order
|
||||||
|
const activeTiles = data
|
||||||
|
.filter(tile => tile.isActive)
|
||||||
|
.sort((a, b) => a.displayOrder - b.displayOrder);
|
||||||
|
|
||||||
|
if (activeTiles.length > 0) {
|
||||||
|
setTiles(activeTiles);
|
||||||
|
setError(false);
|
||||||
|
} else {
|
||||||
|
// No active tiles, use defaults
|
||||||
|
setError(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// API error, use defaults
|
||||||
|
setError(true);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching service tiles:', err);
|
||||||
|
setError(true);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchServiceTiles();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Use fetched tiles or fall back to default
|
||||||
|
const displayTiles = error || tiles.length === 0 ? defaultTiles : tiles;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-8 sm:py-12" style={{ backgroundColor: '#f4f4f4' }}>
|
<section className="py-8 sm:py-12" style={{ backgroundColor: '#f4f4f4' }}>
|
||||||
<div className="max-w-7xl mx-auto px-4">
|
<div className="max-w-7xl mx-auto px-4">
|
||||||
@ -34,37 +84,44 @@ const StatisticsTiles = () => {
|
|||||||
>
|
>
|
||||||
Our Trauma Care Services
|
Our Trauma Care Services
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 lg:gap-6">
|
|
||||||
{tiles.map((tile, index) => (
|
{loading ? (
|
||||||
<div
|
// Loading state
|
||||||
key={index}
|
<div className="flex justify-center items-center py-12">
|
||||||
className="border border-gray-300 rounded-lg p-5 h-full hover:shadow-lg transition-shadow duration-300"
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||||
style={{ backgroundColor: '#ffffff' }}
|
</div>
|
||||||
>
|
) : (
|
||||||
<div className="flex flex-col h-full">
|
// Display tiles
|
||||||
<div className="flex items-center mb-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 lg:gap-6">
|
||||||
<div className="flex-shrink-0 mr-3">
|
{displayTiles.map((tile, index) => (
|
||||||
{tile.icon}
|
<div
|
||||||
|
key={index}
|
||||||
|
className="border border-gray-300 rounded-lg p-5 h-full hover:shadow-lg transition-shadow duration-300"
|
||||||
|
style={{ backgroundColor: '#ffffff' }}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col h-full">
|
||||||
|
<div className="flex items-center mb-3">
|
||||||
|
<h3
|
||||||
|
className="text-base sm:text-lg font-medium"
|
||||||
|
style={{ color: '#012068' }}
|
||||||
|
>
|
||||||
|
{tile.title}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<h3
|
<p
|
||||||
className="text-base sm:text-lg font-medium"
|
className="text-sm sm:text-base leading-relaxed flex-grow"
|
||||||
style={{ color: '#012068' }}
|
style={{ color: '#333' }}
|
||||||
>
|
>
|
||||||
{tile.title}
|
{tile.description}
|
||||||
</h3>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p
|
|
||||||
className="text-sm sm:text-base leading-relaxed flex-grow" style={{ color: '#333' }}
|
|
||||||
>
|
|
||||||
{tile.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StatisticsTiles;
|
export default StatisticsTiles;
|
||||||
@ -1,16 +1,121 @@
|
|||||||
import React from 'react';
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
interface HeroImage {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
description: string;
|
||||||
|
imageUrl: string;
|
||||||
|
imageFilename: string;
|
||||||
|
isActive: boolean;
|
||||||
|
uploadDate: string;
|
||||||
|
lastModified: string;
|
||||||
|
}
|
||||||
|
|
||||||
const HeroSection = () => {
|
const HeroSection = () => {
|
||||||
|
const [heroData, setHeroData] = useState<HeroImage | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
|
// Default fallback data
|
||||||
|
const defaultHeroData = {
|
||||||
|
title: 'This year, we celebrate',
|
||||||
|
subtitle: '125 years of CMC Vellore',
|
||||||
|
description: '1900 - 2025',
|
||||||
|
imageUrl: '/images/heronew.png',
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchHeroImage = async () => {
|
||||||
|
try {
|
||||||
|
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080';
|
||||||
|
console.log('Fetching from:', `${apiUrl}/hero/active`); // Debug log
|
||||||
|
|
||||||
|
const response = await fetch(`${apiUrl}/hero/active`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
cache: 'no-store', // Disable caching to always get fresh data
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data: HeroImage = await response.json();
|
||||||
|
console.log('Hero image data:', data); // Debug log
|
||||||
|
setHeroData(data);
|
||||||
|
setError(false);
|
||||||
|
} else {
|
||||||
|
console.error('Failed to fetch hero image:', response.status);
|
||||||
|
setError(true);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching hero image:', err);
|
||||||
|
setError(true);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchHeroImage();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Use fetched data or fall back to default
|
||||||
|
const displayData = heroData || defaultHeroData;
|
||||||
|
|
||||||
|
// ✅ FIX: Construct full image URL with API base URL
|
||||||
|
const getImageUrl = () => {
|
||||||
|
if (!heroData) {
|
||||||
|
return defaultHeroData.imageUrl; // Local fallback image
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080';
|
||||||
|
|
||||||
|
// If imageUrl already contains the full URL (starts with http), use it directly
|
||||||
|
if (heroData.imageUrl.startsWith('http')) {
|
||||||
|
return heroData.imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, construct the full URL
|
||||||
|
// Remove leading slash from imageUrl if present to avoid double slashes
|
||||||
|
const imagePath = heroData.imageUrl.startsWith('/')
|
||||||
|
? heroData.imageUrl.slice(1)
|
||||||
|
: heroData.imageUrl;
|
||||||
|
|
||||||
|
return `${apiUrl}/${imagePath}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const imageUrl = getImageUrl();
|
||||||
|
|
||||||
|
console.log('Final image URL:', imageUrl); // Debug log
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-80 md:h-96 lg:h-[380px] min-h-80 md:min-h-96 lg:min-h-[500px] overflow-hidden">
|
<div className="relative h-80 md:h-96 lg:h-[380px] min-h-80 md:min-h-96 lg:min-h-[500px] overflow-hidden">
|
||||||
{/* Background Image */}
|
{/* Background Image */}
|
||||||
<Image
|
{heroData ? (
|
||||||
src="/images/heronew.png"
|
// For API images - use full URL with API base
|
||||||
alt="CMC Vellore 125 years celebration"
|
<img
|
||||||
fill
|
src={imageUrl}
|
||||||
className="object-cover"
|
alt={displayData.title}
|
||||||
/>
|
className="absolute inset-0 w-full h-full object-cover"
|
||||||
|
onError={(e) => {
|
||||||
|
console.error('Image failed to load:', imageUrl);
|
||||||
|
// Fallback to default image on error
|
||||||
|
e.currentTarget.src = defaultHeroData.imageUrl;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
// For local images (fallback)
|
||||||
|
<Image
|
||||||
|
src={imageUrl}
|
||||||
|
alt={displayData.title}
|
||||||
|
fill
|
||||||
|
className="object-cover"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="relative z-10 flex items-center h-full px-4 sm:px-6 md:px-8 lg:px-12 xl:px-16 2xl:px-24">
|
<div className="relative z-10 flex items-center h-full px-4 sm:px-6 md:px-8 lg:px-12 xl:px-16 2xl:px-24">
|
||||||
@ -19,15 +124,15 @@ const HeroSection = () => {
|
|||||||
<div className="bg-white/20 backdrop-blur-[2px] rounded-2xl p-8 sm:p-10 md:p-12 lg:p-4 shadow-2xl border border-white/30">
|
<div className="bg-white/20 backdrop-blur-[2px] rounded-2xl p-8 sm:p-10 md:p-12 lg:p-4 shadow-2xl border border-white/30">
|
||||||
{/* Main Heading */}
|
{/* Main Heading */}
|
||||||
<h1 className="mb-6 sm:mb-8 md:mb-10" style={{ color: '#012068' }}>
|
<h1 className="mb-6 sm:mb-8 md:mb-10" style={{ color: '#012068' }}>
|
||||||
<div className="text-lg sm:text-xl md:text-2xl lg:text-3xl xl:text-4xl 2xl:text-5xl font-semibold leading-tight mb-1 sm:mb-2 md:mb-3 lg:mb-4">
|
<div className="text-lg sm:text-xl md:text-2xl lg:text-3xl xl:text-4xl 2xl:text-5xl font-semibold leading-tight mb-1 sm:mb-2 md:mb-3 lg:mb-4">
|
||||||
This year, we celebrate <span className='text-5xl text-bold'>125 years</span>
|
This year, we celebrate <span className='text-5xl text-bold'>125 years</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-lg sm:text-xl md:text-2xl lg:text-3xl xl:text-4xl 2xl:text-5xl font-semibold leading-tight mb-1 sm:mb-2 md:mb-3 lg:mb-4">
|
<div className="text-lg sm:text-xl md:text-2xl lg:text-3xl xl:text-4xl 2xl:text-5xl font-semibold leading-tight mb-1 sm:mb-2 md:mb-3 lg:mb-4">
|
||||||
of CMC Vellore
|
of CMC Vellore
|
||||||
</div>
|
</div>
|
||||||
<div className="text-base sm:text-lg md:text-xl lg:text-2xl xl:text-3xl 2xl:text-4xl font-semibold" style={{ color: '#012068' }}>
|
<div className="text-base sm:text-lg md:text-xl lg:text-2xl xl:text-3xl 2xl:text-4xl font-semibold" style={{ color: '#012068' }}>
|
||||||
1900 - 2025
|
1900 - 2025
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{/* CTA Button */}
|
{/* CTA Button */}
|
||||||
@ -46,6 +151,20 @@ const HeroSection = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Loading indicator */}
|
||||||
|
{loading && (
|
||||||
|
<div className="absolute top-4 right-4 z-20">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-white"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Error indicator (for debugging) */}
|
||||||
|
{error && process.env.NODE_ENV === 'development' && (
|
||||||
|
<div className="absolute top-4 left-4 z-20 bg-red-500 text-white px-4 py-2 rounded">
|
||||||
|
Failed to load hero image
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user