New changes UI
This commit is contained in:
@@ -8,15 +8,16 @@ import { toast } from "react-toastify";
|
||||
const API_BASE = process.env.REACT_APP_API_BASE_URL;
|
||||
|
||||
const SECTORS = [
|
||||
{ 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" },
|
||||
{ label: "All", value: "All" },
|
||||
{ label: "Water Supply", value: "Water Supply" },
|
||||
{ label: "Storm Water", value: "Storm Water" },
|
||||
{ label: "Electromechanical", value: "Electromechanical & Instrumentation" },
|
||||
{ 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" },
|
||||
];
|
||||
|
||||
const AddProjects = () => {
|
||||
|
||||
@@ -1,45 +1,7 @@
|
||||
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");
|
||||
@@ -54,7 +16,7 @@ const Projects = () => {
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "Water Supply", value: "water supply" },
|
||||
{ label: "Storm Water", value: "storm water" },
|
||||
{ label: "Electromechanical", value: "electromechanical" },
|
||||
{ label: "Electromechanical", value: "electromechanical & instrumentation" },
|
||||
{ label: "Real Estate / Buildings", value: "real estate / buildings" },
|
||||
{ label: "Tunnel", value: "tunnel" },
|
||||
{ label: "Roads", value: "roads" },
|
||||
@@ -63,9 +25,6 @@ const Projects = () => {
|
||||
{ label: "Renewable Energy", value: "renewable energy" },
|
||||
];
|
||||
|
||||
/* =====================
|
||||
Fetch Projects
|
||||
===================== */
|
||||
useEffect(() => {
|
||||
const fetchProjects = async () => {
|
||||
try {
|
||||
@@ -82,9 +41,6 @@ const Projects = () => {
|
||||
fetchProjects();
|
||||
}, []);
|
||||
|
||||
/* =====================
|
||||
Scroll to sectors
|
||||
===================== */
|
||||
useEffect(() => {
|
||||
if (window.location.hash === "#sectors" && sectorRef.current) {
|
||||
setTimeout(() => {
|
||||
@@ -108,32 +64,9 @@ const Projects = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
/* =====================
|
||||
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
|
||||
====================== */}
|
||||
{/* Hero Section */}
|
||||
<div
|
||||
className="relative bg-cover bg-center h-[60vh] flex items-center justify-center"
|
||||
style={{ backgroundImage: `url(${heroBg})` }}
|
||||
@@ -149,9 +82,7 @@ const Projects = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* =====================
|
||||
Sector Buttons
|
||||
====================== */}
|
||||
{/* 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"
|
||||
@@ -165,9 +96,7 @@ const Projects = () => {
|
||||
"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}
|
||||
@@ -182,81 +111,73 @@ const Projects = () => {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* =====================
|
||||
Projects Slider
|
||||
====================== */}
|
||||
<div className="px-6 mb-16">
|
||||
{/* Projects Grid */}
|
||||
<div className="px-6 mb-16 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 gap-6">
|
||||
{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>
|
||||
<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">
|
||||
filteredProjects.map((project) => (
|
||||
<div key={project.id} className="radar-card bg-white rounded-xl shadow-lg overflow-hidden hover:shadow-2xl transition flex flex-col h-full">
|
||||
|
||||
{/* ✅ 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>
|
||||
)}
|
||||
{project.image && (
|
||||
<div className="radar-image-container relative w-full aspect-[16/9] bg-gray-200 overflow-hidden flex-shrink-0">
|
||||
<img
|
||||
src={`${process.env.REACT_APP_API_BASE_URL}${project.image}`}
|
||||
alt={project.name}
|
||||
className="radar-image 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}
|
||||
<div className="p-4 flex flex-col flex-grow">
|
||||
<h3 className="radar-title text-lg font-bold text-blue-800 leading-tight">
|
||||
{project.name}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 mt-1 font-semibold">
|
||||
Sector: {project.sector
|
||||
.split(" ")
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(" ")}
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
{project.location && (
|
||||
<p className="text-sm text-gray-600">
|
||||
Location: {project.location}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{project.description && (
|
||||
<div className="text-sm text-gray-700 mt-auto pt-4">
|
||||
<p>
|
||||
{expandedDescriptions[project.id]
|
||||
? project.description
|
||||
: project.description.slice(0, 100) + "..."}
|
||||
</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>
|
||||
{project.description.length > 100 && (
|
||||
<button
|
||||
onClick={() => toggleDescription(project.id)}
|
||||
className="text-blue-600 underline text-sm mt-1"
|
||||
>
|
||||
{expandedDescriptions[project.id] ? "Read Less" : "Read More"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</Slider>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* =====================
|
||||
Image Lightbox
|
||||
====================== */}
|
||||
{/* Image Lightbox */}
|
||||
{lightboxImage && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-80 z-50 flex items-center justify-center"
|
||||
@@ -269,11 +190,7 @@ const Projects = () => {
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<img
|
||||
src={lightboxImage}
|
||||
alt="Project"
|
||||
className="max-w-[90vw] max-h-[90vh] object-contain"
|
||||
/>
|
||||
<img src={lightboxImage} alt="Project" className="max-w-[90vw] max-h-[90vh] object-contain" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -28,3 +28,29 @@
|
||||
stroke-width: 0.7;
|
||||
}
|
||||
}
|
||||
.map-wrapper {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.map-wrapper svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
/* Optional: reduce stroke width and marker size for mobile */
|
||||
.map-wrapper svg .rsm-geography {
|
||||
stroke-width: 0.5 !important;
|
||||
}
|
||||
|
||||
.map-wrapper svg circle {
|
||||
r: 6 !important; /* smaller circles on mobile */
|
||||
stroke-width: 1 !important;
|
||||
}
|
||||
|
||||
.map-wrapper svg text {
|
||||
font-size: 12px !important; /* smaller text on mobile */
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user