Files
pos-system/apps/client-example/docs/FILE_GRID_PAGINATION_EXAMPLE.md

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

  1. Better UX

    • Không load tất cả files cùng lúc
    • Faster initial page load
    • Smooth navigation
  2. Performance

    • Reduced DOM nodes
    • Faster rendering
    • Lower memory usage
  3. Scalability

    • Handles thousands of files
    • Server-side ready
    • Caching support
  4. Accessibility

    • Keyboard navigation
    • Screen reader friendly
    • Clear visual feedback

🧪 Testing Guide

Test Cases:

  1. Basic Navigation

    • Click next → goes to page 2
    • Click previous → goes to page 1
    • Click page number → goes to that page
  2. Edge Cases

    • First page: previous button disabled
    • Last page: next button disabled
    • Single page: no pagination shown
    • Empty files: no pagination
  3. Responsive

    • Desktop: full pagination UI
    • Mobile: simplified "X / Y" display
    • Tablet: medium view
  4. Loading State

    • Controls disabled during loading
    • Visual feedback shown
  5. Integration

    • Works with search
    • Works with sorting
    • Works with folder navigation
    • Resets to page 1 on filter change