2021-05-21 00:50:55 -07:00
|
|
|
import React from "react";
|
2021-06-21 10:54:31 -07:00
|
|
|
import { Box, Button, Flex, Select } from "@chakra-ui/react";
|
|
|
|
import { Link, useHistory, useLocation } from "react-router-dom";
|
2021-05-21 00:50:55 -07:00
|
|
|
|
2021-06-21 10:21:25 -07:00
|
|
|
const PER_PAGE = 30;
|
|
|
|
|
2021-05-21 00:50:55 -07:00
|
|
|
function PaginationToolbar({ isLoading, totalCount, ...props }) {
|
|
|
|
const { search } = useLocation();
|
2021-06-21 10:54:31 -07:00
|
|
|
const history = useHistory();
|
2021-05-21 00:50:55 -07:00
|
|
|
|
|
|
|
const currentOffset =
|
|
|
|
parseInt(new URLSearchParams(search).get("offset")) || 0;
|
|
|
|
|
2021-06-21 10:21:25 -07:00
|
|
|
const currentPageIndex = Math.floor(currentOffset / PER_PAGE);
|
|
|
|
const currentPageNumber = currentPageIndex + 1;
|
|
|
|
const numTotalPages = totalCount ? Math.ceil(totalCount / PER_PAGE) : null;
|
|
|
|
|
2021-05-21 00:50:55 -07:00
|
|
|
const prevPageSearchParams = new URLSearchParams(search);
|
2021-06-21 10:21:25 -07:00
|
|
|
const prevPageOffset = currentOffset - PER_PAGE;
|
2021-05-21 00:50:55 -07:00
|
|
|
prevPageSearchParams.set("offset", prevPageOffset);
|
|
|
|
const prevPageUrl = "?" + prevPageSearchParams.toString();
|
|
|
|
|
|
|
|
const nextPageSearchParams = new URLSearchParams(search);
|
2021-06-21 10:21:25 -07:00
|
|
|
const nextPageOffset = currentOffset + PER_PAGE;
|
2021-05-21 00:50:55 -07:00
|
|
|
nextPageSearchParams.set("offset", nextPageOffset);
|
|
|
|
const nextPageUrl = "?" + nextPageSearchParams.toString();
|
2021-06-21 13:50:49 -07:00
|
|
|
|
|
|
|
// We disable the buttons if we don't know how many total items there are,
|
|
|
|
// and therefore don't know how far navigation can go. We'll additionally
|
|
|
|
// show a loading spinner if `isLoading` is true. (But it's possible the
|
|
|
|
// buttons might be enabled, even if `isLoading` is true, because maybe
|
|
|
|
// something _else_ is loading. `isLoading` is designed to tell us whether
|
|
|
|
// waiting _might_ give us the data we need!)
|
|
|
|
const prevPageIsDisabled = totalCount == null || prevPageOffset < 0;
|
|
|
|
const nextPageIsDisabled = totalCount == null || nextPageOffset >= totalCount;
|
2021-05-21 00:50:55 -07:00
|
|
|
|
2021-06-21 10:54:31 -07:00
|
|
|
const goToPageNumber = React.useCallback(
|
|
|
|
(newPageNumber) => {
|
|
|
|
const newPageIndex = newPageNumber - 1;
|
|
|
|
const newPageOffset = newPageIndex * PER_PAGE;
|
|
|
|
|
|
|
|
const newPageSearchParams = new URLSearchParams(search);
|
|
|
|
newPageSearchParams.set("offset", newPageOffset);
|
|
|
|
history.push({ search: newPageSearchParams.toString() });
|
|
|
|
},
|
|
|
|
[search, history]
|
|
|
|
);
|
|
|
|
|
2021-05-21 00:50:55 -07:00
|
|
|
return (
|
2021-06-21 10:21:25 -07:00
|
|
|
<Flex align="center" justify="space-between" {...props}>
|
2021-05-21 00:50:55 -07:00
|
|
|
<Button
|
|
|
|
as={prevPageIsDisabled ? "button" : Link}
|
|
|
|
to={prevPageIsDisabled ? undefined : prevPageUrl}
|
|
|
|
_disabled={{ cursor: isLoading ? "wait" : "not-allowed", opacity: 0.4 }}
|
|
|
|
isDisabled={prevPageIsDisabled}
|
|
|
|
>
|
|
|
|
← Prev
|
|
|
|
</Button>
|
2021-06-21 10:21:25 -07:00
|
|
|
{numTotalPages && (
|
2021-06-21 10:54:31 -07:00
|
|
|
<Flex align="center">
|
|
|
|
<Box flex="0 0 auto">Page</Box>
|
|
|
|
<Box width="1" />
|
|
|
|
<PageNumberSelect
|
|
|
|
currentPageNumber={currentPageNumber}
|
|
|
|
numTotalPages={numTotalPages}
|
|
|
|
onChange={goToPageNumber}
|
|
|
|
marginBottom="-2px"
|
|
|
|
/>
|
|
|
|
<Box width="1" />
|
|
|
|
<Box flex="0 0 auto">of {numTotalPages}</Box>
|
|
|
|
</Flex>
|
2021-06-21 10:21:25 -07:00
|
|
|
)}
|
2021-05-21 00:50:55 -07:00
|
|
|
<Button
|
|
|
|
as={nextPageIsDisabled ? "button" : Link}
|
|
|
|
to={nextPageIsDisabled ? undefined : nextPageUrl}
|
|
|
|
_disabled={{ cursor: isLoading ? "wait" : "not-allowed", opacity: 0.4 }}
|
|
|
|
isDisabled={nextPageIsDisabled}
|
|
|
|
>
|
|
|
|
Next →
|
|
|
|
</Button>
|
|
|
|
</Flex>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-06-21 10:54:31 -07:00
|
|
|
function PageNumberSelect({
|
|
|
|
currentPageNumber,
|
|
|
|
numTotalPages,
|
|
|
|
onChange,
|
|
|
|
...props
|
|
|
|
}) {
|
|
|
|
const allPageNumbers = Array.from({ length: numTotalPages }, (_, i) => i + 1);
|
|
|
|
|
|
|
|
const handleChange = React.useCallback(
|
|
|
|
(e) => onChange(Number(e.target.value)),
|
|
|
|
[onChange]
|
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Select
|
|
|
|
value={currentPageNumber}
|
|
|
|
onChange={handleChange}
|
|
|
|
width="7ch"
|
|
|
|
variant="flushed"
|
|
|
|
textAlign="center"
|
|
|
|
{...props}
|
|
|
|
>
|
|
|
|
{allPageNumbers.map((pageNumber) => (
|
|
|
|
<option key={pageNumber} value={pageNumber}>
|
|
|
|
{pageNumber}
|
|
|
|
</option>
|
|
|
|
))}
|
|
|
|
</Select>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-05-21 00:50:55 -07:00
|
|
|
export default PaginationToolbar;
|