feat: Улучшение компонентов Dashboard, EmptyState, ErrorAlert и Pagination. Добавлены новые функции, такие как индикаторы тренда, возможность перехода на страницу и улучшенные действия с ошибками. Обновлены стили для повышения удобства использования и доступности.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { IconChevronLeft, IconChevronRight, IconChevronsLeft, IconChevronsRight } from '@tabler/icons-react';
|
||||
|
||||
/**
|
||||
* Универсальный компонент пагинации
|
||||
* Оптимизирован: useMemo для вычисления страниц
|
||||
* Улучшенный компонент пагинации
|
||||
* Добавлено: Jump to Page, иконки, улучшенная доступность
|
||||
*/
|
||||
function Pagination({ currentPage, totalPages, totalItems, pageSize, onPageChange }) {
|
||||
const [jumpToPage, setJumpToPage] = useState('');
|
||||
|
||||
if (totalPages <= 1) return null;
|
||||
|
||||
const pages = useMemo(() => {
|
||||
@@ -31,74 +34,112 @@ function Pagination({ currentPage, totalPages, totalItems, pageSize, onPageChang
|
||||
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">
|
||||
<div className="card-footer d-flex align-items-center justify-content-between flex-wrap gap-2">
|
||||
<div className="text-muted">
|
||||
Показано {startItem} - {endItem} из {totalItems}
|
||||
Показано <strong>{startItem} - {endItem}</strong> из <strong>{totalItems}</strong>
|
||||
</div>
|
||||
<ul className="pagination m-0">
|
||||
<li className={`page-item${currentPage === 1 ? ' disabled' : ''}`}>
|
||||
<button
|
||||
className="page-link"
|
||||
onClick={() => onPageChange(1)}
|
||||
disabled={currentPage === 1}
|
||||
aria-label="Первая страница"
|
||||
>
|
||||
Первая
|
||||
</button>
|
||||
</li>
|
||||
<li className={`page-item${currentPage === 1 ? ' disabled' : ''}`}>
|
||||
<button
|
||||
className="page-link"
|
||||
onClick={() => onPageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
aria-label="Предыдущая страница"
|
||||
>
|
||||
Назад
|
||||
</button>
|
||||
</li>
|
||||
{pages.map((item) => {
|
||||
if (item.type === 'ellipsis') {
|
||||
|
||||
<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 disabled">
|
||||
<span className="page-link">…</span>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
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="Следующая страница"
|
||||
>
|
||||
Вперед
|
||||
</button>
|
||||
</li>
|
||||
<li className={`page-item${currentPage === totalPages ? ' disabled' : ''}`}>
|
||||
<button
|
||||
className="page-link"
|
||||
onClick={() => onPageChange(totalPages)}
|
||||
disabled={currentPage === totalPages}
|
||||
aria-label="Последняя страница"
|
||||
>
|
||||
Последняя
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
})}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user