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:
@@ -10,15 +10,14 @@ type Params = {
|
|||||||
const getUsers = async (_parent: unknown, params: Params, context: Context) => {
|
const getUsers = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
context.currentUser.can('read', 'User');
|
context.currentUser.can('read', 'User');
|
||||||
|
|
||||||
const usersQuery = User
|
const usersQuery = User.query()
|
||||||
.query()
|
|
||||||
.leftJoinRelated({
|
.leftJoinRelated({
|
||||||
role: true
|
role: true,
|
||||||
})
|
})
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
role: true
|
role: true,
|
||||||
})
|
})
|
||||||
.orderBy('full_name', 'desc');
|
.orderBy('full_name', 'asc');
|
||||||
|
|
||||||
return paginate(usersQuery, params.limit, params.offset);
|
return paginate(usersQuery, params.limit, params.offset);
|
||||||
};
|
};
|
||||||
|
@@ -341,6 +341,7 @@ type SamlAuthProvidersRoleMapping {
|
|||||||
type UserConnection {
|
type UserConnection {
|
||||||
edges: [UserEdge]
|
edges: [UserEdge]
|
||||||
pageInfo: PageInfo
|
pageInfo: PageInfo
|
||||||
|
totalCount: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserEdge {
|
type UserEdge {
|
||||||
|
@@ -21,6 +21,7 @@ const paginate = async (
|
|||||||
currentPage: Math.ceil(offset / limit + 1),
|
currentPage: Math.ceil(offset / limit + 1),
|
||||||
totalPages: Math.ceil(count / limit),
|
totalPages: Math.ceil(count / limit),
|
||||||
},
|
},
|
||||||
|
totalCount: count,
|
||||||
edges: records.map((record: Base) => ({
|
edges: records.map((record: Base) => ({
|
||||||
node: record,
|
node: record,
|
||||||
})),
|
})),
|
||||||
|
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
@@ -11,18 +11,43 @@ import Paper from '@mui/material/Paper';
|
|||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
import TableFooter from '@mui/material/TableFooter';
|
||||||
|
|
||||||
import DeleteUserButton from 'components/DeleteUserButton/index.ee';
|
import DeleteUserButton from 'components/DeleteUserButton/index.ee';
|
||||||
import ListLoader from 'components/ListLoader';
|
import ListLoader from 'components/ListLoader';
|
||||||
import useUsers from 'hooks/useUsers';
|
import useUsers from 'hooks/useUsers';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
|
import TablePaginationActions from './TablePaginationActions';
|
||||||
|
import { TablePagination } from './style';
|
||||||
|
|
||||||
export default function UserList(): React.ReactElement {
|
export default function UserList(): React.ReactElement {
|
||||||
const formatMessage = useFormatMessage();
|
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 (
|
return (
|
||||||
|
<>
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
@@ -74,7 +99,9 @@ export default function UserList(): React.ReactElement {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Typography variant="subtitle2">{user.role.name}</Typography>
|
<Typography variant="subtitle2">
|
||||||
|
{user.role.name}
|
||||||
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
@@ -93,7 +120,23 @@ export default function UserList(): React.ReactElement {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</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>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
12
packages/web/src/components/UserList/style.ts
Normal file
12
packages/web/src/components/UserList/style.ts
Normal 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,
|
||||||
|
},
|
||||||
|
}));
|
@@ -13,6 +13,7 @@ export const GET_USERS = gql`
|
|||||||
currentPage
|
currentPage
|
||||||
totalPages
|
totalPages
|
||||||
}
|
}
|
||||||
|
totalCount
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
|
@@ -4,30 +4,37 @@ import { IUser } from '@automatisch/types';
|
|||||||
import { GET_USERS } from 'graphql/queries/get-users';
|
import { GET_USERS } from 'graphql/queries/get-users';
|
||||||
|
|
||||||
type Edge = {
|
type Edge = {
|
||||||
node: IUser
|
node: IUser;
|
||||||
}
|
};
|
||||||
|
|
||||||
type QueryResponse = {
|
type QueryResponse = {
|
||||||
getUsers: {
|
getUsers: {
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
currentPage: number;
|
currentPage: number;
|
||||||
totalPages: 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, {
|
const { data, loading } = useQuery<QueryResponse>(GET_USERS, {
|
||||||
variables: {
|
variables: getLimitAndOffset(page, rowsPerPage),
|
||||||
limit: 100,
|
|
||||||
offset: 0
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
const users = data?.getUsers.edges.map(({ node }) => node) || [];
|
const users = data?.getUsers.edges.map(({ node }) => node) || [];
|
||||||
|
const pageInfo = data?.getUsers.pageInfo;
|
||||||
|
const totalCount = data?.getUsers.totalCount;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
users,
|
users,
|
||||||
loading
|
pageInfo,
|
||||||
|
totalCount,
|
||||||
|
loading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user