Hero image and service admin upload updated
This commit is contained in:
@ -1,30 +1,80 @@
|
||||
import React from 'react';
|
||||
import { ShieldPlus, UserCheck, Hospital, BookOpen } from 'lucide-react';
|
||||
'use client';
|
||||
|
||||
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 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",
|
||||
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",
|
||||
title: "24x7 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."
|
||||
},
|
||||
];
|
||||
|
||||
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 (
|
||||
<section className="py-8 sm:py-12" style={{ backgroundColor: '#f4f4f4' }}>
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
@ -34,34 +84,41 @@ const StatisticsTiles = () => {
|
||||
>
|
||||
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: '#ffffff' }}
|
||||
>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex items-center mb-3">
|
||||
<div className="flex-shrink-0 mr-3">
|
||||
{tile.icon}
|
||||
|
||||
{loading ? (
|
||||
// Loading state
|
||||
<div className="flex justify-center items-center py-12">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
) : (
|
||||
// Display tiles
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 lg:gap-6">
|
||||
{displayTiles.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: '#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>
|
||||
<h3
|
||||
className="text-base sm:text-lg font-medium"
|
||||
style={{ color: '#012068' }}
|
||||
<p
|
||||
className="text-sm sm:text-base leading-relaxed flex-grow"
|
||||
style={{ color: '#333' }}
|
||||
>
|
||||
{tile.title}
|
||||
</h3>
|
||||
{tile.description}
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
className="text-sm sm:text-base leading-relaxed flex-grow" style={{ color: '#333' }}
|
||||
>
|
||||
{tile.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@ -1,16 +1,121 @@
|
||||
import React from 'react';
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
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 [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 (
|
||||
<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 */}
|
||||
<Image
|
||||
src="/images/heronew.png"
|
||||
alt="CMC Vellore 125 years celebration"
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
{heroData ? (
|
||||
// For API images - use full URL with API base
|
||||
<img
|
||||
src={imageUrl}
|
||||
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 */}
|
||||
<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">
|
||||
{/* Main Heading */}
|
||||
<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">
|
||||
This year, we celebrate <span className='text-5xl text-bold'>125 years</span>
|
||||
</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">
|
||||
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" style={{ color: '#012068' }}>
|
||||
1900 - 2025
|
||||
</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">
|
||||
This year, we celebrate <span className='text-5xl text-bold'>125 years</span>
|
||||
</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">
|
||||
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" style={{ color: '#012068' }}>
|
||||
1900 - 2025
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
{/* CTA Button */}
|
||||
@ -46,6 +151,20 @@ const HeroSection = () => {
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user