Mobile view changes
This commit is contained in:
2
.env
2
.env
@@ -1,3 +1,3 @@
|
|||||||
REACT_APP_API_BASE_URL=http://localhost:8000
|
REACT_APP_API_BASE_URL=http://192.168.1.48:8000
|
||||||
HOST=0.0.0.0
|
HOST=0.0.0.0
|
||||||
|
|
||||||
|
|||||||
@@ -9,22 +9,61 @@ import mechanicalBg from "../assets/mechanical-bg.jpg";
|
|||||||
import electricalBg from "../assets/electrical-bg.jpg";
|
import electricalBg from "../assets/electrical-bg.jpg";
|
||||||
import electromechanicalBg from "../assets/electromechanical-bg.jpg";
|
import electromechanicalBg from "../assets/electromechanical-bg.jpg";
|
||||||
import itBg from "../assets/it-bg.jpg";
|
import itBg from "../assets/it-bg.jpg";
|
||||||
import renewableBg from "../assets/renewable-bg.jpg"; // renamed image
|
import renewableBg from "../assets/renewable-bg.jpg";
|
||||||
|
|
||||||
// Animation variants
|
/* =====================
|
||||||
|
Animation Variants
|
||||||
|
===================== */
|
||||||
const containerVariants = {
|
const containerVariants = {
|
||||||
hidden: { opacity: 0 },
|
hidden: { opacity: 0 },
|
||||||
visible: {
|
visible: {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
transition: { staggerChildren: 0.2 }
|
transition: { staggerChildren: 0.2 },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const itemVariants = {
|
const itemVariants = {
|
||||||
hidden: { opacity: 0, y: 20 },
|
hidden: { opacity: 0, y: 20 },
|
||||||
visible: { opacity: 1, y: 0 }
|
visible: { opacity: 1, y: 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* =====================
|
||||||
|
Working Fields Data
|
||||||
|
(Declared Once)
|
||||||
|
===================== */
|
||||||
|
const WORKING_FIELDS = [
|
||||||
|
{
|
||||||
|
title: "Civil Engineering",
|
||||||
|
desc: "Urban infrastructure, roadworks, dams, WTPs, STPs, stormwater management, and structural development.",
|
||||||
|
bg: civilBg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Mechanical Engineering",
|
||||||
|
desc: "Fabrication, installation, and maintenance of industrial systems, heavy machinery, and automated solutions including SCADA.",
|
||||||
|
bg: mechanicalBg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Electrical Engineering",
|
||||||
|
desc: "Reliable installations, power distribution, and maintenance services.",
|
||||||
|
bg: electricalBg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Electromechanical & Instrumentation",
|
||||||
|
desc: "Integration of mechanical and electrical systems for pumping stations and automation.",
|
||||||
|
bg: electromechanicalBg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Information Technology",
|
||||||
|
desc: "Project planning, GIS, data systems, and automation tools for smart engineering.",
|
||||||
|
bg: itBg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Renewable Energy",
|
||||||
|
desc: "Solar, wind and sustainable energy solutions with advanced engineering support.",
|
||||||
|
bg: renewableBg,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const AboutUs = () => {
|
const AboutUs = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -32,8 +71,12 @@ const AboutUs = () => {
|
|||||||
<section className="about-hero">
|
<section className="about-hero">
|
||||||
<div className="hero-overlay">
|
<div className="hero-overlay">
|
||||||
<h1>About Us</h1>
|
<h1>About Us</h1>
|
||||||
<p className="hero-subtitle">Building the Nation with Precision & Excellence</p>
|
<p className="hero-subtitle">
|
||||||
<p className="mt-4 text-white text-lg italic">“Engineering the future, one project at a time.”</p>
|
Building the Nation with Precision & Excellence
|
||||||
|
</p>
|
||||||
|
<p className="mt-4 text-white text-lg italic">
|
||||||
|
“Engineering the future, one project at a time.”
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -51,110 +94,84 @@ const AboutUs = () => {
|
|||||||
About Laxmi Civil Engineering Services Pvt. Ltd.
|
About Laxmi Civil Engineering Services Pvt. Ltd.
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-700 leading-relaxed mb-4">
|
<p className="text-gray-700 leading-relaxed mb-4">
|
||||||
Laxmi Civil Engineering Services Pvt. Ltd. was established on <strong>15th August 1980</strong> as a partnership firm and later
|
Laxmi Civil Engineering Services Pvt. Ltd. was established on{" "}
|
||||||
converted into a Private Limited Company on <strong>31st March 2000</strong>.
|
<strong>15th August 1980</strong> as a partnership firm and later
|
||||||
|
converted into a Private Limited Company on{" "}
|
||||||
|
<strong>31st March 2000</strong>.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-700 leading-relaxed mb-4">
|
<p className="text-gray-700 leading-relaxed mb-4">
|
||||||
For over four decades, LCEPL has been delivering exceptional engineering and construction services, making us one of the leading civil engineering solution providers in India.
|
For over four decades, LCEPL has been delivering exceptional
|
||||||
|
engineering and construction services.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-700 leading-relaxed mb-4">
|
<p className="text-gray-700 leading-relaxed mb-4">
|
||||||
We specialize in major infrastructure projects such as portable water supply, irrigation, pumping stations, electromechanical works, and operation & maintenance.
|
We specialize in major infrastructure projects such as portable
|
||||||
|
water supply, irrigation, pumping stations, electromechanical
|
||||||
|
works, and operation & maintenance.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-700 leading-relaxed mb-4">
|
<p className="text-gray-700 leading-relaxed mb-4">
|
||||||
Our leadership in these unique services has allowed us to establish a strong foothold in the central, western, and southern regions of India, while also expanding into the northern region.
|
Our leadership in these unique services has allowed us to expand
|
||||||
|
across central, western, southern, and northern India.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-700 leading-relaxed">
|
<p className="text-gray-700 leading-relaxed">
|
||||||
With a workforce of <strong>1300+ professionals</strong>, supported by advanced technology and an inspiring work environment, we continue to move forward with confidence.
|
With a workforce of <strong>1300+ professionals</strong>, we
|
||||||
|
continue to move forward with confidence.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* COUNTERS */}
|
{/* COUNTERS */}
|
||||||
<motion.div
|
<motion.div
|
||||||
variants={containerVariants}
|
variants={containerVariants}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
animate="visible"
|
animate="visible"
|
||||||
className="grid grid-cols-1 gap-6"
|
className="grid grid-cols-1 gap-6"
|
||||||
>
|
>
|
||||||
<motion.div variants={itemVariants}>
|
<motion.div variants={itemVariants}>
|
||||||
<CounterCard count="45+" label="Years of Experience" icon="🎖️" className="bg-white shadow-lg border border-gray-200 px-4 py-3 rounded-lg" />
|
<CounterCard
|
||||||
|
count="45+"
|
||||||
|
label="Years of Experience"
|
||||||
|
icon="🎖️"
|
||||||
|
className="bg-white shadow-lg border px-4 py-3 rounded-lg"
|
||||||
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div variants={itemVariants}>
|
<motion.div variants={itemVariants}>
|
||||||
<CounterCard count="18+" label="Number of States" icon="🗺️" className="bg-white shadow-lg border border-gray-200 px-4 py-3 rounded-lg" />
|
<CounterCard
|
||||||
|
count="18+"
|
||||||
|
label="Number of States"
|
||||||
|
icon="🗺️"
|
||||||
|
className="bg-white shadow-lg border px-4 py-3 rounded-lg"
|
||||||
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div variants={itemVariants}>
|
<motion.div variants={itemVariants}>
|
||||||
<CounterCard count="50+" label="Clients" icon="🏭" className="bg-white shadow-lg border border-gray-200 px-4 py-3 rounded-lg" />
|
<CounterCard
|
||||||
|
count="50+"
|
||||||
|
label="Clients"
|
||||||
|
icon="🏭"
|
||||||
|
className="bg-white shadow-lg border px-4 py-3 rounded-lg"
|
||||||
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* QUOTE BANNER */}
|
|
||||||
<div className="bg-blue-100 text-blue-800 text-center py-6 text-xl font-semibold italic tracking-wide">
|
|
||||||
“Driven by innovation, guided by integrity, delivering with passion.”
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* VISION & MISSION SECTION */}
|
|
||||||
<motion.section initial={{ opacity: 0 }} whileInView={{ opacity: 1 }} transition={{ duration: 0.6 }} viewport={{ once: true }} className="relative py-20 px-4 md:px-8 lg:px-20 bg-gradient-to-br from-blue-900 to-blue-800 overflow-hidden">
|
|
||||||
<motion.div initial={{ y: 30, opacity: 0 }} whileInView={{ y: 0, opacity: 1 }} transition={{ duration: 0.6 }} viewport={{ once: true }} className="max-w-7xl mx-auto relative z-10">
|
|
||||||
<h2 className="text-4xl font-bold text-center text-white mb-16 relative pb-2">
|
|
||||||
<span className="relative inline-block">
|
|
||||||
Vision & Mission
|
|
||||||
<motion.span initial={{ scaleX: 0 }} whileInView={{ scaleX: 1 }} transition={{ duration: 0.6 }} viewport={{ once: true }} className="absolute bottom-0 left-0 w-full h-1 bg-red-500 z-0 transform origin-center" />
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<div className="grid md:grid-cols-2 gap-8 mb-16">
|
|
||||||
{/* Vision */}
|
|
||||||
<motion.div initial={{ x: -50, opacity: 0 }} whileInView={{ x: 0, opacity: 1 }} transition={{ duration: 0.5 }} viewport={{ once: true }} className="bg-white p-8 rounded-xl shadow-lg hover:shadow-xl transition-all border-l-4 border-blue-500 hover:border-blue-400">
|
|
||||||
<div className="flex items-start mb-6">
|
|
||||||
<div className="bg-blue-100 p-3 rounded-full mr-4">
|
|
||||||
<svg className="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="text-xl font-bold text-blue-900 mb-2">Vision</h3>
|
|
||||||
<p className="text-gray-700">To be a company at the forefront of engineering and construction, renowned for excellence, quality, performance, and reliability.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Mission */}
|
|
||||||
<motion.div initial={{ x: 50, opacity: 0 }} whileInView={{ x: 0, opacity: 1 }} transition={{ duration: 0.5 }} viewport={{ once: true }} className="bg-white p-8 rounded-xl shadow-lg hover:shadow-xl transition-all border-l-4 border-red-500 hover:border-red-400">
|
|
||||||
<div className="flex items-start mb-6">
|
|
||||||
<div className="bg-blue-100 p-3 rounded-full mr-4">
|
|
||||||
<svg className="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="text-xl font-bold text-blue-900 mb-2">Mission</h3>
|
|
||||||
<p className="text-gray-700">To complete every project undertaken with sincerity, excellence, and in a time-bound manner, meeting all expectations of the client.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</motion.section>
|
|
||||||
|
|
||||||
{/* WORKING FIELDS SECTION */}
|
{/* WORKING FIELDS SECTION */}
|
||||||
<section className="working-fields px-6 lg:px-20 py-16 bg-gray-50">
|
<section className="working-fields px-6 lg:px-20 py-16 bg-gray-50">
|
||||||
<h2 className="highlight-title text-4xl font-bold text-center mb-4 text-black">Expertise Across Multiple Sectors</h2>
|
<h2 className="highlight-title text-4xl font-bold text-center mb-4 text-black">
|
||||||
<p className="text-center text-gray-600 mb-12">Our multidisciplinary approach helps us lead across multiple domains.</p>
|
Expertise Across Multiple Sectors
|
||||||
|
</h2>
|
||||||
|
<p className="text-center text-gray-600 mb-12">
|
||||||
|
Our multidisciplinary approach helps us lead across multiple domains.
|
||||||
|
</p>
|
||||||
|
|
||||||
<div className="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
<div className="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
{[
|
{WORKING_FIELDS.map((field) => (
|
||||||
{ title: "Civil Engineering", desc: "Urban infrastructure, roadworks, dams, WTPs, STPs, stormwater management, and structural development.", bg: civilBg },
|
<div
|
||||||
{ title: "Mechanical Engineering", desc: "Fabrication, installation, and maintenance of industrial systems, heavy machinery, and automated solutions including SCADA.", bg: mechanicalBg },
|
key={field.title}
|
||||||
{ title: "Electrical Engineering", desc: "Reliable installations, power distribution, and maintenance services.", bg: electricalBg },
|
className="relative field-card rounded-xl shadow-lg overflow-hidden transition-all cursor-pointer bg-cover bg-center group hover:shadow-2xl"
|
||||||
{ title: "Electromechanical & Instrumentation", desc: "Integration of mechanical and electrical systems for pumping stations and automation.", bg: electromechanicalBg },
|
style={{ backgroundImage: `url(${field.bg})` }}
|
||||||
{ title: "Information Technology", desc: "Project planning, GIS, data systems, and automation tools for smart engineering.", bg: itBg },
|
>
|
||||||
{ title: "Renewable Energy", desc: "Solar, wind and sustainable energy solutions with advanced engineering support.", bg: renewableBg },
|
|
||||||
].map((field, idx) => (
|
|
||||||
<div key={idx} className="relative field-card rounded-xl shadow-lg overflow-hidden transition-all cursor-pointer bg-cover bg-center group hover:shadow-2xl" style={{ backgroundImage: `url(${field.bg})` }}>
|
|
||||||
<div className="absolute inset-0 bg-black/50 group-hover:bg-black/25 transition-all duration-300"></div>
|
<div className="absolute inset-0 bg-black/50 group-hover:bg-black/25 transition-all duration-300"></div>
|
||||||
|
|
||||||
<div className="relative p-6 flex flex-col items-center text-center text-white z-10">
|
<div className="relative p-6 flex flex-col items-center text-center text-white z-10">
|
||||||
<div className="text-5xl mb-4">{field.icon}</div>
|
|
||||||
<h3 className="text-xl font-bold mb-2">{field.title}</h3>
|
<h3 className="text-xl font-bold mb-2">{field.title}</h3>
|
||||||
<p>{field.desc}</p>
|
<p>{field.desc}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,23 @@ import React, { useState, useEffect } from "react";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
/* =====================
|
||||||
|
Constants (Optimized)
|
||||||
|
===================== */
|
||||||
|
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" },
|
||||||
|
];
|
||||||
|
|
||||||
const AddProjects = () => {
|
const AddProjects = () => {
|
||||||
const [projects, setProjects] = useState([]);
|
const [projects, setProjects] = useState([]);
|
||||||
const [sector, setSector] = useState("");
|
const [sector, setSector] = useState("");
|
||||||
@@ -10,22 +27,12 @@ const AddProjects = () => {
|
|||||||
const [editMode, setEditMode] = useState(false);
|
const [editMode, setEditMode] = useState(false);
|
||||||
const [editProjectId, setEditProjectId] = useState(null);
|
const [editProjectId, setEditProjectId] = useState(null);
|
||||||
|
|
||||||
const sectors = [
|
/* =====================
|
||||||
{ label: "Water Supply", value: "water supply" },
|
Fetch Projects
|
||||||
{ 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 all projects
|
|
||||||
const fetchProjects = async () => {
|
const fetchProjects = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get(`${process.env.REACT_APP_API_BASE_URL}/api/projects`);
|
const res = await axios.get(`${API_BASE}/api/projects`);
|
||||||
setProjects(res.data);
|
setProjects(res.data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error fetching projects", err);
|
console.error("Error fetching projects", err);
|
||||||
@@ -36,7 +43,9 @@ const AddProjects = () => {
|
|||||||
fetchProjects();
|
fetchProjects();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Reset the form
|
/* =====================
|
||||||
|
Helpers
|
||||||
|
===================== */
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setSector("");
|
setSector("");
|
||||||
setImage(null);
|
setImage(null);
|
||||||
@@ -45,16 +54,18 @@ const AddProjects = () => {
|
|||||||
setEditProjectId(null);
|
setEditProjectId(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle image selection
|
|
||||||
const handleImageChange = (e) => {
|
const handleImageChange = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
setImage(file);
|
setImage(file);
|
||||||
setImagePreview(file ? URL.createObjectURL(file) : null);
|
setImagePreview(file ? URL.createObjectURL(file) : null);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle form submit (add or update)
|
/* =====================
|
||||||
|
Submit (Add / Update)
|
||||||
|
===================== */
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!sector || (!editMode && !image)) {
|
if (!sector || (!editMode && !image)) {
|
||||||
toast.error("Sector and image are required!");
|
toast.error("Sector and image are required!");
|
||||||
return;
|
return;
|
||||||
@@ -66,10 +77,13 @@ const AddProjects = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (editMode) {
|
if (editMode) {
|
||||||
await axios.post(`${process.env.REACT_APP_API_BASE_URL}/api/projects/update/${editProjectId}`, formData);
|
await axios.post(
|
||||||
|
`${API_BASE}/api/projects/update/${editProjectId}`,
|
||||||
|
formData
|
||||||
|
);
|
||||||
toast.success("Project updated successfully!");
|
toast.success("Project updated successfully!");
|
||||||
} else {
|
} else {
|
||||||
await axios.post(`${process.env.REACT_APP_API_BASE_URL}/api/projects`, formData);
|
await axios.post(`${API_BASE}/api/projects`, formData);
|
||||||
toast.success("Project added successfully!");
|
toast.success("Project added successfully!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,21 +95,25 @@ const AddProjects = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load project into form for editing
|
/* =====================
|
||||||
|
Edit Project
|
||||||
|
===================== */
|
||||||
const handleEdit = (project) => {
|
const handleEdit = (project) => {
|
||||||
setSector(project.sector);
|
setSector(project.sector);
|
||||||
setImage(null);
|
setImage(null);
|
||||||
setImagePreview(`${process.env.REACT_APP_API_BASE_URL}${project.image}`);
|
setImagePreview(`${API_BASE}${project.image}`);
|
||||||
setEditMode(true);
|
setEditMode(true);
|
||||||
setEditProjectId(project.id);
|
setEditProjectId(project.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Delete a project
|
/* =====================
|
||||||
|
Delete Project
|
||||||
|
===================== */
|
||||||
const handleDelete = async (id) => {
|
const handleDelete = async (id) => {
|
||||||
if (!window.confirm("Are you sure you want to delete this project?")) return;
|
if (!window.confirm("Are you sure you want to delete this project?")) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.delete(`${process.env.REACT_APP_API_BASE_URL}/api/projects/${id}`);
|
await axios.delete(`${API_BASE}/api/projects/${id}`);
|
||||||
toast.success("Project deleted");
|
toast.success("Project deleted");
|
||||||
fetchProjects();
|
fetchProjects();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -111,14 +129,17 @@ const AddProjects = () => {
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{/* FORM */}
|
{/* FORM */}
|
||||||
<form onSubmit={handleSubmit} className="space-y-4 max-w-md mx-auto bg-white p-6 rounded-lg shadow">
|
<form
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
className="space-y-4 max-w-md mx-auto bg-white p-6 rounded-lg shadow"
|
||||||
|
>
|
||||||
<select
|
<select
|
||||||
value={sector}
|
value={sector}
|
||||||
onChange={(e) => setSector(e.target.value)}
|
onChange={(e) => setSector(e.target.value)}
|
||||||
className="w-full border p-2 rounded"
|
className="w-full border p-2 rounded"
|
||||||
>
|
>
|
||||||
<option value="">Select Sector</option>
|
<option value="">Select Sector</option>
|
||||||
{sectors.map((s) => (
|
{SECTORS.map((s) => (
|
||||||
<option key={s.value} value={s.value}>
|
<option key={s.value} value={s.value}>
|
||||||
{s.label}
|
{s.label}
|
||||||
</option>
|
</option>
|
||||||
@@ -147,6 +168,7 @@ const AddProjects = () => {
|
|||||||
>
|
>
|
||||||
{editMode ? "Update Project" : "Save Project"}
|
{editMode ? "Update Project" : "Save Project"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{editMode && (
|
{editMode && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -161,19 +183,25 @@ const AddProjects = () => {
|
|||||||
|
|
||||||
{/* PROJECT LIST */}
|
{/* PROJECT LIST */}
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
<h2 className="text-2xl font-semibold text-blue-800 mb-4">All Projects</h2>
|
<h2 className="text-2xl font-semibold text-blue-800 mb-4">
|
||||||
|
All Projects
|
||||||
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 sm:grid-cols-2 gap-6">
|
<div className="grid md:grid-cols-3 sm:grid-cols-2 gap-6">
|
||||||
{projects.map((proj) => (
|
{projects.map((proj) => (
|
||||||
<div
|
<div
|
||||||
key={proj.id}
|
key={proj.id}
|
||||||
className="bg-white p-4 rounded-xl shadow hover:shadow-lg transition-all relative"
|
className="bg-white p-4 rounded-xl shadow hover:shadow-lg transition-all"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`${process.env.REACT_APP_API_BASE_URL}${proj.image}`}
|
src={`${API_BASE}${proj.image}`}
|
||||||
alt={proj.sector}
|
alt={proj.sector}
|
||||||
className="h-56 w-full object-cover rounded mb-3"
|
className="h-56 w-full object-cover rounded mb-3"
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-gray-700 capitalize mb-1">Sector: {proj.sector}</p>
|
|
||||||
|
<p className="text-sm text-gray-700 capitalize mb-1">
|
||||||
|
Sector: {proj.sector}
|
||||||
|
</p>
|
||||||
|
|
||||||
<div className="flex justify-between mt-2">
|
<div className="flex justify-between mt-2">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,45 +1,72 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
|
/* =====================
|
||||||
|
Constants
|
||||||
|
===================== */
|
||||||
|
const API_BASE = process.env.REACT_APP_API_BASE_URL;
|
||||||
|
|
||||||
const AdminHR = () => {
|
const AdminHR = () => {
|
||||||
const [submissions, setSubmissions] = useState([]);
|
const [submissions, setSubmissions] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
/* =====================
|
||||||
|
Fetch Submissions
|
||||||
|
===================== */
|
||||||
|
const fetchSubmissions = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const res = await axios.get(`${API_BASE}/api/hr-submissions`);
|
||||||
|
setSubmissions(res.data || []);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching HR submissions:', err);
|
||||||
|
setError('Failed to load submissions');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Fetch HR submissions from API
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
axios
|
fetchSubmissions();
|
||||||
.get(`${process.env.REACT_APP_API_BASE_URL}/api/hr-submissions`)
|
|
||||||
.then((res) => {
|
|
||||||
setSubmissions(res.data || []);
|
|
||||||
setLoading(false);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('Error fetching HR submissions:', err);
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDelete = (id) => {
|
/* =====================
|
||||||
if (window.confirm('Are you sure you want to delete this submission?')) {
|
Delete Submission
|
||||||
axios
|
===================== */
|
||||||
.delete(`${process.env.REACT_APP_API_BASE_URL}/api/hr-submissions/${id}`)
|
const handleDelete = async (id) => {
|
||||||
.then(() => {
|
if (!window.confirm('Are you sure you want to delete this submission?')) return;
|
||||||
setSubmissions(submissions.filter((sub) => sub.id !== id));
|
|
||||||
})
|
try {
|
||||||
.catch((err) => console.error('Error deleting submission:', err));
|
await axios.delete(`${API_BASE}/api/hr-submissions/${id}`);
|
||||||
|
setSubmissions((prev) => prev.filter((sub) => sub.id !== id));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error deleting submission:', err);
|
||||||
|
alert('Failed to delete submission');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto px-4 py-16">
|
<div className="max-w-6xl mx-auto px-4 py-16">
|
||||||
<h2 className="text-3xl font-bold text-blue-900 mb-4">Admin - HR</h2>
|
<h2 className="text-3xl font-bold text-blue-900 mb-4">Admin - HR</h2>
|
||||||
<p className="text-gray-600 mb-6">Manage HR contacts and career submissions.</p>
|
<p className="text-gray-600 mb-6">
|
||||||
|
Manage HR contacts and career submissions.
|
||||||
|
</p>
|
||||||
|
|
||||||
{loading ? (
|
{/* STATES */}
|
||||||
<p className="text-gray-500">Loading submissions...</p>
|
{loading && <p className="text-gray-500">Loading submissions...</p>}
|
||||||
) : submissions.length === 0 ? (
|
|
||||||
|
{!loading && error && (
|
||||||
|
<p className="text-red-600">{error}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && !error && submissions.length === 0 && (
|
||||||
<p className="text-gray-500">No submissions found.</p>
|
<p className="text-gray-500">No submissions found.</p>
|
||||||
) : (
|
)}
|
||||||
|
|
||||||
|
{!loading && !error && submissions.length > 0 && (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full border-collapse border border-gray-200">
|
<table className="w-full border-collapse border border-gray-200">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -53,15 +80,20 @@ const AdminHR = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{submissions.map((sub, index) => (
|
{submissions.map((sub) => (
|
||||||
<tr key={sub.id || index} className="hover:bg-gray-50">
|
<tr key={sub.id} className="hover:bg-gray-50">
|
||||||
<td className="p-2 border">{sub.id}</td>
|
<td className="p-2 border">{sub.id}</td>
|
||||||
<td className="p-2 border">{sub.name}</td>
|
<td className="p-2 border">{sub.name}</td>
|
||||||
<td className="p-2 border">{sub.email}</td>
|
<td className="p-2 border">{sub.email}</td>
|
||||||
<td className="p-2 border">{sub.phone}</td>
|
<td className="p-2 border">{sub.phone}</td>
|
||||||
<td className="p-2 border">
|
<td className="p-2 border">
|
||||||
{sub.resume ? (
|
{sub.resume ? (
|
||||||
<a href={sub.resume} target="_blank" rel="noopener noreferrer" className="text-blue-600 underline">
|
<a
|
||||||
|
href={sub.resume}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-blue-600 underline"
|
||||||
|
>
|
||||||
View Resume
|
View Resume
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -72,10 +72,10 @@
|
|||||||
.counters-grid { max-width: 100%; gap: 0.8rem; }
|
.counters-grid { max-width: 100%; gap: 0.8rem; }
|
||||||
.counter-card {
|
.counter-card {
|
||||||
flex-direction: column; /* icon on top, number + label below */
|
flex-direction: column; /* icon on top, number + label below */
|
||||||
align-items: flex-start;
|
/* align-items: flex-start; */
|
||||||
padding: 12px 15px;
|
padding: 12px 15px;
|
||||||
min-height: 60px;
|
min-height: 60px;
|
||||||
}
|
}
|
||||||
.counter-card h3 { font-size: 1.4rem; margin-bottom: 3px; }
|
.counter-card h3 { font-size: 1.4rem; margin-bottom: 3px; }
|
||||||
.counter-card p { font-size: 0.9rem; }
|
.counter-card p { font-size: 1.5rem; }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user