Files
router-lists-ui/frontend/src/components/Pagination.jsx
T

151 lines
5.4 KiB
React

import { useMemo, useState } from 'react';
import { IconChevronLeft, IconChevronRight, IconChevronsLeft, IconChevronsRight } from '@tabler/icons-react';
/**
* Улучшенный компонент пагинации
* Добавлено: Jump to Page, иконки, улучшенная доступность
*/
function Pagination({ currentPage, totalPages, totalItems, pageSize, onPageChange }) {
const [jumpToPage, setJumpToPage] = useState('');
// Хуки должны вызываться безусловно (до любого return)
const pages = useMemo(() => {
if (totalPages <= 1) return [];
const result = [];
let start = Math.max(1, currentPage - 2);
let end = Math.min(totalPages, currentPage + 2);
if (currentPage <= 3) {
end = Math.min(totalPages, 5);
}
if (currentPage >= totalPages - 2) {
start = Math.max(1, totalPages - 4);
}
if (start > 1) result.push({ type: 'ellipsis', key: 'start-ellipsis' });
for (let p = start; p <= end; p++) {
result.push({ type: 'page', page: p, key: p });
}
if (end < totalPages) result.push({ type: 'ellipsis', key: 'end-ellipsis' });
return result;
}, [currentPage, totalPages]);
// Ранний return после всех хуков
if (totalPages <= 1) return null;
const startItem = (currentPage - 1) * pageSize + 1;
const endItem = Math.min(currentPage * pageSize, totalItems);
const handleJumpToPage = (e) => {
e.preventDefault();
const pageNum = parseInt(jumpToPage, 10);
if (pageNum >= 1 && pageNum <= totalPages) {
onPageChange(pageNum);
setJumpToPage('');
}
};
return (
<div className="card-footer d-flex align-items-center justify-content-between flex-wrap gap-2">
<div className="text-muted">
Показано <strong>{startItem} - {endItem}</strong> из <strong>{totalItems}</strong>
</div>
<div className="d-flex align-items-center gap-2 flex-wrap">
{/* Jump to page */}
{totalPages > 10 && (
<form onSubmit={handleJumpToPage} className="d-flex align-items-center gap-1">
<label htmlFor="jump-to-page" className="text-muted small mb-0">
Стр:
</label>
<input
id="jump-to-page"
type="number"
min="1"
max={totalPages}
value={jumpToPage}
onChange={(e) => setJumpToPage(e.target.value)}
placeholder={currentPage.toString()}
className="form-control form-control-sm"
style={{ width: '60px' }}
aria-label="Перейти на страницу"
/>
</form>
)}
{/* Pagination controls */}
<ul className="pagination m-0">
<li className={`page-item${currentPage === 1 ? ' disabled' : ''}`}>
<button
className="page-link"
onClick={() => onPageChange(1)}
disabled={currentPage === 1}
aria-label="Первая страница"
title="Первая страница"
>
<IconChevronsLeft size={16} />
</button>
</li>
<li className={`page-item${currentPage === 1 ? ' disabled' : ''}`}>
<button
className="page-link"
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}
aria-label="Предыдущая страница"
title="Предыдущая страница"
>
<IconChevronLeft size={16} />
</button>
</li>
{pages.map((item) => {
if (item.type === 'ellipsis') {
return (
<li key={item.key} className="page-item disabled">
<span className="page-link"></span>
</li>
);
}
return (
<li key={item.key} className={`page-item${currentPage === item.page ? ' active' : ''}`}>
<button
className="page-link"
onClick={() => onPageChange(item.page)}
aria-label={`Страница ${item.page}`}
aria-current={currentPage === item.page ? 'page' : undefined}
>
{item.page}
</button>
</li>
);
})}
<li className={`page-item${currentPage === totalPages ? ' disabled' : ''}`}>
<button
className="page-link"
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
aria-label="Следующая страница"
title="Следующая страница"
>
<IconChevronRight size={16} />
</button>
</li>
<li className={`page-item${currentPage === totalPages ? ' disabled' : ''}`}>
<button
className="page-link"
onClick={() => onPageChange(totalPages)}
disabled={currentPage === totalPages}
aria-label="Последняя страница"
title="Последняя страница (стр. {totalPages})"
>
<IconChevronsRight size={16} />
</button>
</li>
</ul>
</div>
</div>
);
}
export default Pagination;