feat(ui): update UI v1
This commit is contained in:
@@ -1,18 +1,20 @@
|
||||
import React, { useState } from 'react';
|
||||
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 type { ProjectStatus } from '../data/site';
|
||||
import { useSite, useStatusLabel } from '../api';
|
||||
import { useLang } from '../i18n/lang';
|
||||
import { useFieldSaver } from '../admin/AdminContext';
|
||||
import { InlineEdit } from '../components/admin/InlineEdit';
|
||||
import { AddItemButton, EditableItem } from '../admin/Editable';
|
||||
import { projectsData } from '../data/projectsData';
|
||||
import { LazyProjectsMapAll } from '../map/LazyMaps';
|
||||
import { MAP_PROJECTS, type MapProject } from '../data/mapProjects';
|
||||
|
||||
export function Projects() {
|
||||
const { projects, status_labels } = useSite();
|
||||
const { status_labels } = useSite();
|
||||
const { t } = useLang();
|
||||
const save = useFieldSaver();
|
||||
|
||||
@@ -27,27 +29,79 @@ export function Projects() {
|
||||
|
||||
const doneLabel = useStatusLabel('done');
|
||||
const progressLabel = useStatusLabel('progress');
|
||||
const FILTERS: {key: ProjectStatus;label: string;}[] = [
|
||||
const FILTERS: { key: 'done' | 'progress'; label: string; }[] = [
|
||||
{ key: 'done', label: doneLabel },
|
||||
{ key: 'progress', label: progressLabel }
|
||||
];
|
||||
const [status, setStatus] = useState<ProjectStatus>('done');
|
||||
const list = projects.filter((p) => p.status === status);
|
||||
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" />
|
||||
)}
|
||||
@@ -56,7 +110,7 @@ export function Projects() {
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={isSelected}
|
||||
onClick={() => setStatus(filter.key)}
|
||||
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)]'
|
||||
@@ -76,15 +130,34 @@ export function Projects() {
|
||||
</div>
|
||||
|
||||
<div className="mt-12 grid gap-6 lg:grid-cols-3">
|
||||
{list.map((project, i) => (
|
||||
<Reveal key={project.id} delay={0.05 * i} className="h-full">
|
||||
<EditableItem resource="projects" item={project} className="h-full">
|
||||
<ProjectCard project={project} imageClass="h-80" />
|
||||
</EditableItem>
|
||||
</Reveal>
|
||||
))}
|
||||
<AddItemButton resource="projects" />
|
||||
{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>
|
||||
|
||||
@@ -92,4 +165,4 @@ export function Projects() {
|
||||
<ContactSection />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user