Initial commit of project without large files

This commit is contained in:
2025-12-29 15:45:02 +05:30
parent 82ad17a88a
commit 2a58e4af62
79 changed files with 26524 additions and 0 deletions

284
src/pages/Projects.jsx Normal file
View File

@@ -0,0 +1,284 @@
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;