feat: Улучшение компонентов Dashboard, EmptyState, ErrorAlert и Pagination. Добавлены новые функции, такие как индикаторы тренда, возможность перехода на страницу и улучшенные действия с ошибками. Обновлены стили для повышения удобства использования и доступности.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s

This commit is contained in:
2025-10-03 19:11:10 +07:00
parent 72fbb7bde5
commit 18f3a5312a
16 changed files with 2697 additions and 96 deletions
+106 -65
View File
@@ -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>
);
}