Files
leocrm/frontend/src/components/ui/Pagination.tsx
T

99 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import clsx from 'clsx';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { useTranslation } from 'react-i18next';
export interface PaginationProps {
currentPage: number;
totalPages: number;
total: number;
pageSize: number;
onPageChange: (page: number) => void;
}
export function Pagination({ currentPage, totalPages, total, pageSize, onPageChange }: PaginationProps) {
const { t } = useTranslation();
const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1;
const end = Math.min(currentPage * pageSize, total);
const pages: number[] = [];
const maxVisible = 5;
const halfVisible = Math.floor(maxVisible / 2);
let startPage = Math.max(1, currentPage - halfVisible);
let endPage = Math.min(totalPages, startPage + maxVisible - 1);
if (endPage - startPage < maxVisible - 1) {
startPage = Math.max(1, endPage - maxVisible + 1);
}
for (let i = startPage; i <= endPage; i++) {
pages.push(i);
}
if (totalPages <= 1) return null;
return (
<nav className="flex items-center justify-between px-3 py-1.5 border-t border-secondary-200 bg-white" aria-label="Pagination">
<div className="text-xs text-secondary-500">
<span>{start}{end}</span> <span>{t('table.of')}</span> <span>{total}</span>
</div>
<div className="flex items-center gap-0.5">
<button
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage <= 1}
className="px-1.5 py-0.5 text-xs rounded border border-secondary-300 hover:bg-secondary-50 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center focus:outline-none focus-visible:ring-1 focus-visible:ring-primary-500"
aria-label={t('pagination.previous')}
>
<ChevronLeft className="h-3 w-3" aria-hidden="true" />
</button>
{startPage > 1 && (
<>
<button
onClick={() => onPageChange(1)}
className="px-1.5 py-0.5 text-xs rounded border border-secondary-300 hover:bg-secondary-50 focus:outline-none focus-visible:ring-1 focus-visible:ring-primary-500"
aria-label={t('pagination.first')}
>
1
</button>
{startPage > 2 && <span className="px-0.5 text-secondary-400" aria-hidden="true"></span>}
</>
)}
{pages.map((page) => (
<button
key={page}
onClick={() => onPageChange(page)}
className={clsx(
'px-1.5 py-0.5 text-xs rounded border focus:outline-none focus-visible:ring-1 focus-visible:ring-primary-500',
page === currentPage
? 'border-primary-500 bg-primary-50 text-primary-700 font-semibold'
: 'border-secondary-300 hover:bg-secondary-50'
)}
aria-label={t('pagination.page', { page })}
aria-current={page === currentPage ? 'page' : undefined}
>
{page}
</button>
))}
{endPage < totalPages && (
<>
{endPage < totalPages - 1 && <span className="px-0.5 text-secondary-400" aria-hidden="true"></span>}
<button
onClick={() => onPageChange(totalPages)}
className="px-1.5 py-0.5 text-xs rounded border border-secondary-300 hover:bg-secondary-50 focus:outline-none focus-visible:ring-1 focus-visible:ring-primary-500"
aria-label={t('pagination.last')}
>
{totalPages}
</button>
</>
)}
<button
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage >= totalPages}
className="px-1.5 py-0.5 text-xs rounded border border-secondary-300 hover:bg-secondary-50 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center focus:outline-none focus-visible:ring-1 focus-visible:ring-primary-500"
aria-label={t('pagination.next')}
>
<ChevronRight className="h-3 w-3" aria-hidden="true" />
</button>
</div>
</nav>
);
}