diff --git a/web-ui/src/api/roles.js b/web-ui/src/api/roles.js
index 2013011a12..82c73ced56 100644
--- a/web-ui/src/api/roles.js
+++ b/web-ui/src/api/roles.js
@@ -41,9 +41,16 @@ export const addNewRole = async (role, cookie) => {
return resolve({
method: "POST",
url: roleURL,
- data: {
- role: role,
- },
+ data: role,
headers: { "X-CSRF-Header": cookie, "Accept": "application/json", "Content-Type": "application/json;charset=UTF-8" },
});
};
+
+export const updateRole = async (role, cookie) => {
+ return resolve({
+ method: "PUT",
+ url: roleURL,
+ data: role,
+ headers: { "X-CSRF-Header": cookie, "Accept": "application/json", "Content-Type": "application/json;charset=UTF-8" },
+ });
+};
\ No newline at end of file
diff --git a/web-ui/src/components/admin/roles/RoleUserCards.jsx b/web-ui/src/components/admin/roles/RoleUserCards.jsx
index bbe9aca4b1..c3c332e2d7 100644
--- a/web-ui/src/components/admin/roles/RoleUserCards.jsx
+++ b/web-ui/src/components/admin/roles/RoleUserCards.jsx
@@ -11,45 +11,45 @@ import {
Typography,
} from "@mui/material";
-const RoleUserCards = ({ role, roleToMemberMap, removeFromRole }) => {
- roleToMemberMap[role].sort((a, b) => a.name.localeCompare(b.name));
- return roleToMemberMap[role].map(
- (member) =>
- member && (
-
-
-
-
-
-
- {member.name}
-
- }
- secondary={
-
- {member.title}
-
- }
+const RoleUserCards = ({ roleMembers, onRemove, memberQuery }) => {
+ roleMembers?.sort((a, b) => a.name?.localeCompare(b.name) || -1);
+ return roleMembers?.map((member) =>
+ member &&
+ ((memberQuery && memberQuery.trim())
+ ? member.name.toLowerCase().includes(memberQuery.trim().toLowerCase())
+ : true) && (
+
+
+
+
- {
- removeFromRole(member, role);
- }}
- >
-
-
-
-
-
- )
- );
+
+
+ {member.name}
+
+ }
+ secondary={
+
+ {member.title}
+
+ }
+ />
+ onRemove(member)}
+ >
+
+
+
+
+
+ )
+ ) || null;
};
export default RoleUserCards;
diff --git a/web-ui/src/components/admin/roles/Roles.css b/web-ui/src/components/admin/roles/Roles.css
index 6a33dd51b1..57beadb291 100644
--- a/web-ui/src/components/admin/roles/Roles.css
+++ b/web-ui/src/components/admin/roles/Roles.css
@@ -1,6 +1,14 @@
-.role-buttons {
+.role-header {
display: flex;
- justify-content: flex-end;
+ flex-direction: row;
+ align-items: center;
+ height: 100px;
+ justify-content: space-between;
+ padding: 0 1rem 0 1rem;
+}
+
+.role-header-title {
+ width: 50%;
}
.role-modal {
@@ -43,20 +51,17 @@
.roles-content {
margin-left: 1rem;
-}
-
-.roles-header {
- display: flex;
- justify-content: center;
+ margin-right: 1rem;
}
.roles-content .icon {
- padding-right: 2rem;
cursor: pointer;
}
.roles-content .roles-list-item {
align-items: center;
+ padding-left: 2rem;
+ padding-right: 2rem;
}
.roles-content .roles-search {
@@ -86,30 +91,77 @@
overflow: scroll;
}
-.roles-content .roles .role-buttons .role-add {
+.role-header-buttons {
+ display: flex;
+ flex-direction: row;
+}
+
+.roles-content .roles .role-header-buttons .role-add {
cursor: pointer;
display: flex;
align-items: center;
margin-left: 0.5rem;
}
-.roles-content .roles .role-buttons .role-edit {
+.roles-content .roles .role-header-buttons .role-edit {
cursor: pointer;
display: flex;
align-items: center;
margin-left: 0.5rem;
}
-.roles-content .roles .role-buttons span {
+.roles-content .roles .role-header-buttons span {
margin-right: 0.5rem;
}
.roles-content .roles .roles-bot {
- display: flex;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
}
.roles-content .roles .roles-top {
display: flex;
justify-content: space-between;
- margin-left: 16px;
}
+
+.roles-content .roles .roles-top .roles-top-left {
+ display: flex;
+}
+.roles-content .roles .roles-top .roles-top-right {
+ display: flex;
+}
+
+.roles-content .roles .roles-top-search-fields {
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ width: 100%;
+}
+
+.roles-content .roles .roles-top-search-fields .role-select {
+ width: 50%;
+ margin-right: 2rem;
+}
+
+.roles-content .roles .roles-top-search-fields .member-role-search {
+ width: 50%;
+}
+
+@media screen and (max-width: 600px) {
+
+ .roles-content .roles .roles-top .roles-top-search-fields {
+ flex-direction: column;
+ }
+
+ .roles-content .roles .roles-top .roles-top-search-fields .role-select {
+ width: 300px;
+ margin-top: 0;
+ margin-right: 0;
+ }
+
+ .roles-content .roles .roles-top .roles-top-search-fields .member-role-search {
+ width: 300px;
+ margin-bottom: 1rem;
+ }
+
+}
\ No newline at end of file
diff --git a/web-ui/src/components/admin/roles/Roles.jsx b/web-ui/src/components/admin/roles/Roles.jsx
index 75502155b0..0279fb0ca0 100644
--- a/web-ui/src/components/admin/roles/Roles.jsx
+++ b/web-ui/src/components/admin/roles/Roles.jsx
@@ -1,15 +1,15 @@
-import React, { useContext, useEffect, useState } from "react";
+import React, {useContext, useEffect, useState} from "react";
-import { AppContext } from "../../../context/AppContext";
+import {AppContext} from "../../../context/AppContext";
import {
- UPDATE_ROLES,
+ SET_ROLES,
SET_USER_ROLES,
UPDATE_TOAST,
} from "../../../context/actions";
import {
addUserToRole,
addNewRole,
- removeUserFromRole,
+ removeUserFromRole, updateRole,
} from "../../../api/roles";
import RoleUserCards from "./RoleUserCards";
@@ -19,59 +19,76 @@ import {
Card,
CardActions,
CardContent,
- CardHeader,
List,
+ ListSubheader,
Modal,
TextField,
Typography,
+ Autocomplete,
+ InputAdornment,
+ FormControl,
+ InputLabel,
+ Select,
+ OutlinedInput,
+ MenuItem,
+ Checkbox,
+ ListItemText,
+ FormHelperText,
+ Divider
} from "@mui/material";
import PersonAddIcon from "@mui/icons-material/PersonAdd";
-import Autocomplete from '@mui/material/Autocomplete';
+import SearchIcon from "@mui/icons-material/Search";
+import AddIcon from "@mui/icons-material/Add";
-import { isArrayPresent } from './../../../helpers/checks';
+import {isArrayPresent} from './../../../helpers/checks';
import "./Roles.css";
+import EditIcon from "@mui/icons-material/Edit";
const Roles = () => {
- const { state, dispatch } = useContext(AppContext);
- const { csrf, memberProfiles, roles, userRoles } = state;
+ const {state, dispatch} = useContext(AppContext);
+ const {csrf, memberProfiles, roles, userRoles} = state;
const [showAddUser, setShowAddUser] = useState(false);
- const [showAddRole, setShowAddRole] = useState(false);
- const [newRole, setNewRole] = useState("");
- // const [editRole, setEditRole] = useState(false);
+ const [showEditRole, setShowEditRole] = useState(false);
+ const [editedRole, setEditedRole] = useState(null);
const [searchText, setSearchText] = useState("");
const [selectedMember, setSelectedMember] = useState({});
+ const [selectedRoles, setSelectedRoles] = useState([]);
const [roleToMemberMap, setRoleToMemberMap] = useState({});
- const [selectedRole, setSelectedRole] = useState("");
+ const [currentRole, setCurrentRole] = useState("");
memberProfiles?.sort((a, b) => a.name.localeCompare(b.name));
useEffect(() => {
- const memberMap = {};
- if(memberProfiles && memberProfiles.length > 0) {
- for (const member of memberProfiles) {
- memberMap[member.id] = member;
- }
+ if (roles) {
+ setSelectedRoles(roles.map(roleObj => roleObj.role));
+ }
+ }, [roles]);
+
+ useEffect(() => {
+ const memberMap = {};
+ if (memberProfiles && memberProfiles.length > 0) {
+ for (const member of memberProfiles) {
+ memberMap[member.id] = member;
}
+ }
- const newRoleToMemberMap = {};
- for (const userRole of userRoles || []) {
- const role = roles.find((role) => role.id === userRole?.memberRoleId?.roleId);
- if(role) {
- let memberList = newRoleToMemberMap[role.role];
- if (!memberList) {
- memberList = newRoleToMemberMap[role.role] = [];
- }
- if (memberMap[userRole?.memberRoleId?.memberId] !== undefined) {
- memberList.push({ ...memberMap[userRole?.memberRoleId?.memberId], roleId: role.id });
- }
+ const newRoleToMemberMap = {};
+ for (const userRole of userRoles || []) {
+ const role = roles.find((role) => role.id === userRole?.memberRoleId?.roleId);
+ if (role) {
+ let memberList = newRoleToMemberMap[role.role];
+ if (!memberList) {
+ memberList = newRoleToMemberMap[role.role] = [];
+ }
+ if (memberMap[userRole?.memberRoleId?.memberId] !== undefined) {
+ memberList.push({...memberMap[userRole?.memberRoleId?.memberId], roleId: role.id});
}
}
- setRoleToMemberMap(newRoleToMemberMap);
- }, [userRoles, memberProfiles, roles]);
-
- const uniqueRoles = Object.keys(roleToMemberMap);
+ }
+ setRoleToMemberMap(newRoleToMemberMap);
+ }, [userRoles, memberProfiles, roles]);
const getRoleStats = (role) => {
let members = roleToMemberMap[role];
@@ -80,7 +97,7 @@ const Roles = () => {
const removeFromRole = async (member, role) => {
const members = roleToMemberMap[role];
- const { roleId } = members.find((m) => member.id === m.id);
+ const {roleId} = members.find((m) => member.id === m.id);
let res = await removeUserFromRole(roleId, member.id, csrf);
let data =
res.payload && res.payload.status === 200 && !res.error
@@ -104,7 +121,7 @@ const Roles = () => {
};
const addToRole = async (member) => {
- const role = roles.find((role) => role.role === selectedRole);
+ const role = roles.find((role) => role.role === currentRole.role);
let res = await addUserToRole(role.id, member.id, csrf);
let data =
res.payload && res.payload.data && !res.error ? res.payload.data : null;
@@ -118,28 +135,29 @@ const Roles = () => {
type: UPDATE_TOAST,
payload: {
severity: "success",
- toast: `${member.name} added to ${selectedRole}s`,
+ toast: `${member.name} added to ${currentRole.role}s`,
},
});
}
setSelectedMember({});
};
- const addRole = async (member, role) => {
- let res = await addNewRole(selectedRole, member.id, csrf);
+ const saveRole = async (role) => {
+ let res = role.id ? await updateRole(role, csrf) : await addNewRole(role, csrf);
let data =
res.payload && res.payload.data && !res.error ? res.payload.data : null;
if (data) {
- setShowAddRole(false);
+ setShowEditRole(false);
+ const updatedRoles = [...roles.filter((role) => role?.id !== data.id), data];
dispatch({
- type: UPDATE_ROLES,
- payload: data,
+ type: SET_ROLES,
+ payload: updatedRoles,
});
window.snackDispatch({
type: UPDATE_TOAST,
payload: {
severity: "success",
- toast: `${member.name} added to ${selectedRole}s`,
+ toast: `Role Updated: ${role.role}`,
},
});
}
@@ -149,152 +167,183 @@ const Roles = () => {
setShowAddUser(false);
};
- const closeAddRole = () => {
- setShowAddRole(false);
+ const closeEditRole = () => {
+ setShowEditRole(false);
};
- // const closeEditRole = () => {
- // setEditRole(false);
- // };
+ const setRoleName = (event) => {
+ setEditedRole({...editedRole, role: event?.target?.value});
+ }
+
+ const setRoleDescription = (event) => {
+ setEditedRole({...editedRole, description: event?.target?.value});
+ }
return (
-
Roles
-
{
- setSearchText(e.target.value);
+
+
+ Roles
+
+ {`Showing ${selectedRoles.length}/${roles?.length} roles`}
+
+ {
+ setSearchText(e.target.value);
+ }}
+ InputProps={{
+ endAdornment:
+ }}
+ />
+
+
+
+
- {/*
*/}
-
- {uniqueRoles.map((role) =>
- role.toLowerCase().includes(searchText.toLowerCase()) ? (
-
-
-
- {role}
-
-
- {role.description || ""}
-
-
- {getRoleStats(role)} Users
-
-
- }
- subheader={
-
-
-
- {/*
*/}
-
-
- }
+
+
+
{
+ return !roleToMemberMap[currentRole.role]?.find((m)=>m.id === member.id);
+ }) || []}
+ value={selectedMember}
+ onChange={(event, newValue) =>
+ setSelectedMember(newValue)
+ }
+ getOptionLabel={(option) => option.name || ""}
+ renderInput={(params) => (
+
+ )}
+ />
+
+
+
+
+ {roles?.map((roleObj) =>
+ selectedRoles.includes(roleObj.role) ? (
+
- {
-
+
+
+
+
+
+
+ {roleObj.role}
+
+
+ {roleObj.description || ""}
+
+
+ {getRoleStats(roleObj.role)} Users
+
+
+
+
+
+
+
+
+
removeFromRole(member, roleObj.role)}
+ memberQuery={searchText}
/>
-
- }
+
+
-
+
-
!roleToMemberMap[role].includes(member.id)
- ) || []}
- value={selectedMember}
- onChange={(event, newValue) =>
- setSelectedMember(newValue)
- }
- getOptionLabel={(option) => option.name || ""}
- renderInput={(params) => (
-
- )}
+
-
-
-
-
-
-
- {/*
-
-
- setMember({
- ...editedMember,
- firstName: e.target.value,
- })
- }
- />
- addToRole(selectedMember)}>
- Save
-
-
- */}
) : null
diff --git a/web-ui/src/components/admin/roles/__snapshots__/Roles.test.jsx.snap b/web-ui/src/components/admin/roles/__snapshots__/Roles.test.jsx.snap
index fd0bae68d6..b23dd48c92 100644
--- a/web-ui/src/components/admin/roles/__snapshots__/Roles.test.jsx.snap
+++ b/web-ui/src/components/admin/roles/__snapshots__/Roles.test.jsx.snap
@@ -14,50 +14,346 @@ exports[`renders correctly 1`] = `
-
- Roles
-
-
+
+
+
+ Showing 2/2 roles
+
+
+
+
+ Add Role
+
+
+
+
+
+
+ >
+
+
+
diff --git a/web-ui/src/context/actions.js b/web-ui/src/context/actions.js
index d9b6fbbf2d..658539e691 100644
--- a/web-ui/src/context/actions.js
+++ b/web-ui/src/context/actions.js
@@ -18,7 +18,7 @@ export const UPDATE_CHECKIN = "@@check-ins/update_checkin";
export const UPDATE_CHECKINS = "@@check-ins/update_checkins";
export const UPDATE_MEMBER_PROFILES = "@@check-ins/update_member_profiles";
export const UPDATE_MEMBER_SKILLS = "@@check-ins/update_member_skills";
-export const UPDATE_ROLES = "@@check-ins/update_roles";
+export const ADD_ROLE = "@@check-ins/add_role";
export const UPDATE_SKILL = "@@check-ins/update_skill";
export const UPDATE_SKILLS = "@@check-ins/update_skills";
export const UPDATE_TEAM_MEMBERS = "@@check-ins/update_team_members";
diff --git a/web-ui/src/context/reducer.js b/web-ui/src/context/reducer.js
index ddd032fc58..4902c91dba 100644
--- a/web-ui/src/context/reducer.js
+++ b/web-ui/src/context/reducer.js
@@ -21,7 +21,7 @@ import {
UPDATE_SKILLS,
UPDATE_GUILD,
UPDATE_GUILDS,
- UPDATE_ROLES,
+ ADD_ROLE,
UPDATE_TEAMS,
UPDATE_TEAM_MEMBERS,
UPDATE_TOAST,
@@ -209,7 +209,7 @@ export const reducer = (state, action) => {
case DELETE_ROLE:
state.roles = state.roles.filter((role) => role.id !== action.payload);
break;
- case UPDATE_ROLES:
+ case ADD_ROLE:
state.roles = [...state.roles, action.payload];
break;
case ADD_GUILD: