Spaces:
Sleeping
Sleeping
| import { useState, useRef } from 'react'; | |
| import MovieCard from './MovieCard'; | |
| import Pagination from './Pagination'; | |
| export default function PaginatedGrid({ initialData, category, title, query, topic, country, type }) { | |
| const [data, setData] = useState(initialData); | |
| const [loading, setLoading] = useState(false); | |
| const containerRef = useRef(null); | |
| const moviesList = data?.items || []; | |
| const currentPage = data?.pagination?.currentPage || 1; | |
| const totalPages = data?.pagination?.totalPages || 1; | |
| // Xây dựng đường dẫn cơ sở basePath động chuẩn chỉnh cho chuyển trang | |
| let basePath = ''; | |
| if (typeof window !== 'undefined') { | |
| const url = new URL(window.location.href); | |
| url.searchParams.delete('page'); | |
| // Giữ nguyên các tham số tìm kiếm khác (như q) nếu có | |
| basePath = url.pathname + url.search; | |
| } else { | |
| // Giải pháp dự phòng khi Render trên máy chủ (SSR Fallback) | |
| if (query) { | |
| basePath = `/tim-kiem?q=${encodeURIComponent(query)}`; | |
| } else if (topic) { | |
| basePath = `/chu-de/${topic}`; | |
| } else { | |
| basePath = category ? `/the-loai/${category}` : ''; | |
| } | |
| } | |
| const handlePageChange = async (pageNum) => { | |
| if (pageNum === currentPage || loading) return; | |
| setLoading(true); | |
| // Cuộn mượt mà lên đầu danh sách/bảng phim với offset hợp lý | |
| if (containerRef.current) { | |
| containerRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
| } | |
| try { | |
| let apiUrl = `/api/movies?page=${pageNum}`; | |
| if (category) apiUrl += `&category=${encodeURIComponent(category)}`; | |
| if (topic) apiUrl += `&topic=${encodeURIComponent(topic)}`; | |
| if (country) apiUrl += `&country=${encodeURIComponent(country)}`; | |
| if (query) apiUrl += `&q=${encodeURIComponent(query)}`; | |
| if (type) apiUrl += `&type=${encodeURIComponent(type)}`; | |
| const res = await fetch(apiUrl); | |
| if (!res.ok) throw new Error(`HTTP error ${res.status}`); | |
| const json = await res.json(); | |
| // Giãn cách một chút (300ms) để hiệu ứng chuyển trang trông premium, mượt mà và trực quan | |
| await new Promise((resolve) => setTimeout(resolve, 300)); | |
| setData(json); | |
| // Cập nhật địa chỉ URL trình duyệt mà không gây tải lại toàn bộ trang | |
| const newUrl = new URL(window.location.href); | |
| newUrl.searchParams.set('page', String(pageNum)); | |
| window.history.pushState({}, '', newUrl.toString()); | |
| } catch (err) { | |
| console.error('Lỗi khi tải trang mới:', err); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| return ( | |
| <div ref={containerRef} className="space-y-12 relative scroll-mt-28"> | |
| {/* Hiệu ứng phủ kính Glassmorphism Loading Siêu Sang Trọng */} | |
| {loading && ( | |
| <div className="absolute inset-0 z-50 bg-black/40 backdrop-blur-[2px] rounded-3xl flex items-center justify-center transition-all duration-300 animate-fade-in"> | |
| <div className="flex flex-col items-center gap-3 bg-[#12141d]/85 border border-white/10 rounded-2xl p-6 shadow-2xl"> | |
| <div className="h-10 w-10 border-4 border-primary/20 border-t-primary rounded-full animate-spin"></div> | |
| <span className="text-xs font-black uppercase tracking-widest text-primary animate-pulse">Đang cập nhật...</span> | |
| </div> | |
| </div> | |
| )} | |
| {moviesList.length === 0 ? ( | |
| <div className="py-20 text-center text-gray-500 font-bold uppercase tracking-widest text-xs"> | |
| Không tìm thấy phim nào | |
| </div> | |
| ) : ( | |
| <> | |
| {/* Grid hiển thị danh sách phim hiện tại */} | |
| <div className={`grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 transition-all duration-300 ${loading ? 'opacity-30 blur-[1px]' : 'opacity-100'}`}> | |
| {moviesList.map((movie) => ( | |
| <MovieCard key={movie._id || movie.slug} movie={movie} /> | |
| ))} | |
| </div> | |
| {/* Phân trang tiêu chuẩn cao cấp bằng AJAX */} | |
| <Pagination | |
| currentPage={currentPage} | |
| totalPages={totalPages} | |
| basePath={basePath} | |
| onPageChange={handlePageChange} | |
| /> | |
| </> | |
| )} | |
| </div> | |
| ); | |
| } | |