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 API_BASE = process.env.REACT_APP_API_BASE_URL;
|
||||||
|
|
||||||
const SECTORS = [
|
const SECTORS = [
|
||||||
{ label: "Water Supply", value: "water supply" },
|
{ label: "All", value: "All" },
|
||||||
{ label: "Storm Water", value: "storm water" },
|
{ label: "Water Supply", value: "Water Supply" },
|
||||||
{ label: "Electromechanical", value: "electromechanical" },
|
{ label: "Storm Water", value: "Storm Water" },
|
||||||
{ label: "Real Estate / Buildings", value: "real estate / buildings" },
|
{ label: "Electromechanical", value: "Electromechanical & Instrumentation" },
|
||||||
{ label: "Tunnel", value: "tunnel" },
|
{ label: "Real Estate / Buildings", value: "Real estate / Buildings" },
|
||||||
{ label: "Roads", value: "roads" },
|
{ label: "Tunnel", value: "Tunnel" },
|
||||||
{ label: "Wastewater / Sewerage", value: "wastewater / sewerage" },
|
{ label: "Roads", value: "Roads" },
|
||||||
{ label: "Irrigation", value: "irrigation" },
|
{ label: "Wastewater / Sewerage", value: "Wastewater / Sewerage" },
|
||||||
{ label: "Renewable Energy", value: "renewable energy" },
|
{ label: "Irrigation", value: "Irrigation" },
|
||||||
|
{ label: "Renewable Energy", value: "Renewable Energy" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const AddProjects = () => {
|
const AddProjects = () => {
|
||||||
|
|||||||
@@ -1,45 +1,7 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Slider from "react-slick";
|
|
||||||
import heroBg from "../assets/projects-hero.jpg";
|
import heroBg from "../assets/projects-hero.jpg";
|
||||||
import "../styles/Projects.css";
|
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 Projects = () => {
|
||||||
const [selectedSector, setSelectedSector] = useState("all");
|
const [selectedSector, setSelectedSector] = useState("all");
|
||||||
@@ -54,7 +16,7 @@ const Projects = () => {
|
|||||||
{ label: "All", value: "all" },
|
{ label: "All", value: "all" },
|
||||||
{ label: "Water Supply", value: "water supply" },
|
{ label: "Water Supply", value: "water supply" },
|
||||||
{ label: "Storm Water", value: "storm water" },
|
{ label: "Storm Water", value: "storm water" },
|
||||||
{ label: "Electromechanical", value: "electromechanical" },
|
{ label: "Electromechanical", value: "electromechanical & instrumentation" },
|
||||||
{ label: "Real Estate / Buildings", value: "real estate / buildings" },
|
{ label: "Real Estate / Buildings", value: "real estate / buildings" },
|
||||||
{ label: "Tunnel", value: "tunnel" },
|
{ label: "Tunnel", value: "tunnel" },
|
||||||
{ label: "Roads", value: "roads" },
|
{ label: "Roads", value: "roads" },
|
||||||
@@ -63,9 +25,6 @@ const Projects = () => {
|
|||||||
{ label: "Renewable Energy", value: "renewable energy" },
|
{ label: "Renewable Energy", value: "renewable energy" },
|
||||||
];
|
];
|
||||||
|
|
||||||
/* =====================
|
|
||||||
Fetch Projects
|
|
||||||
===================== */
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchProjects = async () => {
|
const fetchProjects = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -82,9 +41,6 @@ const Projects = () => {
|
|||||||
fetchProjects();
|
fetchProjects();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/* =====================
|
|
||||||
Scroll to sectors
|
|
||||||
===================== */
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.location.hash === "#sectors" && sectorRef.current) {
|
if (window.location.hash === "#sectors" && sectorRef.current) {
|
||||||
setTimeout(() => {
|
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 (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50">
|
||||||
{/* =====================
|
{/* Hero Section */}
|
||||||
Hero Section
|
|
||||||
====================== */}
|
|
||||||
<div
|
<div
|
||||||
className="relative bg-cover bg-center h-[60vh] flex items-center justify-center"
|
className="relative bg-cover bg-center h-[60vh] flex items-center justify-center"
|
||||||
style={{ backgroundImage: `url(${heroBg})` }}
|
style={{ backgroundImage: `url(${heroBg})` }}
|
||||||
@@ -149,9 +82,7 @@ const Projects = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* =====================
|
{/* Sector Buttons */}
|
||||||
Sector Buttons
|
|
||||||
====================== */}
|
|
||||||
<div
|
<div
|
||||||
ref={sectorRef}
|
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"
|
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-purple-400 to-purple-600",
|
||||||
"bg-gradient-to-r from-red-400 to-red-600",
|
"bg-gradient-to-r from-red-400 to-red-600",
|
||||||
];
|
];
|
||||||
|
|
||||||
const isSelected = selectedSector === sector.value;
|
const isSelected = selectedSector === sector.value;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={sector.value}
|
key={sector.value}
|
||||||
@@ -182,81 +111,73 @@ const Projects = () => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* =====================
|
{/* Projects Grid */}
|
||||||
Projects Slider
|
<div className="px-6 mb-16 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 gap-6">
|
||||||
====================== */}
|
|
||||||
<div className="px-6 mb-16">
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p className="text-center text-xl text-gray-500">Loading projects...</p>
|
<p className="text-center text-xl text-gray-500">Loading projects...</p>
|
||||||
) : filteredProjects.length === 0 ? (
|
) : filteredProjects.length === 0 ? (
|
||||||
<p className="text-center text-xl text-gray-600">
|
<p className="text-center text-xl text-gray-600">No projects available.</p>
|
||||||
No projects available.
|
|
||||||
</p>
|
|
||||||
) : (
|
) : (
|
||||||
<Slider key={selectedSector} {...sliderSettings}>
|
filteredProjects.map((project) => (
|
||||||
{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">
|
||||||
<div key={project.id} className="px-2">
|
|
||||||
<div className="bg-white rounded-xl shadow-lg overflow-hidden hover:shadow-2xl transition">
|
{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>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ✅ FIXED IMAGE SIZE (NO BIG / SMALL ISSUE) */}
|
<div className="p-4 flex flex-col flex-grow">
|
||||||
{project.image && (
|
<h3 className="radar-title text-lg font-bold text-blue-800 leading-tight">
|
||||||
<div className="relative w-full aspect-[16/9] bg-gray-200 overflow-hidden">
|
{project.name}
|
||||||
<img
|
</h3>
|
||||||
src={`${process.env.REACT_APP_API_BASE_URL}${project.image}`}
|
<p className="text-sm text-gray-600 mt-1 font-semibold">
|
||||||
alt={project.name}
|
Sector: {project.sector
|
||||||
className="absolute inset-0 w-full h-full object-cover cursor-pointer"
|
.split(" ")
|
||||||
onClick={(e) => {
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||||
e.stopPropagation();
|
.join(" ")}
|
||||||
setLightboxImage(
|
</p>
|
||||||
`${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}
|
{project.location && (
|
||||||
</h3>
|
<p className="text-sm text-gray-600">
|
||||||
<p className="text-sm text-gray-600">
|
Location: {project.location}
|
||||||
Sector: {project.sector}
|
</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>
|
</p>
|
||||||
|
{project.description.length > 100 && (
|
||||||
{project.location && (
|
<button
|
||||||
<p className="text-sm text-gray-600">
|
onClick={() => toggleDescription(project.id)}
|
||||||
Location: {project.location}
|
className="text-blue-600 underline text-sm mt-1"
|
||||||
</p>
|
>
|
||||||
)}
|
{expandedDescriptions[project.id] ? "Read Less" : "Read More"}
|
||||||
|
</button>
|
||||||
{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>
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</Slider>
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* =====================
|
{/* Image Lightbox */}
|
||||||
Image Lightbox
|
|
||||||
====================== */}
|
|
||||||
{lightboxImage && (
|
{lightboxImage && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-black bg-opacity-80 z-50 flex items-center justify-center"
|
className="fixed inset-0 bg-black bg-opacity-80 z-50 flex items-center justify-center"
|
||||||
@@ -269,11 +190,7 @@ const Projects = () => {
|
|||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
<img
|
<img src={lightboxImage} alt="Project" className="max-w-[90vw] max-h-[90vh] object-contain" />
|
||||||
src={lightboxImage}
|
|
||||||
alt="Project"
|
|
||||||
className="max-w-[90vw] max-h-[90vh] object-contain"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -28,3 +28,29 @@
|
|||||||
stroke-width: 0.7;
|
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