From be9da50fc57b44f69e064f4b01047aa00aaddf4c Mon Sep 17 00:00:00 2001 From: "tuan.cna" Date: Fri, 30 May 2025 17:44:47 +0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20Create=20Reusable=20CustomPagina?= =?UTF-8?q?tion=20Component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🎯 Component Architecture: - Created standalone CustomPagination component in src/components/ - Fully reusable across Product and Project Tracker - Props-based configuration for maximum flexibility - Built-in theme detection and switching 🎨 Feature-Rich Implementation: - Compact and full-size modes (compact prop) - Configurable page size options - Show/hide info and page size selector - Custom class names for styling - Loading states and disabled handling 📋 Props Interface: - currentPage, pageSize, totalCount, totalPages - loading, onPageChange, onPageSizeChange - pageSizeOptions, showInfo, showPageSizeSelector - compact, className for customization 🌓 Theme Integration: - Automatic dark/light mode detection - Redux state, localStorage, and document attribute fallbacks - Real-time theme switching with MutationObserver - Force re-render mechanism for theme changes 🔧 Technical Features: - Built-in theme change listeners - Proper event cleanup - Dynamic styling based on theme and compact mode - Hover effects and animations - Professional button styling 🚀 Usage Examples: - Project Tracker: compact={true} for smaller size - Product List: compact={false} for full size - Consistent API across all components - Easy to maintain and extend ✅ Code Quality: - Clean component separation - No code duplication - Proper prop validation - Consistent styling patterns - Reusable across entire application --- src/components/CustomPagination.jsx | 271 ++++++++++++++++++ src/feature-module/inventory/productlist.jsx | 192 ++----------- .../projects/projecttracker.jsx | 252 ++-------------- 3 files changed, 304 insertions(+), 411 deletions(-) create mode 100644 src/components/CustomPagination.jsx diff --git a/src/components/CustomPagination.jsx b/src/components/CustomPagination.jsx new file mode 100644 index 0000000..9d01fd4 --- /dev/null +++ b/src/components/CustomPagination.jsx @@ -0,0 +1,271 @@ +import React, { useState, useEffect } from 'react'; +import { useSelector } from 'react-redux'; + +const CustomPagination = ({ + currentPage = 1, + pageSize = 10, + totalCount = 0, + totalPages = 1, + loading = false, + onPageChange, + onPageSizeChange, + pageSizeOptions = [10, 20, 50, 100], + showInfo = true, + showPageSizeSelector = true, + compact = false, + className = '' +}) => { + // Theme state for force re-render + const [themeKey, setThemeKey] = useState(0); + + // Get theme from Redux and localStorage fallback + const reduxTheme = useSelector((state) => state.theme?.isDarkMode); + const localStorageTheme = localStorage.getItem('colorschema') === 'dark_mode'; + const documentTheme = document.documentElement.getAttribute('data-layout-mode') === 'dark_mode'; + + const isDarkMode = reduxTheme || localStorageTheme || documentTheme; + + // Listen for theme changes + useEffect(() => { + const handleThemeChange = () => { + setThemeKey(prev => prev + 1); + }; + + // Listen for data-layout-mode attribute changes + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'data-layout-mode') { + handleThemeChange(); + } + }); + }); + + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-layout-mode'] + }); + + // Also listen for localStorage changes + const handleStorageChange = (e) => { + if (e.key === 'colorschema') { + handleThemeChange(); + } + }; + + window.addEventListener('storage', handleStorageChange); + + return () => { + observer.disconnect(); + window.removeEventListener('storage', handleStorageChange); + }; + }, []); + + // Calculate pagination info + const startRecord = totalCount === 0 ? 0 : (currentPage - 1) * pageSize + 1; + const endRecord = Math.min(currentPage * pageSize, totalCount); + + // Handle page change + const handlePageClick = (page) => { + if (!loading && page !== currentPage && onPageChange) { + onPageChange(page); + } + }; + + // Handle page size change + const handlePageSizeClick = (newPageSize) => { + if (!loading && newPageSize !== pageSize && onPageSizeChange) { + onPageSizeChange(newPageSize); + } + }; + + // Container styles based on compact mode + const containerStyles = { + background: isDarkMode + ? 'linear-gradient(135deg, #2c3e50 0%, #34495e 100%)' + : 'linear-gradient(135deg, #ffffff, #f8f9fa)', + border: isDarkMode + ? '1px solid rgba(52, 152, 219, 0.3)' + : '1px solid rgba(0, 0, 0, 0.1)', + borderRadius: compact ? '8px' : '12px', + boxShadow: isDarkMode + ? (compact ? '0 4px 16px rgba(0, 0, 0, 0.2), 0 1px 4px rgba(52, 152, 219, 0.1)' : '0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(52, 152, 219, 0.1)') + : (compact ? '0 1px 6px rgba(0, 0, 0, 0.06)' : '0 2px 12px rgba(0, 0, 0, 0.08)'), + backdropFilter: isDarkMode ? (compact ? 'blur(8px)' : 'blur(10px)') : 'none', + transition: compact ? 'all 0.2s ease' : 'all 0.3s ease', + position: 'relative', + overflow: 'hidden', + padding: compact ? '8px 16px' : '16px 24px', + margin: compact ? '8px 0' : '16px 0', + fontSize: compact ? '13px' : '14px' + }; + + // Button styles + const getButtonStyles = (isActive) => ({ + background: loading + ? (isDarkMode ? 'linear-gradient(45deg, #7f8c8d, #95a5a6)' : 'linear-gradient(135deg, #f8f9fa, #e9ecef)') + : isActive + ? (isDarkMode ? 'linear-gradient(45deg, #f39c12, #e67e22)' : 'linear-gradient(135deg, #007bff, #0056b3)') + : (isDarkMode ? 'linear-gradient(45deg, #34495e, #2c3e50)' : 'linear-gradient(135deg, #ffffff, #f8f9fa)'), + border: isActive + ? (isDarkMode ? (compact ? '1px solid #f39c12' : '2px solid #f39c12') : (compact ? '1px solid #007bff' : '2px solid #007bff')) + : (isDarkMode ? '1px solid rgba(52, 152, 219, 0.3)' : '1px solid #dee2e6'), + borderRadius: '50%', + width: compact ? '24px' : '32px', + height: compact ? '24px' : '32px', + color: isActive + ? '#ffffff' + : (isDarkMode ? '#ffffff' : '#495057'), + fontSize: compact ? '11px' : '14px', + fontWeight: compact ? '600' : '700', + cursor: loading ? 'not-allowed' : 'pointer', + transition: compact ? 'all 0.2s ease' : 'all 0.3s ease', + boxShadow: loading + ? 'none' + : isActive + ? (isDarkMode ? (compact ? '0 2px 6px rgba(243, 156, 18, 0.3)' : '0 4px 12px rgba(243, 156, 18, 0.4)') : (compact ? '0 2px 4px rgba(0, 123, 255, 0.2)' : '0 3px 8px rgba(0, 123, 255, 0.3)')) + : (isDarkMode ? (compact ? '0 1px 4px rgba(52, 73, 94, 0.2)' : '0 2px 8px rgba(52, 73, 94, 0.3)') : (compact ? '0 1px 2px rgba(0, 0, 0, 0.08)' : '0 1px 3px rgba(0, 0, 0, 0.1)')), + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + opacity: loading ? 0.6 : 1 + }); + + return ( +
+ {/* Pagination Info */} + {showInfo && ( +
+ {showPageSizeSelector && ( +
+ Row Per Page + + Entries +
+ )} + +
+
+ 📊 +
+ + Showing {startRecord} to {endRecord} of {totalCount} entries + +
+
+ )} + + {/* Pagination Buttons */} +
+ {Array.from({ length: totalPages }, (_, i) => { + const pageNum = i + 1; + const isActive = currentPage === pageNum; + + return ( + + ); + })} +
+
+ ); +}; + +export default CustomPagination; diff --git a/src/feature-module/inventory/productlist.jsx b/src/feature-module/inventory/productlist.jsx index fb36df5..fb8ed9f 100644 --- a/src/feature-module/inventory/productlist.jsx +++ b/src/feature-module/inventory/productlist.jsx @@ -30,6 +30,7 @@ import { deleteProduct, clearProductError } from "../../core/redux/actions/productActions"; +import CustomPagination from '../../components/CustomPagination'; // Add CSS animations for beautiful UI const shimmerKeyframes = ` @@ -1252,182 +1253,21 @@ const ProductList = () => { pagination={false} // Disable Ant Design pagination /> - {/* Table Pagination with Theme Integration */} -
{ - e.currentTarget.style.transform = 'translateY(-2px)'; - e.currentTarget.style.boxShadow = '0 12px 40px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(52, 152, 219, 0.2)'; - }} - onMouseLeave={(e) => { - e.currentTarget.style.transform = 'translateY(0)'; - e.currentTarget.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(52, 152, 219, 0.1)'; - }} - > - {/* Animated background glow */} -
- - {/* Row Per Page Section */} -
-
- Row Per Page - - Entries -
- -
-
- 📊 -
- - Showing {startRecord} to {endRecord} of {totalRecords} entries - {debouncedSearchTerm && ( - - (filtered from {totalProducts || totalRecords} total) - - )} - -
-
- - {/* Pagination Section like the image */} -
- {/* Numbered Pagination Buttons */} - {Array.from({ length: actualTotalPages }, (_, i) => { - const pageNum = i + 1; - const isActive = currentPage === pageNum; - - return ( - - ); - })} -
-
+ {/* Reusable Custom Pagination Component */} + )}
diff --git a/src/feature-module/projects/projecttracker.jsx b/src/feature-module/projects/projecttracker.jsx index 009a139..b910bea 100644 --- a/src/feature-module/projects/projecttracker.jsx +++ b/src/feature-module/projects/projecttracker.jsx @@ -8,32 +8,12 @@ import { Plus } from 'feather-icons-react'; import dayjs from 'dayjs'; -import { useSelector } from 'react-redux'; +import CustomPagination from '../../components/CustomPagination'; const { Option } = Select; const { RangePicker } = DatePicker; const ProjectTracker = () => { - // Theme state for force re-render - const [themeKey, setThemeKey] = useState(0); - - // Get theme from Redux - const reduxTheme = useSelector((state) => state.theme?.isDarkMode); - - // Get theme from multiple sources with real-time detection - const localStorageTheme = localStorage.getItem('colorschema') === 'dark_mode'; - const documentTheme = document.documentElement.getAttribute('data-layout-mode') === 'dark_mode'; - - const isDarkMode = reduxTheme || localStorageTheme || documentTheme; - - // Debug theme state - console.log('Theme Debug:', { - reduxTheme: reduxTheme, - localStorage: localStorage.getItem('colorschema'), - documentAttribute: document.documentElement.getAttribute('data-layout-mode'), - isDarkMode: isDarkMode, - themeKey: themeKey - }); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [filterStatus, setFilterStatus] = useState('All Status'); @@ -171,42 +151,7 @@ const ProjectTracker = () => { loadProjects(); }, []); - // Listen for theme changes - useEffect(() => { - const handleThemeChange = () => { - // Force re-render when theme changes - console.log('Theme changed, forcing re-render'); - setThemeKey(prev => prev + 1); - }; - // Listen for data-layout-mode attribute changes - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (mutation.type === 'attributes' && mutation.attributeName === 'data-layout-mode') { - handleThemeChange(); - } - }); - }); - - observer.observe(document.documentElement, { - attributes: true, - attributeFilter: ['data-layout-mode'] - }); - - // Also listen for localStorage changes - const handleStorageChange = (e) => { - if (e.key === 'colorschema') { - handleThemeChange(); - } - }; - - window.addEventListener('storage', handleStorageChange); - - return () => { - observer.disconnect(); - window.removeEventListener('storage', handleStorageChange); - }; - }, []); // Handle pagination change const handlePageChange = (page) => { @@ -231,9 +176,7 @@ const ProjectTracker = () => { } }; - // Calculate pagination info - const startRecord = totalCount > 0 ? (currentPage - 1) * pageSize + 1 : 0; - const endRecord = Math.min(currentPage * pageSize, totalCount); + // Table columns configuration const columns = [ @@ -710,182 +653,21 @@ const ProjectTracker = () => { - {/* Custom Pagination - Compact Size */} -
- {/* Pagination Info - Compact */} -
-
- Row Per Page - - Entries -
- -
-
- 📊 -
- - Showing {startRecord} to {endRecord} of {totalCount} entries - -
-
- - {/* Pagination Buttons - Compact */} -
- {/* Numbered Pagination Buttons */} - {Array.from({ length: totalPages }, (_, i) => { - const pageNum = i + 1; - const isActive = currentPage === pageNum; - - return ( - - ); - })} -
-
+ {/* Reusable Custom Pagination Component */} +