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

15 KiB

File Grid Navigation Documentation

📊 Tổng Quan

Date: October 15, 2025
Status: Completed
Component: FileGrid + FileGridNavigation

🎯 Mục Đích

Thêm pagination/navigation controls vào FileGrid component để hiển thị và điều hướng qua nhiều trang files.

📁 Components

1. FileGridNavigation.tsx

Purpose: Pagination controls với page numbers và navigation buttons

Features:

  • Previous/Next buttons
  • Page number buttons (smart ellipsis)
  • Total items display
  • Jump to page input (desktop)
  • Responsive design (mobile-friendly)
  • Loading state support

Props:

interface FileGridNavigationProps {
  currentPage: number;        // Trang hiện tại (1-based)
  totalPages: number;          // Tổng số trang
  totalItems: number;          // Tổng số items
  itemsPerPage: number;        // Số items mỗi trang
  onPageChange: (page: number) => void;  // Page change handler
  loading?: boolean;           // Loading state
  className?: string;          // Custom CSS classes
}

Lines: ~165 lines

2. FileGrid.tsx (Updated)

Purpose: Display files với optional pagination

New Props:

interface FileGridProps {
  // ... existing props ...
  
  // Pagination props (optional)
  showPagination?: boolean;    // Enable/disable pagination
  currentPage?: number;        // Current page number
  totalPages?: number;         // Total pages
  totalItems?: number;         // Total items count
  itemsPerPage?: number;       // Items per page
  onPageChange?: (page: number) => void;  // Page change handler
  loading?: boolean;           // Loading state
}

💡 Usage Examples

Example 1: Basic Usage (No Pagination)

import { FileGrid } from '@/components/storage';

function MyStoragePage() {
  const [files, setFiles] = useState<FileResponse[]>([]);
  
  return (
    <FileGrid
      files={files}
      viewMode="grid"
      onFilePreview={(file) => console.log('Preview:', file)}
      // No pagination props = no pagination UI
    />
  );
}

Example 2: With Pagination (Client-Side)

import { FileGrid } from '@/components/storage';
import { useState, useMemo } from 'react';

function MyStoragePageWithPagination() {
  const [allFiles, setAllFiles] = useState<FileResponse[]>([]);
  const [currentPage, setCurrentPage] = useState(1);
  const itemsPerPage = 20;
  
  // Calculate pagination
  const totalPages = Math.ceil(allFiles.length / itemsPerPage);
  const paginatedFiles = useMemo(() => {
    const startIndex = (currentPage - 1) * itemsPerPage;
    const endIndex = startIndex + itemsPerPage;
    return allFiles.slice(startIndex, endIndex);
  }, [allFiles, currentPage, itemsPerPage]);
  
  return (
    <FileGrid
      files={paginatedFiles}
      viewMode="grid"
      showPagination={true}
      currentPage={currentPage}
      totalPages={totalPages}
      totalItems={allFiles.length}
      itemsPerPage={itemsPerPage}
      onPageChange={setCurrentPage}
      onFilePreview={(file) => console.log('Preview:', file)}
    />
  );
}

Example 3: With Pagination (Server-Side)

import { FileGrid } from '@/components/storage';
import { useState, useEffect } from 'react';

function MyServerPaginatedStorage() {
  const [files, setFiles] = useState<FileResponse[]>([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [totalItems, setTotalItems] = useState(0);
  const [loading, setLoading] = useState(false);
  const itemsPerPage = 20;
  
  const fetchFiles = async (page: number) => {
    setLoading(true);
    try {
      const response = await storageService.getFiles({
        page,
        limit: itemsPerPage
      });
      
      if (response.success) {
        setFiles(response.data.files);
        setTotalItems(response.data.meta.totalItems);
      }
    } catch (error) {
      console.error('Failed to fetch files:', error);
    } finally {
      setLoading(false);
    }
  };
  
  useEffect(() => {
    fetchFiles(currentPage);
  }, [currentPage]);
  
  const handlePageChange = (page: number) => {
    setCurrentPage(page);
    // Scroll to top when page changes
    window.scrollTo({ top: 0, behavior: 'smooth' });
  };
  
  const totalPages = Math.ceil(totalItems / itemsPerPage);
  
  return (
    <FileGrid
      files={files}
      viewMode="list"
      showPagination={true}
      currentPage={currentPage}
      totalPages={totalPages}
      totalItems={totalItems}
      itemsPerPage={itemsPerPage}
      onPageChange={handlePageChange}
      loading={loading}
      onFilePreview={(file) => console.log('Preview:', file)}
    />
  );
}

Example 4: Complete Integration with StoragePageContent

// In your storage page
import { useState, useCallback } from 'react';
import { StoragePageContent } from '@/components/storage';

function StoragePage() {
  const [files, setFiles] = useState<FileResponse[]>([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [totalItems, setTotalItems] = useState(0);
  const itemsPerPage = 24; // 4x6 grid
  
  // Fetch files with pagination
  const fetchFiles = useCallback(async (page: number) => {
    const response = await storageService.getFiles({
      page,
      limit: itemsPerPage,
      folderId: currentFolder?.id
    });
    
    if (response.success) {
      setFiles(response.data.files);
      setTotalItems(response.data.meta.totalItems);
    }
  }, [currentFolder]);
  
  useEffect(() => {
    fetchFiles(currentPage);
  }, [currentPage, fetchFiles]);
  
  return (
    <StoragePageContent
      {...otherProps}
      filteredFiles={files}
      // Pass pagination props to FileGrid via StoragePageContent
      showPagination={true}
      currentPage={currentPage}
      totalPages={Math.ceil(totalItems / itemsPerPage)}
      totalItems={totalItems}
      itemsPerPage={itemsPerPage}
      onPageChange={setCurrentPage}
    />
  );
}

🎨 Navigation Features

1. Smart Page Numbers

  • Shows up to 7 page buttons
  • Uses ellipsis (...) for large page ranges
  • Always shows first and last page
  • Shows pages around current page

Example displays:

  • 5 pages: 1 2 3 4 5
  • 10 pages, current=1: 1 2 3 ... 10
  • 10 pages, current=5: 1 ... 4 5 6 ... 10
  • 10 pages, current=10: 1 ... 8 9 10

2. Responsive Design

  • Desktop: Full pagination with page numbers
  • Mobile: Simple "Page X / Y" indicator
  • Tablet: Medium view with essential controls

3. Accessibility

  • Keyboard navigation support
  • Disabled state for loading
  • Clear visual feedback
  • ARIA labels for screen readers

4. Loading State

  • Disabled controls during loading
  • Visual feedback
  • Prevents double-clicks

🔧 Integration Steps

Step 1: Update FileGrid Props

<FileGrid
  files={paginatedFiles}
  // ... other props ...
  
  // Add pagination props
  showPagination={true}
  currentPage={currentPage}
  totalPages={totalPages}
  totalItems={totalItems}
  itemsPerPage={itemsPerPage}
  onPageChange={handlePageChange}
  loading={loading}
/>

Step 2: Implement Page Change Handler

const handlePageChange = (newPage: number) => {
  // Update current page
  setCurrentPage(newPage);
  
  // Optional: Scroll to top
  window.scrollTo({ top: 0, behavior: 'smooth' });
  
  // Optional: Fetch new data (server-side)
  fetchFiles(newPage);
};

Step 3: Calculate Pagination (Client-Side)

const itemsPerPage = 20;
const totalPages = Math.ceil(allFiles.length / itemsPerPage);

const paginatedFiles = useMemo(() => {
  const start = (currentPage - 1) * itemsPerPage;
  const end = start + itemsPerPage;
  return allFiles.slice(start, end);
}, [allFiles, currentPage, itemsPerPage]);

📊 Pagination Strategies

1. Client-Side Pagination

Pros:

  • Fast page switching
  • No server requests
  • Works offline

Cons:

  • All data loaded upfront
  • Memory intensive for large datasets

Best for:

  • < 1000 files
  • Fast local filtering
  • Offline-first apps

Implementation:

const paginatedFiles = allFiles.slice(
  (page - 1) * itemsPerPage,
  page * itemsPerPage
);

2. Server-Side Pagination

Pros:

  • Memory efficient
  • Handles large datasets
  • Real-time data

Cons:

  • Network requests per page
  • Slower page switching

Best for:

  • 1000 files

  • Real-time updates
  • Large file libraries

Implementation:

const fetchFiles = async (page: number) => {
  const response = await api.getFiles({
    page,
    limit: itemsPerPage
  });
  return response.data;
};

3. Hybrid Pagination

Pros:

  • Best of both worlds
  • Smart caching
  • Good UX

Implementation:

const [cache, setCache] = useState<Map<number, FileResponse[]>>(new Map());

const fetchPage = async (page: number) => {
  if (cache.has(page)) {
    return cache.get(page)!;
  }
  
  const data = await api.getFiles({ page, limit: itemsPerPage });
  setCache(prev => new Map(prev).set(page, data));
  return data;
};

🎛️ Customization Options

Items Per Page Options

const ITEMS_PER_PAGE_OPTIONS = [12, 24, 48, 96];

function MyStorage() {
  const [itemsPerPage, setItemsPerPage] = useState(24);
  
  return (
    <>
      <select value={itemsPerPage} onChange={(e) => setItemsPerPage(+e.target.value)}>
        {ITEMS_PER_PAGE_OPTIONS.map(option => (
          <option key={option} value={option}>{option} per page</option>
        ))}
      </select>
      
      <FileGrid
        files={files}
        showPagination={true}
        itemsPerPage={itemsPerPage}
        {...otherProps}
      />
    </>
  );
}

Custom Navigation Style

<FileGridNavigation
  {...paginationProps}
  className="mt-6 sticky bottom-0 shadow-lg"
/>

🚀 Performance Tips

1. Optimize Re-renders

const paginatedFiles = useMemo(() => {
  return files.slice((page - 1) * limit, page * limit);
}, [files, page, limit]);

2. Debounce Page Changes

const debouncedPageChange = useMemo(
  () => debounce((page: number) => {
    setCurrentPage(page);
  }, 300),
  []
);

3. Virtual Scrolling (Alternative)

For very large datasets, consider virtual scrolling instead:

import { FixedSizeGrid } from 'react-window';

<FixedSizeGrid
  columnCount={6}
  rowCount={Math.ceil(files.length / 6)}
  columnWidth={200}
  rowHeight={200}
  height={600}
  width={1200}
>
  {({ columnIndex, rowIndex, style }) => (
    <div style={style}>
      <FileItem file={files[rowIndex * 6 + columnIndex]} />
    </div>
  )}
</FixedSizeGrid>

📱 Mobile Optimizations

Touch-Friendly Buttons

  • Minimum 44x44px touch targets
  • Adequate spacing between buttons
  • Clear visual feedback

Simplified Mobile View

  • Shows "Page X / Y" instead of all page numbers
  • Larger prev/next buttons
  • Swipe gestures support (future enhancement)

🔄 State Management

function StorageWithPagination() {
  // Pagination state
  const [currentPage, setCurrentPage] = useState(1);
  const [itemsPerPage, setItemsPerPage] = useState(24);
  
  // Data state
  const [allFiles, setAllFiles] = useState<FileResponse[]>([]);
  const [loading, setLoading] = useState(false);
  
  // Computed values
  const totalPages = Math.ceil(allFiles.length / itemsPerPage);
  const paginatedFiles = useMemo(() => {
    const start = (currentPage - 1) * itemsPerPage;
    return allFiles.slice(start, start + itemsPerPage);
  }, [allFiles, currentPage, itemsPerPage]);
  
  // Reset to page 1 when filters change
  useEffect(() => {
    setCurrentPage(1);
  }, [searchQuery, sortBy, folderId]);
  
  return (
    <FileGrid
      files={paginatedFiles}
      showPagination={allFiles.length > itemsPerPage}
      currentPage={currentPage}
      totalPages={totalPages}
      totalItems={allFiles.length}
      itemsPerPage={itemsPerPage}
      onPageChange={setCurrentPage}
      loading={loading}
    />
  );
}

🎨 UI/UX Highlights

Visual States

  1. Active Page

    • Blue background
    • White text
    • Clear visual distinction
  2. Hover State

    • Gray background
    • Smooth transition
    • Cursor pointer
  3. Disabled State

    • Gray text
    • No hover effect
    • Cursor not-allowed
  4. Loading State

    • Disabled controls
    • Reduced opacity
    • Loading indicator (if needed)

Smart Features

  1. Automatic Ellipsis

    • Shows ... for skipped pages
    • Always visible: first, last, and nearby pages
  2. Jump to Page (Desktop)

    • Quick navigation to specific page
    • Input validation
    • Only shows for 5+ pages
  3. Responsive Text

    • Desktop: "Hiển thị 1 - 20 trong tổng số 150 files"
    • Mobile: "1 / 8"

🧪 Testing Checklist

  • Component renders without pagination
  • Component renders with pagination
  • Previous button works
  • Next button works
  • Page number buttons work
  • Ellipsis appears correctly
  • Jump to page works (desktop)
  • Mobile view displays correctly
  • Loading state disables controls
  • First/last page edge cases handled
  • User testing in browser (pending)
  • client/src/components/storage/FileGrid.tsx - Main grid component
  • client/src/components/storage/FileGridNavigation.tsx - Navigation component
  • client/src/components/storage/index.ts - Exports
  • client/src/app/[locale]/dashboard/storage/page.tsx - Usage example

🔍 Code Quality

Metric Value
TypeScript Fully typed
Linter No errors
Accessibility ARIA labels
Responsive Mobile-friendly
Performance Optimized

🎯 Future Enhancements

  1. URL State Sync

    const searchParams = useSearchParams();
    const page = Number(searchParams.get('page')) || 1;
    
    const handlePageChange = (newPage: number) => {
      router.push(`?page=${newPage}`);
    };
    
  2. Keyboard Navigation

    useEffect(() => {
      const handleKey = (e: KeyboardEvent) => {
        if (e.key === 'ArrowLeft') handlePageChange(currentPage - 1);
        if (e.key === 'ArrowRight') handlePageChange(currentPage + 1);
      };
      window.addEventListener('keydown', handleKey);
      return () => window.removeEventListener('keydown', handleKey);
    }, [currentPage]);
    
  3. Infinite Scroll Option

    const { ref, inView } = useInView();
    
    useEffect(() => {
      if (inView && hasMore && !loading) {
        loadMore();
      }
    }, [inView, hasMore, loading]);
    
  4. Per-Page Selector

    <select onChange={(e) => setItemsPerPage(+e.target.value)}>
      <option value={12}>12 per page</option>
      <option value={24}>24 per page</option>
      <option value={48}>48 per page</option>
    </select>
    

📅 Change Log

  • 2025-10-15 - Initial implementation
    • Created FileGridNavigation component
    • Updated FileGrid with pagination support
    • Added comprehensive documentation
    • No linter errors
    • No TypeScript errors
    • Fully responsive design
    • Smart ellipsis algorithm
    • Jump to page feature (desktop)