Files
aramesh-backend/frontend/src/pages/Projects.tsx
T
2026-09-25 22:49:30 +03:30

169 lines
6.3 KiB
TypeScript

import React, { useMemo, useState } from 'react';
import { PageHero } from '../components/PageHero';
import { Reveal } from '../components/Reveal';
import { ProjectCard } from '../components/ProjectCard';
import { PremiumButton } from '../components/PremiumButton';
import { Gallery } from '../components/home/Gallery';
import { ContactSection } from '../components/ContactSection';
import { useSite, useStatusLabel } from '../api';
import { useLang } from '../i18n/lang';
import { useFieldSaver } from '../admin/AdminContext';
import { InlineEdit } from '../components/admin/InlineEdit';
import { projectsData } from '../data/projectsData';
import { LazyProjectsMapAll } from '../map/LazyMaps';
import { MAP_PROJECTS, type MapProject } from '../data/mapProjects';
export function Projects() {
const { status_labels } = useSite();
const { t } = useLang();
const save = useFieldSaver();
const saveStatusLabel = (key: string, value: string) => {
const label = status_labels.find((s) => s.key === key);
if (!label) return;
void save('status-labels', label.id, { label: value }, (d) => {
const s = d.status_labels.find((x) => x.id === label.id);
if (s) s.label = value;
});
};
const doneLabel = useStatusLabel('done');
const progressLabel = useStatusLabel('progress');
const FILTERS: { key: 'done' | 'progress'; label: string; }[] = [
{ key: 'done', label: doneLabel },
{ key: 'progress', label: progressLabel }
];
const [status, setStatus] = useState<'done' | 'progress'>('done');
// Filtering and Pagination State
const [page, setPage] = useState(1);
const [isLoading, setIsLoading] = useState(false);
const itemsPerPage = 12;
// Filter projects by status
const filteredProjects = useMemo(() => {
return projectsData.filter((p) => p.status === status);
}, [status]);
// Currently displayed projects
const displayedProjects = useMemo(() => {
return filteredProjects.slice(0, page * itemsPerPage);
}, [filteredProjects, page]);
// Handle Tab Switch
const handleTabSwitch = (newStatus: 'done' | 'progress') => {
setStatus(newStatus);
setPage(1); // Reset pagination on tab switch
};
// Handle Load More
const handleLoadMore = () => {
setIsLoading(true);
// Brief loading state
setTimeout(() => {
setPage((prev) => prev + 1);
setIsLoading(false);
}, 400);
};
const BASE_IMG_URL = "/seed-media/2026/09/";
// Resolve a project href from a MapProject entry by matching against projectsData slugs
const resolveProjectHref = (mapProject: MapProject): string | undefined => {
const match = projectsData.find(
(p) => p.name.fa === mapProject.name || p.name.en === mapProject.name
);
return match ? `/projects/${match.slug}` : undefined;
};
return (
<>
<PageHero pageKey="projects" />
{/* نقشهٔ محدود‌شده — همهٔ پروژه‌ها با سنجاق مسی */}
<section className="bg-char py-10" aria-label={t('mapAria')}>
<div className="mx-auto max-w-site px-6 lg:px-10">
<div className="w-full max-w-full overflow-hidden rounded-2xl border border-white/10">
<LazyProjectsMapAll
projects={MAP_PROJECTS}
resolveProjectHref={resolveProjectHref}
className="h-[350px] md:h-[450px] lg:h-[55vh] max-h-[600px] w-full"
/>
</div>
</div>
</section>
<section className="bg-ink py-24 lg:py-32" aria-label={t('projectsListAria')}>
<div className="mx-auto max-w-site px-6 lg:px-10">
{/* دکمه‌های فیلتر */}
<div className="flex items-center justify-center gap-4 md:gap-6" role="tablist" aria-label={t('projectFilterAria')}>
{FILTERS.map((filter, index) => {
const isSelected = status === filter.key;
return (
<React.Fragment key={filter.key}>
{index > 0 && (
<span className="h-6 w-[2px] rounded-full bg-ivory/10" aria-hidden="true" />
)}
<button
type="button"
role="tab"
aria-selected={isSelected}
onClick={() => handleTabSwitch(filter.key)}
className={`group relative rounded-lg px-6 py-2.5 text-sm font-medium transition-all duration-300 ease-lux active:scale-95 cursor-pointer select-none backdrop-blur-md ${
isSelected
? 'bg-copper/15 text-copper shadow-[0_4px_15px_rgba(197,143,92,0.1)]'
: 'bg-transparent text-ivory/60 hover:bg-white/5 hover:text-ivory'
}`}
>
<span className="relative z-10 transition-colors duration-300 ease-out">
<InlineEdit
value={filter.label}
onSave={(v) => saveStatusLabel(filter.key, v)}
/>
</span>
</button>
</React.Fragment>
);
})}
</div>
<div className="mt-12 grid gap-6 lg:grid-cols-3">
{displayedProjects.map((project, index) => {
const dummyImageName = `${(index % 6) + 1}.png`;
const imageUrl = `${BASE_IMG_URL}${dummyImageName}`;
return (
<Reveal key={project.id} delay={0.05 * (index % itemsPerPage)} className="h-full">
<ProjectCard
project={project}
imageUrl={imageUrl}
detailHref={`/projects/${project.slug}`}
/>
</Reveal>
);
})}
</div>
{/* Load More Button */}
{displayedProjects.length < filteredProjects.length && (
<div className="mt-16 flex justify-center">
<PremiumButton
onClick={handleLoadMore}
disabled={isLoading}
className="px-10 py-4 w-48"
>
{isLoading ? 'در حال بارگذاری...' : 'مشاهده بیشتر'}
</PremiumButton>
</div>
)}
</div>
</section>
<Gallery />
<ContactSection />
</>
);
}