mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
feat: Pagination component.
This commit is contained in:
@@ -16,7 +16,7 @@ import { ScrollSync, ScrollSyncPane } from 'react-scroll-sync';
|
||||
|
||||
import { ConditionalWrapper } from 'utils';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
import { If } from 'components';
|
||||
import { If, Pagination } from 'components';
|
||||
|
||||
const IndeterminateCheckbox = React.forwardRef(
|
||||
({ indeterminate, ...rest }, ref) => {
|
||||
@@ -46,6 +46,11 @@ export default function DataTable({
|
||||
expandToggleColumn = 2,
|
||||
noInitialFetch = false,
|
||||
spinnerProps = { size: 40 },
|
||||
|
||||
pagination = false,
|
||||
pagesCount: controlledPageCount,
|
||||
initialPageIndex,
|
||||
initialPageSize,
|
||||
}) {
|
||||
const {
|
||||
getTableProps,
|
||||
@@ -59,18 +64,28 @@ export default function DataTable({
|
||||
isAllRowsExpanded,
|
||||
totalColumnsWidth,
|
||||
|
||||
// page,
|
||||
pageCount,
|
||||
canPreviousPage,
|
||||
canNextPage,
|
||||
gotoPage,
|
||||
previousPage,
|
||||
nextPage,
|
||||
setPageSize,
|
||||
|
||||
// Get the state from the instance
|
||||
state: { pageIndex, pageSize, sortBy, selectedRowIds },
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data: data,
|
||||
initialState: { pageIndex: 0, expanded }, // Pass our hoisted table state
|
||||
manualPagination: true, // Tell the usePagination
|
||||
// hook that we'll handle our own data fetching
|
||||
// This means we'll also have to provide our own
|
||||
// pageCount.
|
||||
// pageCount: controlledPageCount,
|
||||
initialState: {
|
||||
pageIndex: initialPageIndex,
|
||||
pageSize: initialPageSize,
|
||||
expanded,
|
||||
}, // Pass our hoisted table state
|
||||
manualPagination: true,
|
||||
pageCount: controlledPageCount,
|
||||
getSubRows: (row) => row.children,
|
||||
manualSortBy,
|
||||
expandSubRows,
|
||||
@@ -80,10 +95,10 @@ export default function DataTable({
|
||||
useSortBy,
|
||||
useExpanded,
|
||||
useRowSelect,
|
||||
usePagination,
|
||||
useResizeColumns,
|
||||
useFlexLayout,
|
||||
useSticky,
|
||||
usePagination,
|
||||
(hooks) => {
|
||||
hooks.visibleColumns.push((columns) => [
|
||||
// Let's make a column for selection
|
||||
@@ -197,6 +212,7 @@ export default function DataTable({
|
||||
{...cell.getCellProps({
|
||||
className: classnames(cell.column.className || '', 'td'),
|
||||
})}
|
||||
onContextMenu={handleRowContextMenu(cell, row)}
|
||||
>
|
||||
{RenderCell({ cell, row, index })}
|
||||
</div>
|
||||
@@ -337,6 +353,21 @@ export default function DataTable({
|
||||
</ScrollSyncPane>
|
||||
</div>
|
||||
</ScrollSync>
|
||||
|
||||
<If condition={pagination && pageCount}>
|
||||
<Pagination
|
||||
initialPage={pageIndex + 1}
|
||||
total={pageSize * pageCount}
|
||||
size={pageSize}
|
||||
onPageChange={(currentPage) => {
|
||||
gotoPage(currentPage - 1);
|
||||
}}
|
||||
onPageSizeChange={(pageSize, currentPage) => {
|
||||
gotoPage(currentPage - 1);
|
||||
setPageSize(pageSize);
|
||||
}}
|
||||
/>
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
218
client/src/components/Pagination.js
Normal file
218
client/src/components/Pagination.js
Normal file
@@ -0,0 +1,218 @@
|
||||
import React, { useReducer, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Button, ButtonGroup, Intent, HTMLSelect, } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import { range } from 'lodash';
|
||||
import { Icon } from 'components';
|
||||
|
||||
const getState = ({ currentPage, size, total }) => {
|
||||
const totalPages = Math.ceil(total / size);
|
||||
const visibleItems = 5;
|
||||
const halfVisibleItems = Math.ceil(visibleItems / 2);
|
||||
|
||||
// create an array of pages to ng-repeat in the pager control
|
||||
let startPage, endPage;
|
||||
if (totalPages <= visibleItems) {
|
||||
// less than {visibleItems} total pages so show
|
||||
startPage = 1;
|
||||
endPage = totalPages;
|
||||
} else {
|
||||
// more than {visibleItems} total pages so calculate start and end pages
|
||||
if (currentPage <= halfVisibleItems) {
|
||||
startPage = 1;
|
||||
endPage = visibleItems;
|
||||
} else if (currentPage + (halfVisibleItems - 1) >= totalPages) {
|
||||
startPage = totalPages - (visibleItems - 1);
|
||||
endPage = totalPages;
|
||||
} else {
|
||||
startPage = currentPage - halfVisibleItems;
|
||||
endPage = currentPage + halfVisibleItems - 1;
|
||||
}
|
||||
}
|
||||
const pages = [...Array(endPage + 1 - startPage).keys()].map(
|
||||
(i) => startPage + i,
|
||||
);
|
||||
|
||||
// Too large or small currentPage
|
||||
let correctCurrentpage = currentPage;
|
||||
if (currentPage > totalPages) correctCurrentpage = totalPages;
|
||||
if (currentPage <= 0) correctCurrentpage = 1;
|
||||
|
||||
return {
|
||||
currentPage: correctCurrentpage,
|
||||
size,
|
||||
total,
|
||||
pages,
|
||||
totalPages,
|
||||
};
|
||||
};
|
||||
|
||||
const TYPE = {
|
||||
PAGE_CHANGE: 'PAGE_CHANGE',
|
||||
PAGE_SIZE_CHANGE: 'PAGE_SIZE_CHANGE',
|
||||
INITIALIZE: 'INITIALIZE',
|
||||
};
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case TYPE.PAGE_CHANGE:
|
||||
return getState({
|
||||
currentPage: action.page,
|
||||
size: state.size,
|
||||
total: state.total,
|
||||
});
|
||||
case TYPE.PAGE_SIZE_CHANGE:
|
||||
return getState({
|
||||
currentPage: state.currentPage,
|
||||
size: action.size,
|
||||
total: state.total,
|
||||
});
|
||||
case TYPE.INITIALIZE:
|
||||
return getState({
|
||||
currentPage: state.currentPage,
|
||||
size: action.size,
|
||||
total: action.total,
|
||||
});
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
};
|
||||
|
||||
const Pagination = ({
|
||||
initialPage,
|
||||
total,
|
||||
size,
|
||||
pageSizesOptions = [5, 12, 20, 30, 50, 75, 100, 150],
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
}) => {
|
||||
const [state, dispatch] = useReducer(
|
||||
reducer,
|
||||
{ currentPage: initialPage, total, size },
|
||||
getState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: 'INITIALIZE',
|
||||
total,
|
||||
size,
|
||||
});
|
||||
}, [total, size]);
|
||||
|
||||
return (
|
||||
<div class="pagination">
|
||||
<div class="pagination__buttons-group">
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
disabled={state.currentPage === 1}
|
||||
onClick={() => {
|
||||
dispatch({ type: 'PAGE_CHANGE', page: state.currentPage - 1 });
|
||||
onPageChange(state.currentPage - 1);
|
||||
}}
|
||||
minimal={true}
|
||||
className={'pagination__item pagination__item--previous'}
|
||||
icon={<Icon icon={'arrow-back-24'} iconSize={12} />}
|
||||
>
|
||||
<T id='previous' />
|
||||
</Button>
|
||||
|
||||
{state.pages.map((page) => (
|
||||
<Button
|
||||
key={page}
|
||||
intent={state.currentPage === page ? Intent.PRIMARY : Intent.NONE}
|
||||
disabled={state.currentPage === page}
|
||||
onClick={() => {
|
||||
dispatch({ type: 'PAGE_CHANGE', page });
|
||||
onPageChange(page);
|
||||
}}
|
||||
minimal={true}
|
||||
className={classNames(
|
||||
'pagination__item',
|
||||
'pagination__item--page',
|
||||
{
|
||||
'is-active': state.currentPage === page,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{ page }
|
||||
</Button>
|
||||
))}
|
||||
<Button
|
||||
disabled={state.currentPage === state.totalPages}
|
||||
onClick={() => {
|
||||
dispatch({
|
||||
type: 'PAGE_CHANGE',
|
||||
page: state.currentPage + 1
|
||||
});
|
||||
onPageChange(state.currentPage + 1);
|
||||
}}
|
||||
minimal={true}
|
||||
className={'pagination__item pagination__item--next'}
|
||||
icon={<Icon icon={'arrow-forward-24'} iconSize={12} />}
|
||||
>
|
||||
<T id='next' />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
<div class="pagination__controls">
|
||||
<div class="pagination__goto-control">
|
||||
Go to
|
||||
<HTMLSelect
|
||||
minimal={true}
|
||||
options={range(1, state.totalPages + 1)}
|
||||
value={state.currentPage}
|
||||
onChange={(event) => {
|
||||
const page = parseInt(event.currentTarget.value, 10);
|
||||
|
||||
dispatch({ type: 'PAGE_CHANGE', page });
|
||||
onPageChange(page);
|
||||
}}
|
||||
minimal={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pagination__pagesize-control">
|
||||
Page size
|
||||
<HTMLSelect
|
||||
minimal={true}
|
||||
options={pageSizesOptions}
|
||||
value={size}
|
||||
onChange={(event) => {
|
||||
const pageSize = parseInt(event.currentTarget.value, 10);
|
||||
dispatch({ type: 'PAGE_SIZE_CHANGE', size: pageSize });
|
||||
dispatch({ type: 'PAGE_CHANGE', page: 1 });
|
||||
|
||||
onPageSizeChange(pageSize, 1);
|
||||
}}
|
||||
minimal={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pagination__info">
|
||||
<T id={'showing_current_page_to_total'} values={{
|
||||
currentPage: state.currentPage,
|
||||
totalPages: state.totalPages,
|
||||
total: total,
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Pagination.propTypes = {
|
||||
initialPage: PropTypes.number.isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
total: PropTypes.number.isRequired,
|
||||
onPageChange: PropTypes.func,
|
||||
onPageSizeChange: PropTypes.func,
|
||||
};
|
||||
|
||||
Pagination.defaultProps = {
|
||||
initialPage: 1,
|
||||
size: 25,
|
||||
};
|
||||
|
||||
export default Pagination;
|
||||
@@ -10,6 +10,7 @@ import ErrorMessage from './ErrorMessage';
|
||||
import MODIFIER from './modifiers';
|
||||
import FieldHint from './FieldHint';
|
||||
import MenuItemLabel from './MenuItemLabel';
|
||||
import Pagination from './Pagination';
|
||||
|
||||
const Hint = FieldHint;
|
||||
|
||||
@@ -27,5 +28,6 @@ export {
|
||||
FieldHint,
|
||||
Hint,
|
||||
MenuItemLabel,
|
||||
Pagination,
|
||||
// For,
|
||||
};
|
||||
Reference in New Issue
Block a user