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',