Jade-Infra-test / src /pages /ProjectsSection.jsx
rushiljain's picture
Upload 7 files
30f539e verified
import React, { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { ALL_PROJECTS } from './Projects.jsx';
import ProjectCard from '../components/ProjectCard.jsx';
import Reveal from '../components/Reveal.jsx';
import SectionIntro from '../components/SectionIntro.jsx';
const CATEGORIES = ['All', 'Residential', 'Commercial', 'Retail', 'Mixed Use'];
const STATUSES = ['All', 'Completed', 'Ongoing', 'Upcoming'];
export default function ProjectsSection() {
const { sectionId } = useParams();
const sectionTitle =
sectionId?.toLowerCase() === 'sra'
? 'SRA'
: sectionId?.toLowerCase() === 'construction'
? 'Project Contracting'
: sectionId?.charAt(0).toUpperCase() + sectionId?.slice(1).toLowerCase();
const [query, setQuery] = useState('');
const [filter, setFilter] = useState('All');
const [statusFilter, setStatusFilter] = useState('All');
const data = useMemo(() => {
return ALL_PROJECTS.filter((p) => {
const section = p.section?.toLowerCase();
const current = sectionId?.toLowerCase();
const matchesSection =
current === 'construction'
? ['construction', 'development', 'redevelopment'].includes(section)
: section === current;
const matchesCategory =
filter === 'All' || (Array.isArray(p.categories) && p.categories.includes(filter));
const matchesStatus = statusFilter === 'All' || p.status === statusFilter;
const q = query.trim().toLowerCase();
const matchesQuery =
!q ||
p.title.toLowerCase().includes(q) ||
p.location.toLowerCase().includes(q) ||
p.description.toLowerCase().includes(q);
return matchesSection && matchesCategory && matchesStatus && matchesQuery;
});
}, [sectionId, filter, statusFilter, query]);
return (
<section className="section" aria-labelledby="projects-section-heading">
<div className="container">
<header className="mb-8 text-center">
<h1 id="projects-section-heading" className="h2">{sectionTitle}</h1>
<p className="lead mt-3">Explore our {sectionTitle === 'SRA' ? 'SRA' : sectionTitle?.toLowerCase()} portfolio.</p>
</header>
<SectionIntro sectionId={sectionId} />
<div className="mb-8 card p-6 bg-gradient-to-br from-slate-50 to-white border-slate-200">
<div className="grid grid-cols-1 gap-4 md:grid-cols-4">
{/* Search Input */}
<div className="md:col-span-2">
<label htmlFor="search" className="mb-2 flex items-center gap-2 text-sm font-semibold text-slate-700">
<svg className="h-4 w-4 text-brand-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
Search Projects
</label>
<div className="relative">
<input
id="search"
placeholder="Search by name, location, or description..."
value={query}
onChange={(e) => setQuery(e.target.value)}
className="w-full rounded-lg border border-slate-300 bg-white px-4 py-3 pl-10 shadow-sm transition-all focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20"
/>
<svg className="absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
</div>
{/* Category Filter */}
<div className="md:col-span-1">
<label htmlFor="category-filter" className="mb-2 flex items-center gap-2 text-sm font-semibold text-slate-700">
<svg className="h-4 w-4 text-brand-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
Category
</label>
<div className="relative">
<select
id="category-filter"
value={filter}
onChange={(e) => setFilter(e.target.value)}
className="w-full appearance-none rounded-lg border border-slate-300 bg-white px-4 py-3 pr-10 shadow-sm transition-all focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20"
>
{CATEGORIES.map((c) => (
<option key={c} value={c}>{c}</option>
))}
</select>
<svg className="pointer-events-none absolute right-3 top-1/2 h-5 w-5 -translate-y-1/2 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
{/* Status Filter */}
<div className="md:col-span-1">
<label htmlFor="status-filter" className="mb-2 flex items-center gap-2 text-sm font-semibold text-slate-700">
<svg className="h-4 w-4 text-brand-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Status
</label>
<div className="relative">
<select
id="status-filter"
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="w-full appearance-none rounded-lg border border-slate-300 bg-white px-4 py-3 pr-10 shadow-sm transition-all focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20"
>
{STATUSES.map((s) => (
<option key={s} value={s}>{s}</option>
))}
</select>
<svg className="pointer-events-none absolute right-3 top-1/2 h-5 w-5 -translate-y-1/2 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
</div>
</div>
{(() => {
const ORDER = ['Ongoing', 'Upcoming', 'Completed'];
const buckets = (statusFilter === 'All' ? ORDER : [statusFilter]).map((s) => ({
status: s,
items: data.filter((p) => p.status === s)
}));
const any = buckets.some((b) => b.items.length > 0);
if (!any) return <p className="text-slate-600">No projects found.</p>;
return buckets.map((b) => {
if (b.items.length === 0) return null;
return (
<section key={b.status} className="mt-10 first:mt-0" aria-labelledby={`status-${b.status}`}>
<div className="mb-6 flex items-center justify-between">
<h2 id={`status-${b.status}`} className="h3">{b.status}</h2>
<div className="h-px flex-1 ml-6 bg-slate-200"></div>
</div>
{(() => {
const itemsSorted = [...b.items].sort((a, b) => {
const aHas = (Array.isArray(a.gallery) && a.gallery.length > 0) || !!a.image;
const bHas = (Array.isArray(b.gallery) && b.gallery.length > 0) || !!b.image;
// true first
if (aHas === bHas) return 0;
return aHas ? -1 : 1;
});
return (
<ul className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{itemsSorted.map((p, i) => (
<li key={p.slug}>
<Reveal delay={i * 60}>
<ProjectCard project={p} />
</Reveal>
</li>
))}
</ul>
);
})()}
</section>
);
});
})()}
</div>
</section>
);
}