Files
lcepl-frontend/src/pages/Projects.jsx

285 lines
9.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import Slider from "react-slick";
import heroBg from "../assets/projects-hero.jpg";
import "../styles/Projects.css";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
/* =====================
Custom Arrows
===================== */
const NextArrow = ({ className, style, onClick }) => (
<div
className={className}
style={{
...style,
display: "block",
background: "rgba(0,0,0,0.5)",
borderRadius: "50%",
padding: "10px",
right: "10px",
zIndex: 2,
}}
onClick={onClick}
/>
);
const PrevArrow = ({ className, style, onClick }) => (
<div
className={className}
style={{
...style,
display: "block",
background: "rgba(0,0,0,0.5)",
borderRadius: "50%",
padding: "10px",
left: "10px",
zIndex: 2,
}}
onClick={onClick}
/>
);
const Projects = () => {
const [selectedSector, setSelectedSector] = useState("all");
const [projects, setProjects] = useState([]);
const [loading, setLoading] = useState(true);
const [expandedDescriptions, setExpandedDescriptions] = useState({});
const [lightboxImage, setLightboxImage] = useState(null);
const sectorRef = useRef(null);
const sectors = [
{ label: "All", value: "all" },
{ label: "Water Supply", value: "water supply" },
{ label: "Storm Water", value: "storm water" },
{ label: "Electromechanical", value: "electromechanical" },
{ label: "Real Estate / Buildings", value: "real estate / buildings" },
{ label: "Tunnel", value: "tunnel" },
{ label: "Roads", value: "roads" },
{ label: "Wastewater / Sewerage", value: "wastewater / sewerage" },
{ label: "Irrigation", value: "irrigation" },
{ label: "Renewable Energy", value: "renewable energy" },
];
/* =====================
Fetch Projects
===================== */
useEffect(() => {
const fetchProjects = async () => {
try {
const res = await axios.get(
`${process.env.REACT_APP_API_BASE_URL}/api/projects`
);
setProjects(Array.isArray(res.data) ? res.data : []);
} catch (err) {
console.error("Error fetching projects:", err);
} finally {
setLoading(false);
}
};
fetchProjects();
}, []);
/* =====================
Scroll to sectors
===================== */
useEffect(() => {
if (window.location.hash === "#sectors" && sectorRef.current) {
setTimeout(() => {
sectorRef.current.scrollIntoView({ behavior: "smooth" });
}, 200);
}
}, []);
const filteredProjects = projects.filter((p) => {
if (selectedSector === "all") return true;
return (
p.sector &&
p.sector.toLowerCase().trim() === selectedSector.toLowerCase().trim()
);
});
const toggleDescription = (id) => {
setExpandedDescriptions((prev) => ({
...prev,
[id]: !prev[id],
}));
};
/* =====================
Slider Settings
===================== */
const sliderSettings = {
dots: false,
infinite: true,
speed: 600,
slidesToShow: 3,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 1500,
arrows: true,
nextArrow: <NextArrow />,
prevArrow: <PrevArrow />,
responsive: [
{ breakpoint: 1024, settings: { slidesToShow: 3 } },
{ breakpoint: 768, settings: { slidesToShow: 2 } },
{ breakpoint: 480, settings: { slidesToShow: 1 } },
],
};
return (
<div className="min-h-screen bg-gray-50">
{/* =====================
Hero Section
====================== */}
<div
className="relative bg-cover bg-center h-[60vh] flex items-center justify-center"
style={{ backgroundImage: `url(${heroBg})` }}
>
<div className="absolute inset-0 bg-black bg-opacity-50"></div>
<div className="relative z-10 text-center px-4">
<h1 className="text-3xl sm:text-4xl md:text-5xl font-extrabold text-white">
Our Projects
</h1>
<p className="text-lg sm:text-xl md:text-2xl text-white mt-2 italic">
Stronger Partnerships, Greater Success
</p>
</div>
</div>
{/* =====================
Sector Buttons
====================== */}
<div
ref={sectorRef}
className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 px-4 mt-8 mb-10"
>
{sectors.map((sector, index) => {
const bgColors = [
"bg-gradient-to-r from-blue-500 to-indigo-500",
"bg-gradient-to-r from-green-400 to-green-600",
"bg-gradient-to-r from-yellow-400 to-yellow-600",
"bg-gradient-to-r from-pink-400 to-pink-600",
"bg-gradient-to-r from-purple-400 to-purple-600",
"bg-gradient-to-r from-red-400 to-red-600",
];
const isSelected = selectedSector === sector.value;
return (
<button
key={sector.value}
onClick={() => setSelectedSector(sector.value)}
className={`text-white font-semibold px-5 py-3 rounded-2xl shadow-lg transition-all duration-300 hover:scale-105
${bgColors[index % bgColors.length]}
${isSelected ? "ring-4 ring-white ring-offset-2" : ""}`}
>
{sector.label}
</button>
);
})}
</div>
{/* =====================
Projects Slider
====================== */}
<div className="px-6 mb-16">
{loading ? (
<p className="text-center text-xl text-gray-500">Loading projects...</p>
) : filteredProjects.length === 0 ? (
<p className="text-center text-xl text-gray-600">
No projects available.
</p>
) : (
<Slider key={selectedSector} {...sliderSettings}>
{filteredProjects.map((project) => (
<div key={project.id} className="px-2">
<div className="bg-white rounded-xl shadow-lg overflow-hidden hover:shadow-2xl transition">
{/* ✅ FIXED IMAGE SIZE (NO BIG / SMALL ISSUE) */}
{project.image && (
<div className="relative w-full aspect-[16/9] bg-gray-200 overflow-hidden">
<img
src={`${process.env.REACT_APP_API_BASE_URL}${project.image}`}
alt={project.name}
className="absolute inset-0 w-full h-full object-cover cursor-pointer"
onClick={(e) => {
e.stopPropagation();
setLightboxImage(
`${process.env.REACT_APP_API_BASE_URL}${project.image}`
);
}}
/>
</div>
)}
<div className="p-4">
<h3 className="text-lg font-bold text-blue-800">
{project.name}
</h3>
<p className="text-sm text-gray-600">
Sector: {project.sector}
</p>
{project.location && (
<p className="text-sm text-gray-600">
Location: {project.location}
</p>
)}
{project.description && (
<p className="text-sm text-gray-700 mt-2">
{expandedDescriptions[project.id]
? project.description
: project.description.slice(0, 100) + "..."}
{project.description.length > 100 && (
<button
onClick={() => toggleDescription(project.id)}
className="ml-2 text-blue-600 underline text-sm"
>
{expandedDescriptions[project.id]
? "Read Less"
: "Read More"}
</button>
)}
</p>
)}
</div>
</div>
</div>
))}
</Slider>
)}
</div>
{/* =====================
Image Lightbox
====================== */}
{lightboxImage && (
<div
className="fixed inset-0 bg-black bg-opacity-80 z-50 flex items-center justify-center"
onClick={() => setLightboxImage(null)}
>
<div className="relative" onClick={(e) => e.stopPropagation()}>
<button
onClick={() => setLightboxImage(null)}
className="absolute -top-4 -right-4 text-white text-3xl font-bold"
>
×
</button>
<img
src={lightboxImage}
alt="Project"
className="max-w-[90vw] max-h-[90vh] object-contain"
/>
</div>
</div>
)}
</div>
);
};
export default Projects;