10 KiB
10 KiB
File Grid Pagination - Example Implementation
🎯 Ví Dụ Thực Tế
Example 1: Update Storage Page với Client-Side Pagination
// client/src/app/[locale]/dashboard/storage/page.tsx
'use client';
import React, { useState, useCallback, useEffect, useMemo } from 'react';
import { useTranslations } from 'next-intl';
import { useStorage } from '@/hooks/useStorage';
import { FileResponse, FolderResponse } from '@/types/storage';
import {
buildBreadcrumbs,
filterAndSortFiles,
filterFolders,
StoragePageHeader,
StoragePageSidebar,
StoragePageContent,
StoragePageModals
} from '@/components/storage';
export default function StoragePage() {
const t = useTranslations('Storage');
const {
files,
folders,
currentFolder,
quota,
loading,
error,
// ... other hooks
} = useStorage();
// ============================================================================
// PAGINATION STATE (NEW)
// ============================================================================
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(24); // 4x6 grid
// Reset to page 1 when filters change
useEffect(() => {
setCurrentPage(1);
}, [searchQuery, sortBy, currentFolder]);
// ============================================================================
// COMPUTE PAGINATED DATA
// ============================================================================
// Filter and sort files first
const filteredFiles = filterAndSortFiles(
files,
searchQuery,
sortBy,
sortOrder,
isAdvancedSearchActive,
advancedSearchResults
);
// Calculate pagination
const totalPages = Math.ceil(filteredFiles.length / itemsPerPage);
const totalItems = filteredFiles.length;
// Get current page files
const paginatedFiles = useMemo(() => {
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
return filteredFiles.slice(startIndex, endIndex);
}, [filteredFiles, currentPage, itemsPerPage]);
// ============================================================================
// PAGE CHANGE HANDLER
// ============================================================================
const handlePageChange = useCallback((newPage: number) => {
setCurrentPage(newPage);
// Optional: Scroll to top when page changes
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}, []);
// ============================================================================
// RENDER
// ============================================================================
return (
<>
<StoragePageHeader {...headerProps} />
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div className="flex gap-6">
<StoragePageSidebar {...sidebarProps} />
<div className="flex-1 min-w-0">
<StoragePageContent
breadcrumbs={buildBreadcrumbs(currentFolder, folders, t('root'))}
onBreadcrumbClick={navigateToFolder}
searchQuery={searchQuery}
onSearchQueryChange={setSearchQuery}
sortBy={sortBy}
onSortByChange={setSortBy}
sortOrder={sortOrder}
onSortOrderToggle={() => setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc')}
onShowAdvancedSearch={() => setShowAdvancedSearch(true)}
onShowDuplicateManager={() => setShowDuplicateManager(true)}
isAdvancedSearchActive={isAdvancedSearchActive}
advancedSearchResults={advancedSearchResults}
files={files}
folders={folders}
currentFolder={currentFolder}
filteredFiles={paginatedFiles} // ← Use paginated files
filteredFolders={filterFolders(folders, searchQuery)}
onClearAdvancedSearch={() => {
setIsAdvancedSearchActive(false);
setAdvancedSearchResults([]);
}}
onClearSearch={() => setSearchQuery('')}
onUploadComplete={handleUploadComplete}
selectedFiles={selectedFiles}
onSelectionChange={setSelectedFiles}
onSelectionClear={() => setSelectedFiles([])}
onOperationComplete={refreshData}
viewMode={viewMode}
onFileDelete={handleFileDelete}
onFileShare={handleFileShare}
onFilePreview={handleFilePreview}
onFileVersioning={handleFileVersioning}
loading={loading}
error={error}
// ← NEW: Pagination props
showPagination={filteredFiles.length > itemsPerPage}
currentPage={currentPage}
totalPages={totalPages}
totalItems={totalItems}
itemsPerPage={itemsPerPage}
onPageChange={handlePageChange}
t={t}
/>
</div>
</div>
</div>
<StoragePageModals {...modalsProps} />
</>
);
}
📊 Kết Quả
Với 150 files, itemsPerPage = 24:
- Page 1: Files 1-24
- Page 2: Files 25-48
- Page 3: Files 49-72
- ...
- Page 7: Files 145-150
Navigation hiển thị:
[←] 1 2 3 ... 7 [→] (Page 1)
[←] 1 2 3 4 ... 7 [→] (Page 2)
[←] 1 ... 3 4 5 ... 7 [→] (Page 4)
[←] 1 ... 5 6 7 [→] (Page 7)
🎛️ Tuỳ Chỉnh Items Per Page
// Add items per page selector
function StoragePageHeader() {
return (
<div className="flex items-center space-x-4">
{/* Existing controls */}
{/* Items per page selector */}
<select
value={itemsPerPage}
onChange={(e) => {
setItemsPerPage(Number(e.target.value));
setCurrentPage(1); // Reset to page 1
}}
className="px-3 py-2 border rounded-lg text-sm"
>
<option value={12}>12 per page</option>
<option value={24}>24 per page</option>
<option value={48}>48 per page</option>
<option value={96}>96 per page</option>
</select>
</div>
);
}
🔄 Server-Side Pagination Example
export default function StoragePage() {
const [files, setFiles] = useState<FileResponse[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalItems, setTotalItems] = useState(0);
const [loading, setLoading] = useState(false);
const itemsPerPage = 24;
// Fetch files with pagination from server
const fetchFiles = useCallback(async (page: number) => {
setLoading(true);
try {
const { StorageService } = await import('@/lib/storage.service');
const storageService = new StorageService();
const result = await storageService.getFiles({
page,
limit: itemsPerPage,
folderId: currentFolder?.id,
sortBy,
sortOrder
});
if (result.success && result.data) {
setFiles(result.data.files);
setTotalItems(result.data.meta.totalItems);
}
} catch (error) {
console.error('Failed to fetch files:', error);
} finally {
setLoading(false);
}
}, [currentFolder, sortBy, sortOrder, itemsPerPage]);
// Load files when page or filters change
useEffect(() => {
fetchFiles(currentPage);
}, [currentPage, fetchFiles]);
// Handle page change
const handlePageChange = useCallback((newPage: number) => {
setCurrentPage(newPage);
window.scrollTo({ top: 0, behavior: 'smooth' });
}, []);
const totalPages = Math.ceil(totalItems / itemsPerPage);
return (
<StoragePageContent
{...otherProps}
filteredFiles={files} // Already filtered by server
showPagination={true}
currentPage={currentPage}
totalPages={totalPages}
totalItems={totalItems}
itemsPerPage={itemsPerPage}
onPageChange={handlePageChange}
loading={loading}
t={t}
/>
);
}
🎨 Visual Preview
┌─────────────────────────────────────────────────────────┐
│ [File 1] [File 2] [File 3] [File 4] [File 5] [File 6] │
│ [File 7] [File 8] [File 9] [File 10] [File 11] [...] │
│ [File 13] [File 14] [File 15] [File 16] [File 17] ... │
│ [File 19] [File 20] [File 21] [File 22] [File 23] ... │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Hiển thị 1 - 24 trong tổng số 150 files │
│ │
│ [←] 1 [2] 3 4 ... 7 [→] [Đi đến: 1] │
└─────────────────────────────────────────────────────────┘
✅ Benefits
-
Better UX
- Không load tất cả files cùng lúc
- Faster initial page load
- Smooth navigation
-
Performance
- Reduced DOM nodes
- Faster rendering
- Lower memory usage
-
Scalability
- Handles thousands of files
- Server-side ready
- Caching support
-
Accessibility
- Keyboard navigation
- Screen reader friendly
- Clear visual feedback
🧪 Testing Guide
Test Cases:
-
Basic Navigation
- Click next → goes to page 2
- Click previous → goes to page 1
- Click page number → goes to that page
-
Edge Cases
- First page: previous button disabled
- Last page: next button disabled
- Single page: no pagination shown
- Empty files: no pagination
-
Responsive
- Desktop: full pagination UI
- Mobile: simplified "X / Y" display
- Tablet: medium view
-
Loading State
- Controls disabled during loading
- Visual feedback shown
-
Integration
- Works with search
- Works with sorting
- Works with folder navigation
- Resets to page 1 on filter change