� Implement fully functional Row Per Page pagination

 Features Added:
- � Functional Row Per Page dropdown (10, 20, 50, 100)
- � Working page navigation with API integration
- � Real-time data fetching on page/size changes
- � Loading states with visual feedback
- � Beautiful circular pagination buttons (orange active state)
- � Hide all Ant Design default pagination elements
- � Dynamic total records display with search filtering
-  Smooth animations and hover effects

� Technical Improvements:
- State management for pageSize and currentPage
- API integration with fetchProducts action
- Loading states for dropdown and buttons
- Search term preservation across pagination
- Comprehensive CSS hiding for Ant pagination
- Custom pagination container protection
- Error handling and user feedback

� UI/UX Enhancements:
- Glass morphism design with shimmer effects
- Responsive layout matching reference image
- Professional dark theme with gradients
- Touch-friendly 32px circular buttons
- Disabled states with gray styling
- Smooth transitions and hover animations

� Perfect match to reference design with full functionality!
This commit is contained in:
tuanOts 2025-05-28 23:37:31 +07:00
parent f75e524565
commit b7031a8722

View File

@ -65,7 +65,48 @@ if (typeof document !== 'undefined' && !document.getElementById('beautiful-pagin
const styleSheet = document.createElement('style'); const styleSheet = document.createElement('style');
styleSheet.id = 'beautiful-pagination-styles'; styleSheet.id = 'beautiful-pagination-styles';
styleSheet.type = 'text/css'; styleSheet.type = 'text/css';
styleSheet.innerText = shimmerKeyframes; styleSheet.innerText = shimmerKeyframes + `
/* Hide all Ant Design pagination elements */
.ant-pagination,
.ant-pagination-item,
.ant-pagination-item-active,
.ant-pagination-prev,
.ant-pagination-next,
.ant-table-pagination,
ul.ant-pagination,
li.ant-pagination-item,
.ant-pagination-item-1,
.ant-pagination-item-2,
.ant-pagination-item-3,
.ant-pagination-item-4,
.ant-pagination-item-5,
.ant-pagination-jump-prev,
.ant-pagination-jump-next,
.ant-pagination-options,
.ant-pagination-total-text,
.ant-table-wrapper .ant-pagination,
.ant-spin-container .ant-pagination {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
position: absolute !important;
left: -9999px !important;
top: -9999px !important;
z-index: -1 !important;
width: 0 !important;
height: 0 !important;
overflow: hidden !important;
}
/* Ensure our custom pagination is visible */
.custom-pagination-container {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
position: relative !important;
z-index: 1 !important;
}
`;
document.head.appendChild(styleSheet); document.head.appendChild(styleSheet);
} }
@ -93,7 +134,7 @@ const ProductList = () => {
// State for pagination - sync with Redux // State for pagination - sync with Redux
const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1); const [currentPage, setCurrentPage] = useState(reduxCurrentPage || 1);
const pageSize = reduxPageSize || 20; const [pageSize, setPageSize] = useState(reduxPageSize || 20);
// Debounced search term // Debounced search term
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(""); const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
@ -171,6 +212,30 @@ const ProductList = () => {
// Handle pagination // Handle pagination
const handlePageChange = (page) => { const handlePageChange = (page) => {
setCurrentPage(page); setCurrentPage(page);
// Dispatch action to fetch products for the new page
const searchParams = {
page: page,
pageSize: pageSize,
searchTerm: debouncedSearchTerm
};
dispatch(fetchProducts(searchParams));
};
// Handle page size change
const handlePageSizeChange = (newPageSize) => {
setPageSize(newPageSize);
setCurrentPage(1); // Reset to first page when changing page size
// Dispatch action to fetch products with new page size
const searchParams = {
page: 1,
pageSize: newPageSize,
searchTerm: debouncedSearchTerm
};
dispatch(fetchProducts(searchParams));
}; };
// Calculate pagination info // Calculate pagination info
@ -642,9 +707,9 @@ const ProductList = () => {
pagination={false} // Disable Ant Design pagination pagination={false} // Disable Ant Design pagination
/> />
{/* Ant Design Pagination Structure with Beautiful Design */} {/* Table Pagination like the image */}
<div <div
className="ant-pagination ant-table-pagination ant-table-pagination-right css-dev-only-do-not-override-vrrzze" className="custom-pagination-container"
style={{ style={{
background: 'linear-gradient(135deg, #2c3e50 0%, #34495e 100%)', background: 'linear-gradient(135deg, #2c3e50 0%, #34495e 100%)',
border: '1px solid rgba(52, 152, 219, 0.3)', border: '1px solid rgba(52, 152, 219, 0.3)',
@ -655,10 +720,7 @@ const ProductList = () => {
position: 'relative', position: 'relative',
overflow: 'hidden', overflow: 'hidden',
padding: '16px 24px', padding: '16px 24px',
margin: '16px 0', margin: '16px 0'
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}} }}
onMouseEnter={(e) => { onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-2px)'; e.currentTarget.style.transform = 'translateY(-2px)';
@ -683,143 +745,126 @@ const ProductList = () => {
}} }}
/> />
{/* Left side - Total Records Info */} {/* Row Per Page Section */}
<div <div
className="ant-pagination-total-text"
style={{ style={{
position: 'relative', position: 'relative',
zIndex: 1, zIndex: 1,
display: 'flex', display: 'flex',
justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
gap: '12px' marginBottom: '16px'
}} }}
> >
<div style={{display: 'flex', alignItems: 'center', gap: '12px'}}>
<span 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 style={{color: '#bdc3c7', fontSize: '14px'}}>Entries</span>
</div>
<div style={{display: 'flex', alignItems: 'center', gap: '12px'}}>
<div <div
style={{ style={{
background: 'linear-gradient(45deg, #3498db, #2ecc71)', background: 'linear-gradient(45deg, #3498db, #2ecc71)',
borderRadius: '50%', borderRadius: '50%',
width: '32px', width: '24px',
height: '32px', height: '24px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
fontSize: '14px', fontSize: '12px',
boxShadow: '0 4px 12px rgba(52, 152, 219, 0.3)' boxShadow: '0 2px 8px rgba(52, 152, 219, 0.3)'
}} }}
> >
📊 📊
</div> </div>
<div> <span style={{color: '#bdc3c7', fontSize: '14px'}}>
<span style={{color: '#bdc3c7', fontSize: '14px', lineHeight: '1.4'}}> Showing <strong style={{color: '#3498db'}}>{startRecord}</strong> to <strong style={{color: '#3498db'}}>{endRecord}</strong> of <strong style={{color: '#e74c3c'}}>{totalRecords}</strong> entries
Showing <strong style={{color: '#3498db', fontWeight: '700'}}>{startRecord}</strong> to <strong style={{color: '#3498db', fontWeight: '700'}}>{endRecord}</strong> of <strong style={{color: '#e74c3c', fontWeight: '700'}}>{totalRecords}</strong> entries
{debouncedSearchTerm && ( {debouncedSearchTerm && (
<div style={{color: '#2ecc71', fontSize: '12px', marginTop: '2px'}}> <span style={{color: '#2ecc71', marginLeft: '8px'}}>
🔍 Filtered from <strong style={{color: '#f39c12'}}>{totalProducts || totalRecords}</strong> total products (filtered from <strong style={{color: '#f39c12'}}>{totalProducts || totalRecords}</strong> total)
</div> </span>
)} )}
</span> </span>
</div> </div>
</div> </div>
{/* Right side - Pagination Controls */} {/* Pagination Section like the image */}
{actualTotalPages > 1 && (
<div <div
className="ant-pagination-options"
style={{ style={{
position: 'relative', position: 'relative',
zIndex: 1, zIndex: 1,
display: 'flex', display: 'flex',
justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
gap: '8px' gap: '8px'
}} }}
> >
<span style={{color: '#bdc3c7', fontSize: '12px', marginRight: '8px'}}> {/* Numbered Pagination Buttons */}
Page {currentPage} of {actualTotalPages} {Array.from({ length: actualTotalPages }, (_, i) => {
</span> const pageNum = i + 1;
<ul
className="ant-pagination-list"
style={{
display: 'flex',
listStyle: 'none',
margin: 0,
padding: 0,
gap: '4px'
}}
>
<li className={`ant-pagination-prev ${currentPage === 1 ? 'ant-pagination-disabled' : ''}`}>
<button
className="ant-pagination-item-link"
style={{
background: currentPage === 1
? 'rgba(52, 73, 94, 0.5)'
: 'linear-gradient(45deg, #3498db, #2980b9)',
border: 'none',
borderRadius: '8px',
color: currentPage === 1 ? '#7f8c8d' : '#ffffff',
padding: '6px 12px',
fontSize: '12px',
fontWeight: '600',
transition: 'all 0.3s ease',
boxShadow: currentPage === 1
? 'none'
: '0 2px 8px rgba(52, 152, 219, 0.3)',
cursor: currentPage === 1 ? 'not-allowed' : 'pointer'
}}
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
onMouseEnter={(e) => {
if (currentPage !== 1) {
e.target.style.transform = 'translateY(-1px)';
e.target.style.boxShadow = '0 4px 12px rgba(52, 152, 219, 0.4)';
}
}}
onMouseLeave={(e) => {
if (currentPage !== 1) {
e.target.style.transform = 'translateY(0)';
e.target.style.boxShadow = '0 2px 8px rgba(52, 152, 219, 0.3)';
}
}}
>
Prev
</button>
</li>
{Array.from({ length: Math.min(3, actualTotalPages) }, (_, i) => {
let pageNum = i + 1;
if (actualTotalPages > 3 && currentPage > 2) {
pageNum = currentPage - 1 + i;
}
const isActive = currentPage === pageNum; const isActive = currentPage === pageNum;
return ( return (
<li key={pageNum} className={`ant-pagination-item ${isActive ? 'ant-pagination-item-active' : ''}`}>
<button <button
className="ant-pagination-item-link" key={pageNum}
onClick={() => !loading && handlePageChange(pageNum)}
disabled={loading}
style={{ style={{
background: isActive background: loading
? 'linear-gradient(45deg, #e74c3c, #c0392b)' ? 'linear-gradient(45deg, #7f8c8d, #95a5a6)'
: isActive
? 'linear-gradient(45deg, #f39c12, #e67e22)'
: 'linear-gradient(45deg, #34495e, #2c3e50)', : 'linear-gradient(45deg, #34495e, #2c3e50)',
border: isActive border: isActive
? '2px solid #e74c3c' ? '2px solid #f39c12'
: '1px solid rgba(52, 152, 219, 0.3)', : '1px solid rgba(52, 152, 219, 0.3)',
borderRadius: '8px', borderRadius: '50%',
width: '32px',
height: '32px',
color: '#ffffff', color: '#ffffff',
padding: '6px 12px', fontSize: '14px',
fontSize: '12px',
fontWeight: '700', fontWeight: '700',
minWidth: '32px', cursor: loading ? 'not-allowed' : 'pointer',
transition: 'all 0.3s ease', transition: 'all 0.3s ease',
boxShadow: isActive boxShadow: loading
? '0 4px 12px rgba(231, 76, 60, 0.4)' ? 'none'
: isActive
? '0 4px 12px rgba(243, 156, 18, 0.4)'
: '0 2px 8px rgba(52, 73, 94, 0.3)', : '0 2px 8px rgba(52, 73, 94, 0.3)',
cursor: 'pointer' display: 'flex',
alignItems: 'center',
justifyContent: 'center',
opacity: loading ? 0.7 : 1
}} }}
onClick={() => handlePageChange(pageNum)}
onMouseEnter={(e) => { onMouseEnter={(e) => {
if (!isActive) { if (!isActive) {
e.target.style.background = 'linear-gradient(45deg, #3498db, #2980b9)'; e.target.style.background = 'linear-gradient(45deg, #3498db, #2980b9)';
e.target.style.transform = 'translateY(-1px)'; e.target.style.transform = 'translateY(-2px)';
e.target.style.boxShadow = '0 4px 12px rgba(52, 152, 219, 0.4)'; e.target.style.boxShadow = '0 4px 12px rgba(52, 152, 219, 0.4)';
} }
}} }}
@ -833,50 +878,9 @@ const ProductList = () => {
> >
{pageNum} {pageNum}
</button> </button>
</li>
); );
})} })}
<li className={`ant-pagination-next ${currentPage === actualTotalPages ? 'ant-pagination-disabled' : ''}`}>
<button
className="ant-pagination-item-link"
style={{
background: currentPage === actualTotalPages
? 'rgba(52, 73, 94, 0.5)'
: 'linear-gradient(45deg, #3498db, #2980b9)',
border: 'none',
borderRadius: '8px',
color: currentPage === actualTotalPages ? '#7f8c8d' : '#ffffff',
padding: '6px 12px',
fontSize: '12px',
fontWeight: '600',
transition: 'all 0.3s ease',
boxShadow: currentPage === actualTotalPages
? 'none'
: '0 2px 8px rgba(52, 152, 219, 0.3)',
cursor: currentPage === actualTotalPages ? 'not-allowed' : 'pointer'
}}
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === actualTotalPages}
onMouseEnter={(e) => {
if (currentPage !== actualTotalPages) {
e.target.style.transform = 'translateY(-1px)';
e.target.style.boxShadow = '0 4px 12px rgba(52, 152, 219, 0.4)';
}
}}
onMouseLeave={(e) => {
if (currentPage !== actualTotalPages) {
e.target.style.transform = 'translateY(0)';
e.target.style.boxShadow = '0 2px 8px rgba(52, 152, 219, 0.3)';
}
}}
>
Next
</button>
</li>
</ul>
</div> </div>
)}
</div> </div>
</> </>
)} )}