π Create Reusable CustomPagination Component
π― 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
This commit is contained in:
parent
4784fc8bf5
commit
be9da50fc5
271
src/components/CustomPagination.jsx
Normal file
271
src/components/CustomPagination.jsx
Normal file
@ -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 (
|
||||
<div
|
||||
key={`pagination-${themeKey}`}
|
||||
className={`custom-pagination-container ${isDarkMode ? '' : 'light-mode'} ${className}`}
|
||||
style={containerStyles}
|
||||
>
|
||||
{/* Pagination Info */}
|
||||
{showInfo && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: compact ? '8px' : '16px',
|
||||
flexWrap: 'wrap',
|
||||
gap: compact ? '8px' : '12px'
|
||||
}}
|
||||
>
|
||||
{showPageSizeSelector && (
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: compact ? '6px' : '12px'}}>
|
||||
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: compact ? '12px' : '14px', fontWeight: '500'}}>Row Per Page</span>
|
||||
<select
|
||||
value={pageSize}
|
||||
onChange={(e) => handlePageSizeClick(parseInt(e.target.value))}
|
||||
disabled={loading}
|
||||
style={{
|
||||
background: loading
|
||||
? (isDarkMode ? 'linear-gradient(45deg, #7f8c8d, #95a5a6)' : 'linear-gradient(135deg, #f8f9fa, #e9ecef)')
|
||||
: (isDarkMode ? 'linear-gradient(45deg, #34495e, #2c3e50)' : 'linear-gradient(135deg, #ffffff, #f8f9fa)'),
|
||||
border: isDarkMode
|
||||
? '1px solid rgba(52, 152, 219, 0.3)'
|
||||
: '1px solid #dee2e6',
|
||||
borderRadius: compact ? '4px' : '6px',
|
||||
color: isDarkMode ? '#ffffff' : '#495057',
|
||||
padding: compact ? '2px 6px' : '4px 8px',
|
||||
fontSize: compact ? '12px' : '14px',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
opacity: loading ? 0.7 : 1,
|
||||
boxShadow: isDarkMode ? 'none' : (compact ? '0 1px 2px rgba(0, 0, 0, 0.05)' : '0 1px 3px rgba(0, 0, 0, 0.1)')
|
||||
}}
|
||||
>
|
||||
{pageSizeOptions.map(option => (
|
||||
<option
|
||||
key={option}
|
||||
value={option}
|
||||
style={{background: isDarkMode ? '#2c3e50' : '#ffffff', color: isDarkMode ? '#ffffff' : '#495057'}}
|
||||
>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: compact ? '12px' : '14px', fontWeight: '500'}}>Entries</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: compact ? '6px' : '12px'}}>
|
||||
<div
|
||||
style={{
|
||||
background: isDarkMode
|
||||
? 'linear-gradient(45deg, #3498db, #2ecc71)'
|
||||
: 'linear-gradient(45deg, #007bff, #28a745)',
|
||||
borderRadius: '50%',
|
||||
width: compact ? '16px' : '24px',
|
||||
height: compact ? '16px' : '24px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: compact ? '8px' : '12px',
|
||||
boxShadow: isDarkMode
|
||||
? (compact ? '0 1px 4px rgba(52, 152, 219, 0.3)' : '0 2px 8px rgba(52, 152, 219, 0.3)')
|
||||
: (compact ? '0 1px 4px rgba(0, 123, 255, 0.2)' : '0 2px 8px rgba(0, 123, 255, 0.2)'),
|
||||
transition: compact ? 'all 0.2s ease' : 'all 0.3s ease'
|
||||
}}
|
||||
>
|
||||
π
|
||||
</div>
|
||||
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: compact ? '12px' : '14px', fontWeight: '500'}}>
|
||||
Showing <strong style={{color: isDarkMode ? '#3498db' : '#007bff'}}>{startRecord}</strong> to <strong style={{color: isDarkMode ? '#3498db' : '#007bff'}}>{endRecord}</strong> of <strong style={{color: isDarkMode ? '#e74c3c' : '#dc3545'}}>{totalCount}</strong> entries
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Pagination Buttons */}
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: compact ? '4px' : '8px'
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: totalPages }, (_, i) => {
|
||||
const pageNum = i + 1;
|
||||
const isActive = currentPage === pageNum;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => handlePageClick(pageNum)}
|
||||
disabled={loading}
|
||||
style={getButtonStyles(isActive)}
|
||||
onMouseEnter={(e) => {
|
||||
if (!loading && !isActive) {
|
||||
e.target.style.background = isDarkMode
|
||||
? 'linear-gradient(45deg, #3498db, #2980b9)'
|
||||
: 'linear-gradient(135deg, #e9ecef, #f8f9fa)';
|
||||
e.target.style.transform = isDarkMode ? (compact ? 'scale(1.05)' : 'scale(1.1)') : (compact ? 'translateY(-1px) scale(1.02)' : 'translateY(-1px) scale(1.05)');
|
||||
e.target.style.boxShadow = isDarkMode
|
||||
? (compact ? '0 2px 6px rgba(52, 152, 219, 0.3)' : '0 4px 12px rgba(52, 152, 219, 0.4)')
|
||||
: (compact ? '0 2px 4px rgba(0, 0, 0, 0.12)' : '0 3px 8px rgba(0, 0, 0, 0.15)');
|
||||
e.target.style.borderColor = isDarkMode ? '#3498db' : '#adb5bd';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!loading && !isActive) {
|
||||
e.target.style.background = isDarkMode
|
||||
? 'linear-gradient(45deg, #34495e, #2c3e50)'
|
||||
: 'linear-gradient(135deg, #ffffff, #f8f9fa)';
|
||||
e.target.style.transform = 'scale(1)';
|
||||
e.target.style.boxShadow = 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)');
|
||||
e.target.style.borderColor = isDarkMode ? 'rgba(52, 152, 219, 0.3)' : '#dee2e6';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomPagination;
|
||||
@ -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 */}
|
||||
<div
|
||||
className={`custom-pagination-container ${isDarkMode ? '' : 'light-mode'}`}
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #2c3e50 0%, #34495e 100%)',
|
||||
border: '1px solid rgba(52, 152, 219, 0.3)',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(52, 152, 219, 0.1)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
transition: 'all 0.3s ease',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
padding: '16px 24px',
|
||||
margin: '16px 0'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
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 */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: '-100%',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: 'linear-gradient(90deg, transparent, rgba(52, 152, 219, 0.1), transparent)',
|
||||
animation: 'shimmer 3s infinite',
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
{/* Reusable Custom Pagination Component */}
|
||||
<CustomPagination
|
||||
currentPage={currentPage}
|
||||
pageSize={pageSize}
|
||||
totalCount={totalRecords}
|
||||
totalPages={actualTotalPages}
|
||||
loading={loading}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
pageSizeOptions={[10, 20, 50, 100]}
|
||||
showInfo={true}
|
||||
showPageSizeSelector={true}
|
||||
compact={false}
|
||||
className="product-list-pagination"
|
||||
/>
|
||||
|
||||
{/* Row Per Page Section */}
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: '16px'
|
||||
}}
|
||||
>
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: '12px'}}>
|
||||
<span className="pagination-info" style={{color: '#bdc3c7', fontSize: '14px'}}>Row Per Page</span>
|
||||
<select
|
||||
value={pageSize}
|
||||
onChange={(e) => {
|
||||
const newPageSize = parseInt(e.target.value);
|
||||
handlePageSizeChange(newPageSize);
|
||||
}}
|
||||
disabled={loading}
|
||||
style={{
|
||||
background: loading
|
||||
? 'linear-gradient(45deg, #7f8c8d, #95a5a6)'
|
||||
: 'linear-gradient(45deg, #34495e, #2c3e50)',
|
||||
border: '1px solid rgba(52, 152, 219, 0.3)',
|
||||
borderRadius: '6px',
|
||||
color: '#ffffff',
|
||||
padding: '4px 8px',
|
||||
fontSize: '14px',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
opacity: loading ? 0.7 : 1
|
||||
}}
|
||||
>
|
||||
<option value={10} style={{background: '#2c3e50', color: '#ffffff'}}>10</option>
|
||||
<option value={20} style={{background: '#2c3e50', color: '#ffffff'}}>20</option>
|
||||
<option value={50} style={{background: '#2c3e50', color: '#ffffff'}}>50</option>
|
||||
<option value={100} style={{background: '#2c3e50', color: '#ffffff'}}>100</option>
|
||||
</select>
|
||||
<span className="pagination-info" style={{color: '#bdc3c7', fontSize: '14px'}}>Entries</span>
|
||||
</div>
|
||||
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: '12px'}}>
|
||||
<div
|
||||
style={{
|
||||
background: 'linear-gradient(45deg, #3498db, #2ecc71)',
|
||||
borderRadius: '50%',
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '12px',
|
||||
boxShadow: '0 2px 8px rgba(52, 152, 219, 0.3)'
|
||||
}}
|
||||
>
|
||||
π
|
||||
</div>
|
||||
<span className="pagination-info" style={{color: '#bdc3c7', fontSize: '14px'}}>
|
||||
Showing <strong style={{color: '#3498db'}}>{startRecord}</strong> to <strong style={{color: '#3498db'}}>{endRecord}</strong> of <strong style={{color: '#e74c3c'}}>{totalRecords}</strong> entries
|
||||
{debouncedSearchTerm && (
|
||||
<span style={{color: '#2ecc71', marginLeft: '8px'}}>
|
||||
(filtered from <strong style={{color: '#f39c12'}}>{totalProducts || totalRecords}</strong> total)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pagination Section like the image */}
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}
|
||||
>
|
||||
{/* Numbered Pagination Buttons */}
|
||||
{Array.from({ length: actualTotalPages }, (_, i) => {
|
||||
const pageNum = i + 1;
|
||||
const isActive = currentPage === pageNum;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => !loading && handlePageChange(pageNum)}
|
||||
disabled={loading}
|
||||
className={isActive ? 'active' : ''}
|
||||
style={{
|
||||
background: loading
|
||||
? 'linear-gradient(45deg, #7f8c8d, #95a5a6)'
|
||||
: isActive
|
||||
? 'linear-gradient(45deg, #f39c12, #e67e22)'
|
||||
: 'linear-gradient(45deg, #34495e, #2c3e50)',
|
||||
border: isActive
|
||||
? '2px solid #f39c12'
|
||||
: '1px solid rgba(52, 152, 219, 0.3)',
|
||||
borderRadius: '50%',
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
color: '#ffffff',
|
||||
fontSize: '14px',
|
||||
fontWeight: '700',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
transition: 'all 0.3s ease',
|
||||
boxShadow: loading
|
||||
? 'none'
|
||||
: isActive
|
||||
? '0 4px 12px rgba(243, 156, 18, 0.4)'
|
||||
: '0 2px 8px rgba(52, 73, 94, 0.3)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
opacity: loading ? 0.7 : 1
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!isActive) {
|
||||
e.target.style.background = 'linear-gradient(45deg, #3498db, #2980b9)';
|
||||
e.target.style.transform = 'translateY(-2px)';
|
||||
e.target.style.boxShadow = '0 4px 12px rgba(52, 152, 219, 0.4)';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!isActive) {
|
||||
e.target.style.background = 'linear-gradient(45deg, #34495e, #2c3e50)';
|
||||
e.target.style.transform = 'translateY(0)';
|
||||
e.target.style.boxShadow = '0 2px 8px rgba(52, 73, 94, 0.3)';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -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 = () => {
|
||||
</Spin>
|
||||
</div>
|
||||
|
||||
{/* Custom Pagination - Compact Size */}
|
||||
<div
|
||||
key={`pagination-${themeKey}`}
|
||||
className={`custom-pagination-container ${isDarkMode ? '' : 'light-mode'}`}
|
||||
style={{
|
||||
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: '8px',
|
||||
boxShadow: isDarkMode
|
||||
? '0 4px 16px rgba(0, 0, 0, 0.2), 0 1px 4px rgba(52, 152, 219, 0.1)'
|
||||
: '0 1px 6px rgba(0, 0, 0, 0.06)',
|
||||
backdropFilter: isDarkMode ? 'blur(8px)' : 'none',
|
||||
transition: 'all 0.2s ease',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
padding: '8px 16px',
|
||||
margin: '8px 0',
|
||||
fontSize: '13px'
|
||||
}}
|
||||
>
|
||||
{/* Pagination Info - Compact */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: '8px',
|
||||
flexWrap: 'wrap',
|
||||
gap: '8px'
|
||||
}}
|
||||
>
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: '6px'}}>
|
||||
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: '12px', fontWeight: '500'}}>Row Per Page</span>
|
||||
<select
|
||||
value={pageSize}
|
||||
onChange={(e) => {
|
||||
const newPageSize = parseInt(e.target.value);
|
||||
handlePageSizeChange(newPageSize);
|
||||
}}
|
||||
disabled={loading}
|
||||
style={{
|
||||
background: loading
|
||||
? (isDarkMode ? 'linear-gradient(45deg, #7f8c8d, #95a5a6)' : 'linear-gradient(135deg, #f8f9fa, #e9ecef)')
|
||||
: (isDarkMode ? 'linear-gradient(45deg, #34495e, #2c3e50)' : 'linear-gradient(135deg, #ffffff, #f8f9fa)'),
|
||||
border: isDarkMode
|
||||
? '1px solid rgba(52, 152, 219, 0.3)'
|
||||
: '1px solid #dee2e6',
|
||||
borderRadius: '4px',
|
||||
color: isDarkMode ? '#ffffff' : '#495057',
|
||||
padding: '2px 6px',
|
||||
fontSize: '12px',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
opacity: loading ? 0.7 : 1,
|
||||
boxShadow: isDarkMode ? 'none' : '0 1px 2px rgba(0, 0, 0, 0.05)'
|
||||
}}
|
||||
>
|
||||
<option value={10} style={{background: isDarkMode ? '#2c3e50' : '#ffffff', color: isDarkMode ? '#ffffff' : '#495057'}}>10</option>
|
||||
<option value={20} style={{background: isDarkMode ? '#2c3e50' : '#ffffff', color: isDarkMode ? '#ffffff' : '#495057'}}>20</option>
|
||||
<option value={50} style={{background: isDarkMode ? '#2c3e50' : '#ffffff', color: isDarkMode ? '#ffffff' : '#495057'}}>50</option>
|
||||
<option value={100} style={{background: isDarkMode ? '#2c3e50' : '#ffffff', color: isDarkMode ? '#ffffff' : '#495057'}}>100</option>
|
||||
</select>
|
||||
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: '12px', fontWeight: '500'}}>Entries</span>
|
||||
</div>
|
||||
|
||||
<div style={{display: 'flex', alignItems: 'center', gap: '6px'}}>
|
||||
<div
|
||||
style={{
|
||||
background: isDarkMode
|
||||
? 'linear-gradient(45deg, #3498db, #2ecc71)'
|
||||
: 'linear-gradient(45deg, #007bff, #28a745)',
|
||||
borderRadius: '50%',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '8px',
|
||||
boxShadow: isDarkMode
|
||||
? '0 1px 4px rgba(52, 152, 219, 0.3)'
|
||||
: '0 1px 4px rgba(0, 123, 255, 0.2)',
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
>
|
||||
π
|
||||
</div>
|
||||
<span style={{color: isDarkMode ? '#bdc3c7' : '#2c3e50', fontSize: '12px', fontWeight: '500'}}>
|
||||
Showing <strong style={{color: isDarkMode ? '#3498db' : '#007bff'}}>{startRecord}</strong> to <strong style={{color: isDarkMode ? '#3498db' : '#007bff'}}>{endRecord}</strong> of <strong style={{color: isDarkMode ? '#e74c3c' : '#dc3545'}}>{totalCount}</strong> entries
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pagination Buttons - Compact */}
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '4px'
|
||||
}}
|
||||
>
|
||||
{/* Numbered Pagination Buttons */}
|
||||
{Array.from({ length: totalPages }, (_, i) => {
|
||||
const pageNum = i + 1;
|
||||
const isActive = currentPage === pageNum;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => !loading && handlePageChange(pageNum)}
|
||||
disabled={loading}
|
||||
style={{
|
||||
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 ? '1px solid #f39c12' : '1px solid #007bff')
|
||||
: (isDarkMode ? '1px solid rgba(52, 152, 219, 0.3)' : '1px solid #dee2e6'),
|
||||
borderRadius: '50%',
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
color: isActive
|
||||
? '#ffffff'
|
||||
: (isDarkMode ? '#ffffff' : '#495057'),
|
||||
fontSize: '11px',
|
||||
fontWeight: '600',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
boxShadow: loading
|
||||
? 'none'
|
||||
: isActive
|
||||
? (isDarkMode ? '0 2px 6px rgba(243, 156, 18, 0.3)' : '0 2px 4px rgba(0, 123, 255, 0.2)')
|
||||
: (isDarkMode ? '0 1px 4px rgba(52, 73, 94, 0.2)' : '0 1px 2px rgba(0, 0, 0, 0.08)'),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
opacity: loading ? 0.6 : 1
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!loading && !isActive) {
|
||||
e.target.style.background = isDarkMode
|
||||
? 'linear-gradient(45deg, #3498db, #2980b9)'
|
||||
: 'linear-gradient(135deg, #e9ecef, #f8f9fa)';
|
||||
e.target.style.transform = isDarkMode ? 'scale(1.05)' : 'translateY(-1px) scale(1.02)';
|
||||
e.target.style.boxShadow = isDarkMode
|
||||
? '0 2px 6px rgba(52, 152, 219, 0.3)'
|
||||
: '0 2px 4px rgba(0, 0, 0, 0.12)';
|
||||
e.target.style.borderColor = isDarkMode ? '#3498db' : '#adb5bd';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!loading && !isActive) {
|
||||
e.target.style.background = isDarkMode
|
||||
? 'linear-gradient(45deg, #34495e, #2c3e50)'
|
||||
: 'linear-gradient(135deg, #ffffff, #f8f9fa)';
|
||||
e.target.style.transform = 'scale(1)';
|
||||
e.target.style.boxShadow = isDarkMode
|
||||
? '0 1px 4px rgba(52, 73, 94, 0.2)'
|
||||
: '0 1px 2px rgba(0, 0, 0, 0.08)';
|
||||
e.target.style.borderColor = isDarkMode ? 'rgba(52, 152, 219, 0.3)' : '#dee2e6';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{/* Reusable Custom Pagination Component */}
|
||||
<CustomPagination
|
||||
currentPage={currentPage}
|
||||
pageSize={pageSize}
|
||||
totalCount={totalCount}
|
||||
totalPages={totalPages}
|
||||
loading={loading}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
pageSizeOptions={[10, 20, 50, 100]}
|
||||
showInfo={true}
|
||||
showPageSizeSelector={true}
|
||||
compact={true}
|
||||
className="project-tracker-pagination"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Loadingβ¦
x
Reference in New Issue
Block a user