diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/PageSize.js b/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/PageSize.js index 434a1614aa..8f878178c2 100644 --- a/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/PageSize.js +++ b/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/PageSize.js @@ -17,6 +17,7 @@ const PageSize = () => { const handleChange = e => { setQuery({ pageSize: e, + page: 1, }); }; diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/Pagination.js b/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/Pagination.js new file mode 100644 index 0000000000..cceed00375 --- /dev/null +++ b/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/Pagination.js @@ -0,0 +1,142 @@ +/** + * Pagination + * + * The component works as follows + * `1` , 2, 3, ... 10 + * 1, `2`, 3, ... 10 + * 1, 2, `3`, 4, ... 10 + * 1, 2, 3, `4`, 5, ... 10 + * 1, ..,4, `5`, 6, ... 10 + * + * 1, ...., 8, 9, `10` + * 1, ...., 8, `9`, 10 + * 1, ...., 7, `8`, 9, 10 + * 1, ... 6, `7`, 8, 9, 10 + */ + +import React from 'react'; +import { + NextLink, + Pagination as PaginationCompo, + PreviousLink, + Dots, + PageLink, +} from '@strapi/parts'; +import PropTypes from 'prop-types'; +import { useQueryParams } from '@strapi/helper-plugin'; +import { useLocation } from 'react-router-dom'; +import { stringify } from 'qs'; + +const Pagination = ({ pagination: { pageCount } }) => { + const [{ query }] = useQueryParams(); + const activePage = parseInt(query.page, 10); + const { pathname } = useLocation(); + const makeSearch = page => stringify({ ...query, page }, { encode: false }); + const nextSearch = makeSearch(activePage + (pageCount > 1 ? 1 : 0)); + + const previousSearch = makeSearch(activePage - 1); + + const firstLinks = [ + + Go to page 1 + , + ]; + + let firstLinksToCreate = []; + let lastLinks = []; + let lastLinksToCreate = []; + const middleLinks = []; + + if (pageCount > 1) { + lastLinks.push( + + Go to page {pageCount} + + ); + } + + if (activePage === 1 && pageCount >= 3) { + firstLinksToCreate = [2]; + } + + if (activePage === 2 && pageCount >= 3) { + firstLinksToCreate = pageCount === 5 ? [2, 3, 4] : [2, 3]; + } + + if (activePage === 4 && pageCount >= 3) { + firstLinksToCreate = [2]; + } + + if (activePage === pageCount && pageCount >= 3) { + lastLinksToCreate = [pageCount - 1]; + } + + if (activePage === pageCount - 2 && pageCount >= 3) { + lastLinksToCreate = [activePage + 1, activePage, activePage - 1]; + } + + if (activePage === pageCount - 3 && pageCount >= 3 && activePage > 5) { + lastLinksToCreate = [activePage + 2, activePage + 1, activePage, activePage - 1]; + } + + if (activePage === pageCount - 1 && pageCount >= 3) { + lastLinksToCreate = [activePage, activePage - 1]; + } + + lastLinksToCreate.forEach(number => { + lastLinks.unshift( + + Go to page {number} + + ); + }); + + firstLinksToCreate.forEach(number => { + firstLinks.push( + + Go to page {number} + + ); + }); + + if (![1, 2].includes(activePage) && activePage < pageCount - 3) { + const middleLinksToCreate = [activePage - 1, activePage, activePage + 1]; + + middleLinksToCreate.forEach(number => { + middleLinks.push( + + Go to page {number} + + ); + }); + } + + const shouldShowDotsAfterFirstLink = + pageCount > 5 || (pageCount === 5 && (activePage === 1 || activePage === 5)); + const shouldShowMiddleDots = middleLinks.length > 2 && activePage > 4 && pageCount > 5; + + const beforeDotsLinks = shouldShowMiddleDots + ? pageCount - activePage - 1 + : pageCount - firstLinks.length - lastLinks.length; + const afterDotsLength = shouldShowMiddleDots + ? pageCount - firstLinks.length - lastLinks.length + : pageCount - activePage - 1; + + return ( + + Go to previous page + {firstLinks} + {shouldShowMiddleDots && And {beforeDotsLinks} links} + {middleLinks} + {shouldShowDotsAfterFirstLink && And {afterDotsLength} links} + {lastLinks} + Go to next page + + ); +}; + +Pagination.propTypes = { + pagination: PropTypes.shape({ pageCount: PropTypes.number.isRequired }).isRequired, +}; + +export default Pagination; diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/index.js b/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/index.js index 767a35abd7..5190067338 100644 --- a/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/index.js +++ b/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/index.js @@ -1,32 +1,35 @@ import React from 'react'; -import { Box, Row, NextLink, Pagination, PreviousLink, Dots, PageLink } from '@strapi/parts'; +import PropTypes from 'prop-types'; +import { Box, Row } from '@strapi/parts'; +import Pagination from './Pagination'; import PageSize from './PageSize'; -const PaginationFooter = () => { +const PaginationFooter = ({ pagination }) => { return ( - - Go to previous page - - Go to page 1 - - - Go to page 2 - - And 23 other links - - Go to page 3 - - - Go to page 26 - - Go to next page - + ); }; +PaginationFooter.defaultProps = { + pagination: { + pageCount: 0, + pageSize: 10, + total: 0, + }, +}; + +PaginationFooter.propTypes = { + pagination: PropTypes.shape({ + page: PropTypes.number, + pageCount: PropTypes.number, + pageSize: PropTypes.number, + total: PropTypes.number, + }), +}; + export default PaginationFooter; diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/tests/__snapshots__/index.test.js.snap b/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/tests/__snapshots__/index.test.js.snap new file mode 100644 index 0000000000..1f11ca712b --- /dev/null +++ b/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/tests/__snapshots__/index.test.js.snap @@ -0,0 +1,467 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DynamicTable renders and matches the snapshot 1`] = ` +.c25 { + border: 0; + -webkit-clip: rect(0 0 0 0); + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +.c4 { + font-weight: 500; + font-size: 0.75rem; + line-height: 1.33; + color: #32324d; +} + +.c9 { + font-weight: 400; + font-size: 0.875rem; + line-height: 1.43; + color: #32324d; +} + +.c13 { + font-weight: 400; + font-size: 0.875rem; + line-height: 1.43; + color: #8e8ea9; +} + +.c23 { + font-weight: 400; + font-size: 0.75rem; + line-height: 1.33; + color: #32324d; +} + +.c0 { + padding-top: 24px; +} + +.c8 { + padding-right: 16px; + padding-left: 16px; +} + +.c10 { + padding-left: 12px; +} + +.c1 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c2 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c18 { + border: 0; + -webkit-clip: rect(0 0 0 0); + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +.c3 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.c3 > * { + margin-top: 0; + margin-bottom: 0; +} + +.c3 > * + * { + margin-top: 0px; +} + +.c15 > * + * { + margin-left: 4px; +} + +.c21 { + line-height: revert; +} + +.c16 { + padding: 12px; + border-radius: 4px; + -webkit-text-decoration: none; + text-decoration: none; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.c19 { + padding: 12px; + border-radius: 4px; + box-shadow: 0px 1px 4px rgba(33,33,52,0.1); + -webkit-text-decoration: none; + text-decoration: none; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.c20 { + color: #271fe0; + background: #ffffff; +} + +.c20:hover { + box-shadow: 0px 1px 4px rgba(33,33,52,0.1); +} + +.c22 { + color: #32324d; +} + +.c22:hover { + box-shadow: 0px 1px 4px rgba(33,33,52,0.1); +} + +.c17 { + font-size: 0.7rem; + pointer-events: none; +} + +.c17 svg path { + fill: #c0c0cf; +} + +.c17:focus svg path, +.c17:hover svg path { + fill: #c0c0cf; +} + +.c24 { + font-size: 0.7rem; +} + +.c24 svg path { + fill: #666687; +} + +.c24:focus svg path, +.c24:hover svg path { + fill: #4a4a6a; +} + +.c6 { + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 0; + width: 100%; + background: transparent; + border: none; +} + +.c6:focus { + outline: none; +} + +.c5 { + position: relative; + border: 1px solid #dcdce4; + padding-right: 12px; + border-radius: 4px; + background: #ffffff; + overflow: hidden; +} + +.c5:focus-within { + border: 1px solid #4945ff; +} + +.c11 { + background: transparent; + border: none; + position: relative; + z-index: 1; +} + +.c11 svg { + height: 0.6875rem; + width: 0.6875rem; +} + +.c11 svg path { + fill: #666687; +} + +.c12 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + background: none; + border: none; +} + +.c12 svg { + width: 0.375rem; +} + +.c7 { + min-height: 2.5rem; +} + +.c14 { + margin-left: 5px; +} + +
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+ +
+

+

+

+ +`; diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/tests/index.test.js b/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/tests/index.test.js new file mode 100644 index 0000000000..8981903c1d --- /dev/null +++ b/packages/core/admin/admin/src/pages/Users/ListPage/PaginationFooter/tests/index.test.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { IntlProvider } from 'react-intl'; +import { createMemoryHistory } from 'history'; +import { Router, Route } from 'react-router-dom'; +import Theme from '../../../../../components/Theme'; +import PaginationFooter from '../index'; + +const makeApp = (history, pagination) => { + return ( + + + + + + + + + + ); +}; + +describe('DynamicTable', () => { + it('renders and matches the snapshot', () => { + const history = createMemoryHistory(); + const pagination = { pageCount: 2 }; + history.push('/settings/user?pageSize=10&page=1&sort=firstname'); + const app = makeApp(history, pagination); + + const { container } = render(app); + + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/index.js b/packages/core/admin/admin/src/pages/Users/ListPage/index.js index a5c1f4b3d1..c256c1d741 100644 --- a/packages/core/admin/admin/src/pages/Users/ListPage/index.js +++ b/packages/core/admin/admin/src/pages/Users/ListPage/index.js @@ -281,7 +281,7 @@ const ListPage = () => { withBulkActions withMainAction={canDelete} /> - + )} diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/tests/index.test.js b/packages/core/admin/admin/src/pages/Users/ListPage/tests/index.test.js index 52a1798a70..22ff08826c 100644 --- a/packages/core/admin/admin/src/pages/Users/ListPage/tests/index.test.js +++ b/packages/core/admin/admin/src/pages/Users/ListPage/tests/index.test.js @@ -51,7 +51,10 @@ describe('ADMIN | Pages | USERS | ListPage', () => { afterEach(() => server.resetHandlers()); - afterAll(() => server.close()); + afterAll(() => { + server.close(); + jest.resetAllMocks(); + }); it('renders and matches the snapshot', () => { const history = createMemoryHistory(); @@ -370,8 +373,8 @@ describe('ADMIN | Pages | USERS | ListPage', () => { const { getByText } = render(app); await waitFor(() => { - expect(getByText('soup soup')).toBeInTheDocument(); - expect(getByText('dummy dummy')).toBeInTheDocument(); + expect(getByText('soup')).toBeInTheDocument(); + expect(getByText('dummy')).toBeInTheDocument(); expect(getByText('Active')).toBeInTheDocument(); expect(getByText('Inactive')).toBeInTheDocument(); }); diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/tests/utils/server.js b/packages/core/admin/admin/src/pages/Users/ListPage/tests/utils/server.js index 8aae44cc6b..48d4eb5e5d 100644 --- a/packages/core/admin/admin/src/pages/Users/ListPage/tests/utils/server.js +++ b/packages/core/admin/admin/src/pages/Users/ListPage/tests/utils/server.js @@ -17,7 +17,7 @@ const handlers = [ firstname: 'soup', id: 1, isActive: true, - lastname: 'soup', + lastname: 'soupette', roles: [ { id: 1, @@ -30,7 +30,7 @@ const handlers = [ firstname: 'dummy', id: 2, isActive: false, - lastname: 'dummy', + lastname: 'dum test', roles: [ { id: 1, diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/utils/tableHeaders.js b/packages/core/admin/admin/src/pages/Users/ListPage/utils/tableHeaders.js index c97a16c378..dcc77adf31 100644 --- a/packages/core/admin/admin/src/pages/Users/ListPage/utils/tableHeaders.js +++ b/packages/core/admin/admin/src/pages/Users/ListPage/utils/tableHeaders.js @@ -6,15 +6,12 @@ const tableHeaders = [ { name: 'firstname', key: 'firstname', - metadatas: { label: 'Name', sortable: true }, - // eslint-disable-next-line react/prop-types - cellFormatter: ({ firstname, lastname }) => { - return ( - - {firstname} {lastname} - - ); - }, + metadatas: { label: 'Firstname', sortable: true }, + }, + { + name: 'lastname', + key: 'lastname', + metadatas: { label: 'Lastname', sortable: true }, }, { key: 'email',