Skip to content

Commit

Permalink
MMT-3561: Adding Pagination to Table Component (#1110)
Browse files Browse the repository at this point in the history
* MMT-3492: Saving progress on table component

* MMT-3492: Adjusting RenderRows

* MMT-3492: Finish writing tests

* MMT-3492: Addressing PR Comments

* MMT-3492: Fixing 'data' structure

* MMT-3561: Adding Pagination to Table

* MMT-3492: eslint error fixes

* MMT-3492: reverting changes to ManagePage

* MMT-3561: Adding Pagination to Table

* MMT-3561: Adding tests

* MMT-3492: Fixing shortName and title

* MMT-3561: Fixing conflict

* MMT-3561: Saving progress on limit and offset

* MMT-3561: Adding offset and limit

* MMT-3492: Editing table and ManagePage

* MMT-3492: Trying to get tests to run on github

* MMT-3492: Addressing comments

* MMT-3492: Changing all conceptTypes to ummMetadata

* MMT-3492: lint

* MMT-3561: Writing tests for pagination

* MMT-3561: Addressing Comments
  • Loading branch information
mandyparson authored Feb 5, 2024
1 parent 18d0d12 commit 962742f
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 46 deletions.
18 changes: 15 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion static/src/css/vendor/bootstrap/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
// @import "bootstrap/scss/card";
@import "bootstrap/scss/breadcrumb";
@import "bootstrap/scss/accordion";
// @import "bootstrap/scss/pagination";
@import "bootstrap/scss/pagination";
@import "bootstrap/scss/badge";
@import "bootstrap/scss/alert";
// @import "bootstrap/scss/progress";
Expand Down
36 changes: 17 additions & 19 deletions static/src/js/components/DraftList/DraftList.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React from 'react'
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { Link, useParams } from 'react-router-dom'
import { useLazyQuery } from '@apollo/client'
import { FaFileDownload } from 'react-icons/fa'
import pluralize from 'pluralize'
import Col from 'react-bootstrap/Col'
import Placeholder from 'react-bootstrap/Placeholder'
import Row from 'react-bootstrap/Row'
Expand All @@ -26,10 +25,17 @@ const DraftList = ({ draftType }) => {
const { providerId } = user
const { draftType: paramDraftType } = useParams()

const { drafts, error, loading } = useDraftsQuery({ draftType })
const [offset, setOffset] = useState(0)
const limit = 20

const { drafts, error, loading } = useDraftsQuery({
draftType,
offset,
limit
})
const { count, items = [] } = drafts

const noDraftsError = `No ${draftType} drafts exist for the provider ${providerId}`
const noDataError = `No ${draftType} drafts exist for the provider ${providerId}`

const [downloadDraft] = useLazyQuery(DOWNLOAD_DRAFT, {
onCompleted: (getDraftData) => {
Expand Down Expand Up @@ -141,33 +147,25 @@ const DraftList = ({ draftType }) => {
<>
{
loading
? (
&& (
<span className="d-block mb-3">
<Placeholder as="span" animation="glow">
<Placeholder xs={2} />
</Placeholder>
</span>
)
: (
<span className="d-block mb-3">
Showing
{' '}
{count > 0 && 'all'}
{' '}
{count}
{' '}
{draftType}
{' '}
{pluralize('Draft', count)}
</span>
)
}
<Table
headers={['Short Name', 'Entry Title', 'Last Modified', 'Actions']}
classNames={['col-md-4', 'col-md-4', 'col-auto', 'col-auto']}
loading={loading}
data={data}
error={noDraftsError}
error={error}
noDataError={noDataError}
count={count}
setOffset={setOffset}
limit={limit}
offset={offset}
/>
</>
)
Expand Down
143 changes: 143 additions & 0 deletions static/src/js/components/Pagination/Pagination.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import Pagination from 'react-bootstrap/Pagination'
import Row from 'react-bootstrap/Row'
import Col from 'react-bootstrap/Col'

/**
* Table
* @typedef {Object} PaginationComponent
* @property {Number} limit A number that is set in parent element of table
* @property {Number} count A number that indicates how many results are in the total query
* @property {function} setOffset A function that resets the offset of results to come back
*/

const PaginationComponent = ({
limit,
count,
setOffset
}) => {
const [pageNum, setPageNum] = useState(1)

const lastPageNum = parseInt(Math.ceil(count / limit), 10)

const defaultPaginationStyles = {
minWidth: '2.5rem',
textAlign: 'center'
}

const handleItemClick = (currentPage) => {
setPageNum(currentPage)
setOffset((currentPage - 1) * limit)
}

const generatePaginationItems = () => {
// Only show 3 pages, the current page and one before or after (within the valid range of pages)
const pages = [pageNum - 1, pageNum, pageNum + 1]
.filter((page) => page >= 1 && page <= lastPageNum)

const returnItems = []

// If the first page is not 1, add the pagination item for page 1
if (pages[0] !== 1) {
returnItems.push(
<Pagination.Item
key="page-1"
onClick={() => handleItemClick(1)}
active={pageNum === 1}
style={defaultPaginationStyles}
>
{1}
</Pagination.Item>
)

// And if the first page is not 2, add an ellipsis
if (pages[0] !== 2) {
returnItems.push(
<Pagination.Ellipsis
key="page-ellipsis-1"
disabled
/>
)
}
}

pages.forEach((page) => {
returnItems.push(
<Pagination.Item
key={`page-${page}`}
onClick={() => handleItemClick(page)}
active={page === pageNum}
style={defaultPaginationStyles}
>
{page}
</Pagination.Item>
)
})

// If the last page is not lastPageNum, add the pagination item for the lastPageNum
if (pages[pages.length - 1] !== lastPageNum) {
// And if the last page is not lastPageNum - 1, add an ellipsis
if (pages[pages.length - 1] !== lastPageNum - 1) {
returnItems.push(
<Pagination.Ellipsis
key="page-ellipsis-2"
disabled
/>
)
}

returnItems.push(
<Pagination.Item
key={`page-${lastPageNum}`}
onClick={() => handleItemClick(lastPageNum)}
active={pageNum === lastPageNum}
style={defaultPaginationStyles}
>
{lastPageNum}
</Pagination.Item>
)
}

return returnItems
}

const handlePageChange = (direction) => {
const newCurrentPage = pageNum + direction

setPageNum(newCurrentPage)
setOffset((newCurrentPage - 1) * limit)
}

return (
<Row>
<Col xs="auto">
<div className="mx-auto">
<Pagination>
<Pagination.Prev
disabled={pageNum === 1}
onClick={() => handlePageChange(-1)}
/>
{generatePaginationItems()}
<Pagination.Next
onClick={() => handlePageChange(1)}
disabled={pageNum >= lastPageNum}
/>
</Pagination>
</div>
</Col>
</Row>
)
}

PaginationComponent.defaultProps = {
count: null
}

PaginationComponent.propTypes = {
setOffset: PropTypes.func.isRequired,
limit: PropTypes.number.isRequired,
count: PropTypes.number
}

export default PaginationComponent
61 changes: 61 additions & 0 deletions static/src/js/components/Pagination/__tests__/Pagination.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react'
import {
render,
screen,
fireEvent
} from '@testing-library/react'
import userEvent from '@testing-library/user-event'

import PaginationComponent from '../Pagination'

const setOffset = jest.fn()

const setup = () => {
const props = {
limit: 2,
count: 14,
setOffset
}
render(
<PaginationComponent {...props} />
)

return {
props,
user: userEvent.setup()
}
}

describe('Pagination', () => {
describe('when pagination component is passed count < limit', () => {
test('renders pagination bar', () => {
setup()

expect(screen.queryAllByRole('button')).toHaveLength(3)

// Check individual buttons work
fireEvent.click(screen.getByRole('button', { name: '2' }))

// Check the next button works
fireEvent.click(screen.getByRole('button', { name: 'Next' }))

// // Click on Previous Page
fireEvent.click(screen.getByRole('button', { name: 'Previous' }))

// // Check pages[0] always stays at 1 and two ellipsis render
fireEvent.click(screen.getByRole('button', { name: 'Next' }))
fireEvent.click(screen.getByRole('button', { name: '4' }))
expect(screen.getByRole('button', { name: '1' }))
expect(screen.queryAllByText('More')).toHaveLength(2)

// Make sure onclick for pages[0] function above works
fireEvent.click(screen.getByRole('button', { name: '1' }))

// Can click on last page
fireEvent.click(screen.getByRole('button', { name: '7' }))
fireEvent.click(screen.getByRole('button', { name: 'Previous' }))
fireEvent.click(screen.getByRole('button', { name: 'Previous' }))
expect(screen.queryAllByText('More')).toHaveLength(1)
})
})
})
Loading

0 comments on commit 962742f

Please sign in to comment.