Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m49s
151 lines
5.4 KiB
React
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;
|