From dbf9d63f392f4170cfe782ee9ceda947756876d6 Mon Sep 17 00:00:00 2001 From: alireza Date: Tue, 25 Aug 2026 15:48:02 +0330 Subject: [PATCH] feat: add paginated slider carousel for Technogen analytical notes --- .../pages/Home/sections/TechnogenSection.tsx | 352 +++++++++++------- 1 file changed, 220 insertions(+), 132 deletions(-) diff --git a/frontend/src/pages/Home/sections/TechnogenSection.tsx b/frontend/src/pages/Home/sections/TechnogenSection.tsx index f1d1e69..fcd237a 100644 --- a/frontend/src/pages/Home/sections/TechnogenSection.tsx +++ b/frontend/src/pages/Home/sections/TechnogenSection.tsx @@ -1,18 +1,23 @@ import { useState, useEffect } from 'react' import { motion, AnimatePresence } from 'framer-motion' -import { Download, FileText, ExternalLink, X, Eye, BookOpen } from 'lucide-react' +import { Download, FileText, ExternalLink, X, Eye, BookOpen, ChevronLeft, ChevronRight } from 'lucide-react' import { useLang } from '@/context/LangContext' import { FALLBACK_TECHNOGEN_REPORTS, type TechnogenReport } from '@/data/technogen' const NAVY = '#032340' const GOLD = '#CD9E53' const EASE = [0.22, 1, 0.36, 1] as const +const PAGE_SIZE = 3 + +const faNum = (n: number | string) => String(n).replace(/\d/g, (d) => '۰۱۲۳۴۵۶۷۸۹'[+d]) export default function TechnogenSection() { const { lang } = useLang() const isFa = lang === 'fa' const [reports, setReports] = useState(FALLBACK_TECHNOGEN_REPORTS) const [selectedReport, setSelectedReport] = useState(null) + const [page, setPage] = useState(0) + const [direction, setDirection] = useState(0) useEffect(() => { let unmounted = false @@ -36,6 +41,19 @@ export default function TechnogenSection() { const featured = reports.find((r) => r.isFeatured) || reports[0] const analyticalNotes = reports.filter((r) => r.id !== featured?.id) + const totalPages = Math.max(1, Math.ceil(analyticalNotes.length / PAGE_SIZE)) + const currentNotes = analyticalNotes.slice(page * PAGE_SIZE, page * PAGE_SIZE + PAGE_SIZE) + + const goNext = () => { + setDirection(1) + setPage((p) => (p + 1) % totalPages) + } + + const goPrev = () => { + setDirection(-1) + setPage((p) => (p - 1 + totalPages) % totalPages) + } + const openPreview = (report: TechnogenReport) => { setSelectedReport(report) } @@ -314,162 +332,232 @@ export default function TechnogenSection() { )} - {/* ── Bento Cards 2, 3, 4: Analytical Notes (Spans 5 columns) ── */} + {/* ── Bento Cards: Analytical Notes Slider (Spans 5 columns) ── */}
- {analyticalNotes.map((note, idx) => ( - 1 && ( +
- {/* Mini Cover Thumbnail */} -
openPreview(note)} - style={{ - flex: '0 0 54px', - width: 54, - aspectRatio: '1 / 1.414', - borderRadius: 6, - overflow: 'hidden', - boxShadow: '0 6px 14px rgba(0,0,0,0.35)', - border: '1px solid rgba(255,255,255,0.15)', - background: NAVY, - cursor: 'pointer', - }} - > - {isFa +
+ {isFa ? 'سایر یادداشت‌های تحلیلی' : 'Analytical Notes'}
- - {/* Content */} -
-
- - {isFa ? (note.tagFa || 'یادداشت تحلیلی') : (note.tagEn || 'Analytical Note')} - -
- -

openPreview(note)} - style={{ - fontSize: 13, - fontWeight: 800, - color: '#FFFFFF', - lineHeight: 1.4, - margin: '0 0 2px', - cursor: 'pointer', - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - }} - title={isFa ? note.titleFa : (note.titleEn || note.titleFa)} - > - {isFa ? note.titleFa : (note.titleEn || note.titleFa)} -

- - {note.subtitleFa && ( -

- {isFa ? note.subtitleFa : (note.subtitleEn || note.subtitleFa)} -

- )} -
- - {/* Quick Action Buttons */} -
- {note.fileUrl && ( - - - - )} - +
+ + {isFa ? `${faNum(page + 1)} از ${faNum(totalPages)}` : `${page + 1} of ${totalPages}`} + +
- - ))} +
+ )} + + {/* Slider Content Frame */} +
+ + + {currentNotes.map((note) => ( +
+ {/* Mini Cover Thumbnail */} +
openPreview(note)} + style={{ + flex: '0 0 54px', + width: 54, + aspectRatio: '1 / 1.414', + borderRadius: 6, + overflow: 'hidden', + boxShadow: '0 6px 14px rgba(0,0,0,0.35)', + border: '1px solid rgba(255,255,255,0.15)', + background: NAVY, + cursor: 'pointer', + }} + > + {isFa +
+ + {/* Content */} +
+
+ + {isFa ? (note.tagFa || 'یادداشت تحلیلی') : (note.tagEn || 'Analytical Note')} + +
+ +

openPreview(note)} + style={{ + fontSize: 13, + fontWeight: 800, + color: '#FFFFFF', + lineHeight: 1.4, + margin: '0 0 2px', + cursor: 'pointer', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }} + title={isFa ? note.titleFa : (note.titleEn || note.titleFa)} + > + {isFa ? note.titleFa : (note.titleEn || note.titleFa)} +

+ + {note.subtitleFa && ( +

+ {isFa ? note.subtitleFa : (note.subtitleEn || note.subtitleFa)} +

+ )} +
+ + {/* Quick Action Buttons */} +
+ {note.fileUrl && ( + + + + )} + + +
+
+ ))} +
+
+