Hero image and service admin upload updated

This commit is contained in:
2025-11-06 10:29:44 +05:30
parent 3f5604aa6b
commit d63aaf8901
2 changed files with 225 additions and 49 deletions

View File

@ -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>
);

View File

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