Skip to content

Commit

Permalink
feat(UI): added manage group-users page
Browse files Browse the repository at this point in the history
Signed-off-by: dushimsam <[email protected]>
  • Loading branch information
dushimsam committed Sep 6, 2022
1 parent cf11dae commit 9cd0e98
Show file tree
Hide file tree
Showing 11 changed files with 564 additions and 3 deletions.
6 changes: 6 additions & 0 deletions src/Routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ const DeleteGroup = React.lazy(() => import("pages/Admin/Group/Delete"));
const DeleteUser = React.lazy(() => import("pages/Admin/Users/Delete"));
const AddUser = React.lazy(() => import("pages/Admin/Users/Add"));
const EditUser = React.lazy(() => import("pages/Admin/Users/Edit"));
const ManageGroup = React.lazy(() => import("pages/Admin/Group/Manage"));
const AddLicense = React.lazy(() => import("pages/Admin/License/Create"));
const SelectLicense = React.lazy(() =>
import("pages/Admin/License/SelectLicense")
Expand Down Expand Up @@ -292,6 +293,11 @@ const Routes = () => {
path={routes.admin.group.delete}
component={DeleteGroup}
/>
<AdminLayout
exact
path={routes.admin.group.manageGroup}
component={ManageGroup}
/>
<AdminLayout
exact
path={routes.admin.license.create}
Expand Down
39 changes: 39 additions & 0 deletions src/api/groups.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,42 @@ export const deleteGroupApi = (id) => {
addGroupName: false,
});
};

// Get all group members
export const getAllGroupMembersApi = (groupId) => {
const url = endpoints.admin.groups.getAllGroupMembers(groupId);
return sendRequest({
url,
method: "GET",
headers: {
Authorization: getToken(),
},
});
};

// Remove Group Member
export const removeGroupMemberApi = (groupId, userId) => {
const url = endpoints.admin.groups.removeGroupMember(groupId, userId);
return sendRequest({
url,
method: "DELETE",
headers: {
Authorization: getToken(),
},
});
};

// Change user permission
export const changeUserPermissionApi = (groupId, userId, permission) => {
const url = endpoints.admin.groups.changeUserPermission(groupId, userId);
return sendRequest({
url,
method: "PUT",
headers: {
Authorization: getToken(),
},
body: {
perm: permission,
},
});
};
67 changes: 66 additions & 1 deletion src/api/groups.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@

import sendRequest from "api/sendRequest";
import endpoints from "constants/endpoints";
import { createGroupApi, getAllGroupsApi } from "api/groups";
import {
changeUserPermissionApi,
createGroupApi,
getAllGroupMembersApi,
getAllGroupsApi,
removeGroupMemberApi,
} from "api/groups";
import { getToken } from "shared/authHelper";

jest.mock("api/sendRequest");
Expand Down Expand Up @@ -56,4 +62,63 @@ describe("groups", () => {
})
);
});

test("removeGroupMemberApi", () => {
const groupId = 2;
const userId = 1;
const url = endpoints.admin.groups.removeGroupMember(groupId, userId);
sendRequest.mockImplementation(() => true);

expect(removeGroupMemberApi(groupId, userId)).toBe(sendRequest({}));
expect(sendRequest).toHaveBeenCalledWith(
expect.objectContaining({
url,
method: "DELETE",
headers: {
Authorization: getToken(),
},
})
);
});

test("getAllGroupMembersApi", () => {
const groupId = 1;
const url = endpoints.admin.groups.getAllGroupMembers(groupId);
sendRequest.mockImplementation(() => true);

expect(getAllGroupMembersApi(groupId)).toBe(sendRequest({}));
expect(sendRequest).toHaveBeenCalledWith(
expect.objectContaining({
url,
method: "GET",
headers: {
Authorization: getToken(),
},
})
);
});

test("changeUserPermissionApi", () => {
const groupId = 1;
const userId = 1;
const permission = 2;
const url = endpoints.admin.groups.changeUserPermission(groupId, userId);
sendRequest.mockImplementation(() => true);

expect(changeUserPermissionApi(groupId, userId, permission)).toBe(
sendRequest({})
);
expect(sendRequest).toHaveBeenCalledWith(
expect.objectContaining({
url,
method: "PUT",
headers: {
Authorization: getToken(),
},
body: {
perm: permission,
},
})
);
});
});
221 changes: 221 additions & 0 deletions src/components/Admin/ChangePermission.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/*
Copyright (C) 2022 Samuel Dushimimana ([email protected])
SPDX-License-Identifier: GPL-2.0
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import React, { useEffect, useState } from "react";

import { InputContainer } from "components/Widgets";

// Required functions for calling APIs
import { changeUserPermission, removeGroupMember } from "services/groups";

import PropTypes from "prop-types";

// Required constants
import { userPermissions } from "constants/constants";

const ChangePermissionContainer = ({
groupMembers,
noneGroupMembers,
setShowMessage,
setMessage,
currGroup,
handleFetchGroupMembers,
}) => {
const [currUser, setCurrentUser] = useState(null);
const [currNonMember, setCurrentNonMember] = useState(null);

useEffect(() => {
if (groupMembers.length > 0) {
setCurrentUser({
user: groupMembers[0].id,
perm: groupMembers[0].group_perm,
});
}
}, [groupMembers]);

useEffect(() => {
if (noneGroupMembers.length > 0) {
setCurrentNonMember({
user: noneGroupMembers[0].id,
perm: -1,
});
}
}, [noneGroupMembers]);

const handleChangeCurrUser = async (newUser, isMember = true) => {
if (isMember) {
let perm;
groupMembers.forEach((item) => {
if (item.id === parseInt(newUser, 10)) {
perm = item.group_perm;
}
});
setCurrentUser({ user: parseInt(newUser, 10), perm });
} else {
setCurrentNonMember({ user: parseInt(newUser, 10), perm: -1 });
}
};

const handleSetNewPermission = async (newPerm, isMember = true) => {
try {
let res;

if (parseInt(newPerm, 10) === -1) {
res = await removeGroupMember(currGroup, currUser.user);
} else {
res = await changeUserPermission(
currGroup,
isMember ? currUser.user : currNonMember.user,
parseInt(newPerm, 10)
);
}

setShowMessage(true);
setMessage({
type: "success",
text: res.message,
});

handleFetchGroupMembers(currGroup);
} catch (e) {
setMessage({
type: "danger",
text: e.message,
});
} finally {
setTimeout(() => {
setShowMessage(false);
}, [3000]);
}
};
return (
<>
<div className="container">
<div className="row">
{groupMembers.length > 0 ? (
<div className="col-md-12 col-sm-12 col-12 mt-3">
<TableFill
title="Group Members"
ContentFill={
<tr>
<td>
<InputContainer
type="select"
name="name"
options={groupMembers}
id="select-user-tag"
value={currUser?.user}
property="name"
onChange={(e) => handleChangeCurrUser(e.target.value)}
/>
</td>
<td>
<InputContainer
type="select"
name="name"
options={userPermissions}
id="select-tag"
value={currUser?.perm}
property="name"
onChange={(e) => handleSetNewPermission(e.target.value)}
/>
</td>
</tr>
}
/>
</div>
) : (
<></>
)}
{noneGroupMembers.length > 0 ? (
<div className="col-md-12 col-sm-12 col-12 mt-3">
<TableFill
title="None Group Members"
ContentFill={
<tr>
<td>
<InputContainer
type="select"
name="name"
options={noneGroupMembers}
id="select-user-tag"
value={currNonMember?.user}
property="name"
onChange={(e) =>
handleChangeCurrUser(e.target.value, false)
}
/>
</td>
<td>
<InputContainer
type="select"
name="name"
options={userPermissions}
id="select-tag"
value={-1}
property="name"
onChange={(e) =>
handleSetNewPermission(e.target.value, false)
}
/>
</td>
</tr>
}
/>
</div>
) : (
<></>
)}
</div>
</div>
</>
);
};

// eslint-disable-next-line react/prop-types
export const TableFill = ({ ContentFill, title }) => (
<>
<h5>{title}</h5>
<table className="table table-striped table-bordered rounded mt-1">
<thead className="bg-dark text-light font-weight-bold">
<tr>
<th>User</th>
<th>Permission</th>
</tr>
</thead>
<tbody>{ContentFill}</tbody>
</table>
</>
);

TableFill.prototype = {
ContentFill: PropTypes.any,
title: PropTypes.string,
};

ChangePermissionContainer.propTypes = {
groupMembers: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.any)).isRequired,
noneGroupMembers: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.any))
.isRequired,
setMessage: PropTypes.func,
currGroup: PropTypes.number,
handleFetchGroupMembers: PropTypes.func,
setShowMessage: PropTypes.func,
};

export default ChangePermissionContainer;
4 changes: 2 additions & 2 deletions src/components/Header/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,9 @@ const Header = () => {
</NavDropdown.Item>
<NavDropdown.Item
as={Link}
to={routes.admin.group.delete}
to={routes.admin.group.manageGroup}
>
Delete Group
Manage Group Users
</NavDropdown.Item>
</div>
</DropdownButton>
Expand Down
3 changes: 3 additions & 0 deletions src/components/Widgets/Input/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const InputContainer = ({
id,
className,
onChange,
defaultValue = null,
children,
checked = false,
placeholder = null,
Expand Down Expand Up @@ -70,6 +71,7 @@ const InputContainer = ({
className ? `mr-2 form-control ${className}` : `mr-2 form-control`
}
value={value}
defaultValue={defaultValue}
onChange={onChange}
multiple={multiple && multiple}
size={multiple ? "15" : ""}
Expand Down Expand Up @@ -125,6 +127,7 @@ InputContainer.propTypes = {
onChange: PropTypes.func,
checked: PropTypes.bool,
disabled: PropTypes.bool,
defaultValue: PropTypes.string,
children: PropTypes.node,
options: PropTypes.arrayOf(
PropTypes.shape({
Expand Down
Loading

0 comments on commit 9cd0e98

Please sign in to comment.