feat(user-list): add pagination (#1219)

* feat(user-list): add pagination

* feat: add actual total count in getUsers

---------

Co-authored-by: Ali BARIN <ali.barin53@gmail.com>
This commit is contained in:
Rıdvan Akca
2023-08-21 22:15:07 +03:00
committed by GitHub
parent cb06d3b0ae
commit 163aca6179
8 changed files with 235 additions and 82 deletions

View File

@@ -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);
};

View File

@@ -341,6 +341,7 @@ type SamlAuthProvidersRoleMapping {
type UserConnection {
edges: [UserEdge]
pageInfo: PageInfo
totalCount: Int
}
type UserEdge {

View File

@@ -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,
})),

View File

@@ -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<HTMLButtonElement>,
newPage: number
) => void;
}
export default function TablePaginationActions(
props: TablePaginationActionsProps
) {
const theme = useTheme();
const { count, page, rowsPerPage, onPageChange } = props;
const handleFirstPageButtonClick = (
event: React.MouseEvent<HTMLButtonElement>
) => {
onPageChange(event, 0);
};
const handleBackButtonClick = (
event: React.MouseEvent<HTMLButtonElement>
) => {
onPageChange(event, page - 1);
};
const handleNextButtonClick = (
event: React.MouseEvent<HTMLButtonElement>
) => {
onPageChange(event, page + 1);
};
const handleLastPageButtonClick = (
event: React.MouseEvent<HTMLButtonElement>
) => {
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
};
return (
<Box sx={{ flexShrink: 0, ml: 2.5 }}>
<IconButton
onClick={handleFirstPageButtonClick}
disabled={page === 0}
aria-label="first page"
>
{theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />}
</IconButton>
<IconButton
onClick={handleBackButtonClick}
disabled={page === 0}
aria-label="previous page"
>
{theme.direction === 'rtl' ? (
<KeyboardArrowRight />
) : (
<KeyboardArrowLeft />
)}
</IconButton>
<IconButton
onClick={handleNextButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="next page"
>
{theme.direction === 'rtl' ? (
<KeyboardArrowLeft />
) : (
<KeyboardArrowRight />
)}
</IconButton>
<IconButton
onClick={handleLastPageButtonClick}
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
aria-label="last page"
>
{theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />}
</IconButton>
</Box>
);
}

View File

@@ -11,18 +11,43 @@ 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<HTMLButtonElement> | null,
newPage: number
) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setRowsPerPage(+event.target.value);
setPage(0);
};
return (
<>
<TableContainer component={Paper}>
<Table>
<TableHead>
@@ -74,7 +99,9 @@ export default function UserList(): React.ReactElement {
</TableCell>
<TableCell>
<Typography variant="subtitle2">{user.role.name}</Typography>
<Typography variant="subtitle2">
{user.role.name}
</Typography>
</TableCell>
<TableCell>
@@ -93,7 +120,23 @@ export default function UserList(): React.ReactElement {
</TableRow>
))}
</TableBody>
{totalCount && (
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[10, 25, 50, 100]}
page={page}
count={totalCount}
onPageChange={handleChangePage}
rowsPerPage={rowsPerPage}
onRowsPerPageChange={handleChangeRowsPerPage}
ActionsComponent={TablePaginationActions}
/>
</TableRow>
</TableFooter>
)}
</Table>
</TableContainer>
</>
);
}

View File

@@ -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,
},
}));

View File

@@ -13,6 +13,7 @@ export const GET_USERS = gql`
currentPage
totalPages
}
totalCount
edges {
node {
id

View File

@@ -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<QueryResponse>(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,
};
}