From b8018676116df73fc95ef408563514d7a9d1487e Mon Sep 17 00:00:00 2001 From: aramesh-site deploy Date: Fri, 9 Oct 2026 16:52:50 +0000 Subject: [PATCH] =?UTF-8?q?feat(projects):=20=D8=B5=D9=81=D8=AD=D9=87?= =?UTF-8?q?=E2=80=8C=D8=A8=D9=86=D8=AF=DB=8C=20=D8=B4=D9=85=D8=A7=D8=B1?= =?UTF-8?q?=D9=87=E2=80=8C=D8=AF=D8=A7=D8=B1=20=D8=A8=D9=87=E2=80=8C=D8=AC?= =?UTF-8?q?=D8=A7=DB=8C=20=C2=AB=D9=85=D8=B4=D8=A7=D9=87=D8=AF=D9=87=20?= =?UTF-8?q?=D8=A8=DB=8C=D8=B4=D8=AA=D8=B1=C2=BB=20=E2=80=94=20=DB=B1=DB=B2?= =?UTF-8?q?=20=D9=BE=D8=B1=D9=88=DA=98=D9=87=20=D8=AF=D8=B1=20=D9=87=D8=B1?= =?UTF-8?q?=20=D8=B5=D9=81=D8=AD=D9=87=D8=8C=20=D8=A8=D8=A7=20=D8=A8=D8=B1?= =?UTF-8?q?=DA=AF=D8=B4=D8=AA=20=D9=86=DA=AF=D8=A7=D9=87=20=D8=A8=D9=87=20?= =?UTF-8?q?=D8=A7=D8=A8=D8=AA=D8=AF=D8=A7=DB=8C=20=D9=81=D9=87=D8=B1=D8=B3?= =?UTF-8?q?=D8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/i18n/dict.ts | 6 +++ frontend/src/pages/Projects.tsx | 93 +++++++++++++++++++++++---------- 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/frontend/src/i18n/dict.ts b/frontend/src/i18n/dict.ts index 9c53c6d..1e5bade 100644 --- a/frontend/src/i18n/dict.ts +++ b/frontend/src/i18n/dict.ts @@ -12,6 +12,9 @@ export const fa = { switchLang: 'English', switchLangAria: 'تغییر زبان به انگلیسی', understood: 'متوجه شدم', + paginationAria: 'صفحه‌بندی پروژه‌ها', + previousPage: 'صفحهٔ قبل', + nextPage: 'صفحهٔ بعد', // هدر و ناوبری mainNav: 'ناوبری اصلی', @@ -141,6 +144,9 @@ export const en: Dict = { switchLang: 'فا', switchLangAria: 'Switch language to Persian (Farsi)', understood: 'Got it', + paginationAria: 'Projects pagination', + previousPage: 'Previous page', + nextPage: 'Next page', mainNav: 'Main navigation', mobileNav: 'Mobile navigation', diff --git a/frontend/src/pages/Projects.tsx b/frontend/src/pages/Projects.tsx index e7e0a37..52d9727 100644 --- a/frontend/src/pages/Projects.tsx +++ b/frontend/src/pages/Projects.tsx @@ -1,8 +1,9 @@ -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { PageHero } from '../components/PageHero'; import { Reveal } from '../components/Reveal'; import { ProjectCard } from '../components/ProjectCard'; -import { PremiumButton } from '../components/PremiumButton'; +import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'; +import { toPersianDigits } from '../utils/digits'; import { ContactSection } from '../components/ContactSection'; import { useSite, useStatusLabel } from '../api'; import { useLang } from '../i18n/lang'; @@ -14,8 +15,10 @@ import { MAP_PROJECTS, type MapProject } from '../data/mapProjects'; export function Projects() { const { status_labels, projects: dbProjects } = useSite(); - const { t } = useLang(); + const { t, lang } = useLang(); const save = useFieldSaver(); + /** لنگر ابتدای فهرست — پس از تغییر صفحه، نگاه کاربر به بالا برمی‌گردد */ + const listTopRef = useRef(null); const saveStatusLabel = (key: string, value: string) => { const label = status_labels.find((s) => s.key === key); @@ -38,9 +41,8 @@ export function Projects() { // فایل استاتیک فقط وقتی دیتابیس خالی است جانشین می‌شود (محتوای قدیمی). const cardProjects = useMemo(() => dbProjects, [dbProjects]); - // Filtering and Pagination State + // صفحه‌بندی شماره‌دار — هر صفحه ۱۲ پروژه const [page, setPage] = useState(1); - const [isLoading, setIsLoading] = useState(false); const itemsPerPage = 12; // Filter projects by status @@ -48,27 +50,32 @@ export function Projects() { return cardProjects.filter((p) => p.status === status); }, [cardProjects, status]); - // Currently displayed projects + // پروژه‌های همین صفحه + const totalPages = Math.max(1, Math.ceil(filteredProjects.length / itemsPerPage)); const displayedProjects = useMemo(() => { - return filteredProjects.slice(0, page * itemsPerPage); + const start = (page - 1) * itemsPerPage; + return filteredProjects.slice(start, start + itemsPerPage); }, [filteredProjects, page]); + // با تغییر فیلتر یا کوتاه‌شدن فهرست، صفحه به بازهٔ معتبر برگردد + useEffect(() => { + if (page > totalPages) setPage(totalPages); + }, [page, totalPages]); + + /** رفتن به صفحه و بازگرداندن نگاه کاربر به ابتدای فهرست */ + const goToPage = (next: number) => { + const target = Math.min(Math.max(1, next), totalPages); + if (target === page) return; + setPage(target); + listTopRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }; + // 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 names — first in the @@ -112,6 +119,8 @@ export function Projects() {
+