From 163aca617989d6baabbd7d66640a18cb533b11a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= <43352493+ridvanakca@users.noreply.github.com> Date: Mon, 21 Aug 2023 22:15:07 +0300 Subject: [PATCH] feat(user-list): add pagination (#1219) * feat(user-list): add pagination * feat: add actual total count in getUsers --------- Co-authored-by: Ali BARIN --- .../backend/src/graphql/queries/get-users.ts | 9 +- packages/backend/src/graphql/schema.graphql | 1 + packages/backend/src/helpers/pagination.ts | 1 + .../UserList/TablePaginationActions/index.tsx | 89 +++++++++ .../web/src/components/UserList/index.tsx | 173 +++++++++++------- packages/web/src/components/UserList/style.ts | 12 ++ packages/web/src/graphql/queries/get-users.ts | 1 + packages/web/src/hooks/useUsers.ts | 31 ++-- 8 files changed, 235 insertions(+), 82 deletions(-) create mode 100644 packages/web/src/components/UserList/TablePaginationActions/index.tsx create mode 100644 packages/web/src/components/UserList/style.ts diff --git a/packages/backend/src/graphql/queries/get-users.ts b/packages/backend/src/graphql/queries/get-users.ts index ad0df2f9..8c0fbb56 100644 --- a/packages/backend/src/graphql/queries/get-users.ts +++ b/packages/backend/src/graphql/queries/get-users.ts @@ -10,15 +10,14 @@ type Params = { const getUsers = async (_parent: unknown, params: Params, context: Context) => { context.currentUser.can('read', 'User'); - const usersQuery = User - .query() + const usersQuery = User.query() .leftJoinRelated({ - role: true + role: true, }) .withGraphFetched({ - role: true + role: true, }) - .orderBy('full_name', 'desc'); + .orderBy('full_name', 'asc'); return paginate(usersQuery, params.limit, params.offset); }; diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index b48daac3..fa08d89b 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -341,6 +341,7 @@ type SamlAuthProvidersRoleMapping { type UserConnection { edges: [UserEdge] pageInfo: PageInfo + totalCount: Int } type UserEdge { diff --git a/packages/backend/src/helpers/pagination.ts b/packages/backend/src/helpers/pagination.ts index 5d1c3981..77916052 100644 --- a/packages/backend/src/helpers/pagination.ts +++ b/packages/backend/src/helpers/pagination.ts @@ -21,6 +21,7 @@ const paginate = async ( currentPage: Math.ceil(offset / limit + 1), totalPages: Math.ceil(count / limit), }, + totalCount: count, edges: records.map((record: Base) => ({ node: record, })), diff --git a/packages/web/src/components/UserList/TablePaginationActions/index.tsx b/packages/web/src/components/UserList/TablePaginationActions/index.tsx new file mode 100644 index 00000000..e143c8fb --- /dev/null +++ b/packages/web/src/components/UserList/TablePaginationActions/index.tsx @@ -0,0 +1,89 @@ +import { useTheme } from '@mui/material'; +import IconButton from '@mui/material/IconButton'; +import FirstPageIcon from '@mui/icons-material/FirstPage'; +import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft'; +import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight'; +import LastPageIcon from '@mui/icons-material/LastPage'; +import Box from '@mui/material/Box'; + +interface TablePaginationActionsProps { + count: number; + page: number; + rowsPerPage: number; + onPageChange: ( + event: React.MouseEvent, + newPage: number + ) => void; +} + +export default function TablePaginationActions( + props: TablePaginationActionsProps +) { + const theme = useTheme(); + const { count, page, rowsPerPage, onPageChange } = props; + + const handleFirstPageButtonClick = ( + event: React.MouseEvent + ) => { + onPageChange(event, 0); + }; + + const handleBackButtonClick = ( + event: React.MouseEvent + ) => { + onPageChange(event, page - 1); + }; + + const handleNextButtonClick = ( + event: React.MouseEvent + ) => { + onPageChange(event, page + 1); + }; + + const handleLastPageButtonClick = ( + event: React.MouseEvent + ) => { + onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); + }; + + return ( + + + {theme.direction === 'rtl' ? : } + + + {theme.direction === 'rtl' ? ( + + ) : ( + + )} + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="next page" + > + {theme.direction === 'rtl' ? ( + + ) : ( + + )} + + = Math.ceil(count / rowsPerPage) - 1} + aria-label="last page" + > + {theme.direction === 'rtl' ? : } + + + ); +} diff --git a/packages/web/src/components/UserList/index.tsx b/packages/web/src/components/UserList/index.tsx index f4a089ce..dd39c201 100644 --- a/packages/web/src/components/UserList/index.tsx +++ b/packages/web/src/components/UserList/index.tsx @@ -11,89 +11,132 @@ import Paper from '@mui/material/Paper'; import IconButton from '@mui/material/IconButton'; import Typography from '@mui/material/Typography'; import EditIcon from '@mui/icons-material/Edit'; +import TableFooter from '@mui/material/TableFooter'; import DeleteUserButton from 'components/DeleteUserButton/index.ee'; import ListLoader from 'components/ListLoader'; import useUsers from 'hooks/useUsers'; import useFormatMessage from 'hooks/useFormatMessage'; import * as URLS from 'config/urls'; +import TablePaginationActions from './TablePaginationActions'; +import { TablePagination } from './style'; export default function UserList(): React.ReactElement { const formatMessage = useFormatMessage(); - const { users, loading } = useUsers(); + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(10); + const { + users, + pageInfo, + totalCount, + loading, + } = useUsers(page, rowsPerPage); + + const handleChangePage = ( + event: React.MouseEvent | null, + newPage: number + ) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = ( + event: React.ChangeEvent + ) => { + setRowsPerPage(+event.target.value); + setPage(0); + }; return ( - - - - - - - {formatMessage('userList.fullName')} - - + <> + +
+ + + + + {formatMessage('userList.fullName')} + + - - - {formatMessage('userList.email')} - - + + + {formatMessage('userList.email')} + + - - - {formatMessage('userList.role')} - - + + + {formatMessage('userList.role')} + + - - - - - {loading && } - {!loading && - users.map((user) => ( - - - {user.fullName} - + + + + + {loading && } + {!loading && + users.map((user) => ( + + + {user.fullName} + - - {user.email} - + + {user.email} + - - {user.role.name} - + + + {user.role.name} + + - - - - - + + + + + - - - + + + + + ))} + + {totalCount && ( + + + - ))} - -
-
+ + )} + + + ); } diff --git a/packages/web/src/components/UserList/style.ts b/packages/web/src/components/UserList/style.ts new file mode 100644 index 00000000..aa02dabf --- /dev/null +++ b/packages/web/src/components/UserList/style.ts @@ -0,0 +1,12 @@ +import { styled } from '@mui/material/styles'; +import MuiTablePagination, { + tablePaginationClasses, +} from '@mui/material/TablePagination'; + +export const TablePagination = styled(MuiTablePagination)(() => ({ + [`& .${tablePaginationClasses.selectLabel}, & .${tablePaginationClasses.displayedRows}`]: + { + fontWeight: 400, + fontSize: 14, + }, +})); diff --git a/packages/web/src/graphql/queries/get-users.ts b/packages/web/src/graphql/queries/get-users.ts index 08612857..76a3bac7 100644 --- a/packages/web/src/graphql/queries/get-users.ts +++ b/packages/web/src/graphql/queries/get-users.ts @@ -13,6 +13,7 @@ export const GET_USERS = gql` currentPage totalPages } + totalCount edges { node { id diff --git a/packages/web/src/hooks/useUsers.ts b/packages/web/src/hooks/useUsers.ts index 66970992..f1496194 100644 --- a/packages/web/src/hooks/useUsers.ts +++ b/packages/web/src/hooks/useUsers.ts @@ -4,30 +4,37 @@ import { IUser } from '@automatisch/types'; import { GET_USERS } from 'graphql/queries/get-users'; type Edge = { - node: IUser -} + node: IUser; +}; type QueryResponse = { getUsers: { pageInfo: { currentPage: number; totalPages: number; - } - edges: Edge[] - } -} + }; + totalCount: number; + edges: Edge[]; + }; +}; -export default function useUsers() { +const getLimitAndOffset = (page: number, rowsPerPage: number) => ({ + limit: rowsPerPage, + offset: page * rowsPerPage, +}); + +export default function useUsers(page: number, rowsPerPage: number) { const { data, loading } = useQuery(GET_USERS, { - variables: { - limit: 100, - offset: 0 - } + variables: getLimitAndOffset(page, rowsPerPage), }); const users = data?.getUsers.edges.map(({ node }) => node) || []; + const pageInfo = data?.getUsers.pageInfo; + const totalCount = data?.getUsers.totalCount; return { users, - loading + pageInfo, + totalCount, + loading, }; }