mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
426 lines
13 KiB
TypeScript
426 lines
13 KiB
TypeScript
'use client';
|
|
|
|
import { ChangeEventHandler, ReactNode } from 'react';
|
|
|
|
import { Icon } from '@iconify/react';
|
|
import Button from '@/components/Button';
|
|
|
|
import { cn } from '@/lib/helper';
|
|
|
|
const range = (start: number, end: number) =>
|
|
Array.from({ length: end - start + 1 }, (_, i) => i + start);
|
|
|
|
const PaginationButton = ({
|
|
content = '',
|
|
disabled = false,
|
|
onClick = () => {},
|
|
}: {
|
|
content?: ReactNode;
|
|
disabled?: boolean;
|
|
onClick?: () => void;
|
|
}) => (
|
|
<Button
|
|
variant='ghost'
|
|
color='none'
|
|
disabled={disabled}
|
|
onClick={onClick}
|
|
className={cn(
|
|
'join-item w-10 h-10 grid place-items-center p-2.5 rounded-lg! text-sm font-semibold text-base-content/50 aspect-square',
|
|
'disabled:text-primary disabled:pointer-events-auto! disabled:cursor-not-allowed! disabled:bg-primary/10 disabled:active:translate-y-0'
|
|
)}
|
|
>
|
|
{content}
|
|
</Button>
|
|
);
|
|
|
|
const EtcPaginationButton = ({
|
|
startPage = 0,
|
|
endPage = 0,
|
|
onPageItemClick,
|
|
}: {
|
|
startPage: number;
|
|
endPage: number;
|
|
onPageItemClick: (a: number) => void;
|
|
}) => {
|
|
const pages = range(startPage, endPage);
|
|
|
|
return (
|
|
<>
|
|
{startPage > 0 && endPage >= startPage && (
|
|
<div className='dropdown dropdown-top dropdown-center'>
|
|
<button
|
|
tabIndex={0}
|
|
role='button'
|
|
className={cn(
|
|
'join-item btn btn-ghost p-2.5 rounded-lg! text-sm font-medium text-gray-500 aspect-square'
|
|
)}
|
|
>
|
|
...
|
|
</button>
|
|
|
|
<div className='dropdown-content'>
|
|
<ul
|
|
tabIndex={0}
|
|
className='menu bg-base-100 rounded-lg! z-1 w-fit min-w-max max-h-64 p-1 shadow-sm mb-2 overflow-y-auto flex-nowrap'
|
|
>
|
|
{pages.map((pageNumber) => (
|
|
<li key={pageNumber}>
|
|
<PaginationButton
|
|
content={pageNumber}
|
|
onClick={() => onPageItemClick(pageNumber)}
|
|
/>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{(startPage === 0 || endPage < startPage) && (
|
|
<button
|
|
disabled
|
|
className={cn(
|
|
'join-item btn btn-ghost p-2.5 rounded-lg! text-sm font-medium text-gray-500 aspect-square'
|
|
)}
|
|
>
|
|
...
|
|
</button>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
const Pagination = ({
|
|
currentPage = 1,
|
|
totalItems = 0,
|
|
itemsPerPage = 10,
|
|
rowOptions = [10, 20, 50, 100],
|
|
onPageChange,
|
|
onPrevPage = () => {},
|
|
onNextPage = () => {},
|
|
onRowChange,
|
|
}: {
|
|
currentPage: number;
|
|
totalItems: number;
|
|
itemsPerPage: number;
|
|
rowOptions?: number[];
|
|
onPageChange: (pageNumber: number) => void;
|
|
onPrevPage: () => void;
|
|
onNextPage: () => void;
|
|
onRowChange?: (row: number) => void;
|
|
}) => {
|
|
const totalPages =
|
|
Math.ceil(totalItems / itemsPerPage) === 0
|
|
? 1
|
|
: Math.ceil(totalItems / itemsPerPage);
|
|
|
|
const pageChangeHandler = (pageNumber: number) => onPageChange(pageNumber);
|
|
const firstPageClickHandler = () => onPageChange(1);
|
|
const lastPageClickHandler = () => onPageChange(totalPages);
|
|
|
|
const rowChangeHandler: ChangeEventHandler<HTMLSelectElement> = (e) => {
|
|
onRowChange?.(Number(e.target.value));
|
|
};
|
|
|
|
const DisplayedRowCountSelect = () => (
|
|
<div className='flex flex-row items-center gap-4'>
|
|
<span className='text-sm font-medium text-base-content/50'>Showing</span>
|
|
|
|
<select
|
|
defaultValue={itemsPerPage}
|
|
onChange={rowChangeHandler}
|
|
className='select select-xs w-fit pl-3 pr-7 text-base-content/50'
|
|
>
|
|
{rowOptions.map((rowOption, rowOptionIdx) => (
|
|
<option
|
|
key={rowOptionIdx}
|
|
value={rowOption}
|
|
className='text-base-content active:text-neutral-content'
|
|
>
|
|
{rowOption} Per page
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
);
|
|
|
|
const GoToFirstPageButton = () => (
|
|
<Button
|
|
disabled={currentPage === 1}
|
|
onClick={firstPageClickHandler}
|
|
variant='ghost'
|
|
color='none'
|
|
className={cn(
|
|
'join-item w-10 h-10 grid place-items-center p-2.5 rounded-lg! text-sm font-semibold text-base-content/50 aspect-square',
|
|
'disabled:bg-[initial]! disabled:text-base-content disabled:pointer-events-auto! disabled:cursor-not-allowed disabled:active:translate-y-0'
|
|
)}
|
|
>
|
|
<Icon
|
|
icon='heroicons:chevron-double-left'
|
|
width={20}
|
|
height={20}
|
|
className='text-gray-400 group-disabled:text-gray-300'
|
|
/>
|
|
</Button>
|
|
);
|
|
|
|
const PrevPageButton = () => (
|
|
<Button
|
|
disabled={currentPage === 1}
|
|
onClick={onPrevPage}
|
|
variant='ghost'
|
|
color='none'
|
|
className={cn(
|
|
'join-item w-10 h-10 grid place-items-center p-2.5 rounded-lg! text-sm font-semibold text-base-content/50 aspect-square',
|
|
'disabled:bg-[initial]! disabled:text-base-content disabled:pointer-events-auto! disabled:cursor-not-allowed disabled:active:translate-y-0'
|
|
)}
|
|
>
|
|
<Icon
|
|
icon='heroicons:chevron-left'
|
|
width={20}
|
|
height={20}
|
|
className='text-gray-400 group-disabled:text-gray-300'
|
|
/>
|
|
</Button>
|
|
);
|
|
|
|
const GoToLastPageButton = () => (
|
|
<Button
|
|
variant='ghost'
|
|
color='none'
|
|
disabled={currentPage === totalPages}
|
|
onClick={lastPageClickHandler}
|
|
className={cn(
|
|
'join-item w-10 h-10 grid place-items-center p-2.5 rounded-lg! text-sm font-semibold text-base-content/50 aspect-square',
|
|
'disabled:bg-[initial]! disabled:text-base-content disabled:pointer-events-auto! disabled:cursor-not-allowed disabled:active:translate-y-0'
|
|
)}
|
|
>
|
|
<Icon
|
|
icon='heroicons:chevron-double-right'
|
|
width={20}
|
|
height={20}
|
|
className='text-gray-400 group-disabled:text-gray-300'
|
|
/>
|
|
</Button>
|
|
);
|
|
|
|
const NextPageButton = () => (
|
|
<Button
|
|
variant='ghost'
|
|
color='none'
|
|
disabled={currentPage === totalPages}
|
|
onClick={onNextPage}
|
|
className={cn(
|
|
'join-item w-10 h-10 grid place-items-center p-2.5 rounded-lg! text-sm font-semibold text-base-content/50 aspect-square',
|
|
'disabled:bg-[initial]! disabled:text-base-content disabled:pointer-events-auto! disabled:cursor-not-allowed disabled:active:translate-y-0'
|
|
)}
|
|
>
|
|
<Icon
|
|
icon='heroicons:chevron-right'
|
|
width={20}
|
|
height={20}
|
|
className='text-gray-400 group-disabled:text-gray-300'
|
|
/>
|
|
</Button>
|
|
);
|
|
|
|
const PageInfo = () => (
|
|
<span className='text-nowrap text-sm font-medium text-base-content/50'>
|
|
Total Item: {totalItems} | Page {currentPage} of {totalPages}
|
|
</span>
|
|
);
|
|
|
|
return (
|
|
<div className='@container'>
|
|
<div className='flex flex-row justify-center items-center'>
|
|
<div className='hidden @md:block'>
|
|
<DisplayedRowCountSelect />
|
|
</div>
|
|
|
|
<div className='join w-full justify-end @md:justify-center items-center gap-0.5'>
|
|
<div className='hidden @md:block'>
|
|
<GoToFirstPageButton />
|
|
</div>
|
|
|
|
<div className='hidden @md:block'>
|
|
<PrevPageButton />
|
|
</div>
|
|
|
|
{totalPages <= 7 &&
|
|
range(1, totalPages).map((pageNumber) => (
|
|
<PaginationButton
|
|
key={pageNumber}
|
|
content={pageNumber}
|
|
disabled={currentPage === pageNumber}
|
|
onClick={() => pageChangeHandler(pageNumber)}
|
|
/>
|
|
))}
|
|
|
|
{totalPages > 7 && (
|
|
<>
|
|
<PaginationButton
|
|
content={1}
|
|
disabled={currentPage === 1}
|
|
onClick={() => pageChangeHandler(1)}
|
|
/>
|
|
|
|
{totalPages >= 2 &&
|
|
(currentPage <= 3 || currentPage >= totalPages - 2) && (
|
|
<PaginationButton
|
|
content={2}
|
|
disabled={currentPage === 2}
|
|
onClick={() => pageChangeHandler(2)}
|
|
/>
|
|
)}
|
|
|
|
{totalPages >= 2 &&
|
|
currentPage > 3 &&
|
|
currentPage < totalPages - 2 && (
|
|
<EtcPaginationButton
|
|
startPage={2}
|
|
endPage={currentPage - 2}
|
|
onPageItemClick={pageChangeHandler}
|
|
/>
|
|
)}
|
|
|
|
{totalPages >= 3 &&
|
|
(currentPage <= 4 || currentPage >= totalPages - 2) &&
|
|
currentPage !== totalPages - 2 && (
|
|
<PaginationButton
|
|
content={3}
|
|
disabled={currentPage === 3}
|
|
onClick={() => pageChangeHandler(3)}
|
|
/>
|
|
)}
|
|
|
|
{totalPages >= 7 &&
|
|
(currentPage <= 2 || currentPage >= totalPages - 2) && (
|
|
<EtcPaginationButton
|
|
startPage={
|
|
currentPage <= 2
|
|
? currentPage + 2
|
|
: currentPage === totalPages - 2
|
|
? 3
|
|
: currentPage >= totalPages - 1
|
|
? 4
|
|
: 1
|
|
}
|
|
endPage={
|
|
currentPage <= 2 || currentPage >= totalPages - 1
|
|
? totalPages - 3
|
|
: currentPage === totalPages - 2
|
|
? totalPages - 4
|
|
: 2
|
|
}
|
|
onPageItemClick={pageChangeHandler}
|
|
/>
|
|
)}
|
|
|
|
{totalPages >= 3 &&
|
|
currentPage > 4 &&
|
|
currentPage < totalPages - 1 && (
|
|
<PaginationButton
|
|
content={currentPage - 1}
|
|
onClick={() => pageChangeHandler(currentPage - 1)}
|
|
/>
|
|
)}
|
|
|
|
{totalPages >= 7 &&
|
|
currentPage > 3 &&
|
|
currentPage < totalPages - 2 && (
|
|
<PaginationButton content={currentPage} disabled />
|
|
)}
|
|
|
|
{totalPages >= 5 &&
|
|
currentPage > 2 &&
|
|
currentPage < totalPages - 2 && (
|
|
<PaginationButton
|
|
content={currentPage + 1}
|
|
onClick={() => pageChangeHandler(currentPage + 1)}
|
|
/>
|
|
)}
|
|
|
|
{totalPages >= 5 &&
|
|
(currentPage <= 2 || currentPage >= totalPages - 2) && (
|
|
<PaginationButton
|
|
content={totalPages - 2}
|
|
disabled={currentPage === totalPages - 2}
|
|
onClick={() => pageChangeHandler(totalPages - 2)}
|
|
/>
|
|
)}
|
|
|
|
{totalPages >= 6 &&
|
|
currentPage > 2 &&
|
|
currentPage < totalPages - 3 && (
|
|
<EtcPaginationButton
|
|
startPage={
|
|
currentPage <= 3
|
|
? currentPage + 2
|
|
: currentPage >= 4
|
|
? currentPage + 2
|
|
: 1
|
|
}
|
|
endPage={
|
|
currentPage <= 3
|
|
? totalPages - 2
|
|
: currentPage >= 4
|
|
? totalPages - 1
|
|
: 0
|
|
}
|
|
onPageItemClick={pageChangeHandler}
|
|
/>
|
|
)}
|
|
|
|
{totalPages >= 6 &&
|
|
(currentPage <= 3 || currentPage >= totalPages - 3) && (
|
|
<PaginationButton
|
|
content={totalPages - 1}
|
|
disabled={currentPage === totalPages - 1}
|
|
onClick={() => pageChangeHandler(totalPages - 1)}
|
|
/>
|
|
)}
|
|
|
|
{totalPages >= 7 && (
|
|
<PaginationButton
|
|
content={totalPages}
|
|
disabled={currentPage === totalPages}
|
|
onClick={() => pageChangeHandler(totalPages)}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
<div className='hidden @md:block'>
|
|
<NextPageButton />
|
|
</div>
|
|
|
|
<div className='hidden @md:block'>
|
|
<GoToLastPageButton />
|
|
</div>
|
|
</div>
|
|
|
|
<div className='hidden @md:block'>
|
|
<PageInfo />
|
|
</div>
|
|
</div>
|
|
|
|
<div className='flex @md:hidden flex-col justify-center items-end gap-2'>
|
|
<div className='flex flex-row items-center gap-0.5'>
|
|
<GoToFirstPageButton />
|
|
<PrevPageButton />
|
|
<NextPageButton />
|
|
<GoToLastPageButton />
|
|
</div>
|
|
|
|
<div className='flex flex-row items-center gap-4'>
|
|
<DisplayedRowCountSelect />
|
|
|
|
<PageInfo />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Pagination;
|