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, - }) - } - /> - -
-
*/}
) : 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 -

- - + +
+ +
+
+ +
+ > +
+
+
    +
    +
  • +
    +
    +

    + ADMIN +

    +
    +
    + 0 + Users +
    +
    +
    + + +
    +
    +
    +
  • +
    +
+
+
+
+
+
+
    +
    +
  • +
    +
    +

    + PDL +

    +
    +
    + 0 + Users +
    +
    +
    + + +
    +
    +
    +
  • +
    +
+
+
+
+
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: