From 3bfe51a09c2de5f2edab13d3b3d9cc1887b8498c Mon Sep 17 00:00:00 2001 From: RaccoonLi <88028886+RaccoonLi@users.noreply.github.com> Date: Thu, 3 Nov 2022 15:04:49 +0800 Subject: [PATCH] Team & Team Member Management (#108) * Team & Member Management Page * Fix * Add Member as Team Manager * Add Get Authorized Team List --- react/src/component/BaseView.jsx | 18 ++ react/src/component/Dashboard.jsx | 12 +- react/src/component/HeaderView.jsx | 30 +- react/src/component/TeamManagement.jsx | 321 +++++++++++++++++++++ react/src/component/TeamUserManagement.jsx | 293 +++++++++++++++++++ 5 files changed, 649 insertions(+), 25 deletions(-) create mode 100644 react/src/component/TeamManagement.jsx create mode 100644 react/src/component/TeamUserManagement.jsx diff --git a/react/src/component/BaseView.jsx b/react/src/component/BaseView.jsx index 14c813eec..e9152d1fc 100644 --- a/react/src/component/BaseView.jsx +++ b/react/src/component/BaseView.jsx @@ -9,6 +9,7 @@ export default class BaseView extends React.Component { snackbarIsShown: false, snackbarSeverity: null, snackbarMessage: null, + userInfo: null, teamList: null, defaultTeam: null } @@ -67,6 +68,23 @@ export default class BaseView extends React.Component { console.log(event.target.files[0].name) } + getUserInfo = () => { + axios.get('/api/auth/getUser').then(res => { + if (res.data && res.data.code === 200) { + console.log(res.data.content) + this.setState({ + userInfo: res.data.content + }) + } else { + this.snackBarFail(res) + } + }).catch((error) => { + this.snackBarError(error) + }) + console.log(this.state.userInfo) + } + + refreshTeamList() { this.setState({ teamList: null, diff --git a/react/src/component/Dashboard.jsx b/react/src/component/Dashboard.jsx index b6fb4f021..88594d402 100644 --- a/react/src/component/Dashboard.jsx +++ b/react/src/component/Dashboard.jsx @@ -27,6 +27,7 @@ import {ThemeProvider} from '@material-ui/styles'; import Container from '@mui/material/Container'; import axios from "@/axios"; import TestJsonView from './TestJsonView'; +import TeamManagement from "@/component/TeamManagement"; const drawerWidth = 240; @@ -239,6 +240,12 @@ export default function Dashboard() { + + + people + + + @@ -248,7 +255,7 @@ export default function Dashboard() { @@ -299,6 +306,9 @@ export default function Dashboard() { + + + }/> diff --git a/react/src/component/HeaderView.jsx b/react/src/component/HeaderView.jsx index dcc3f945b..b0885f474 100644 --- a/react/src/component/HeaderView.jsx +++ b/react/src/component/HeaderView.jsx @@ -24,7 +24,6 @@ import Button from "@mui/material/Button"; export default class HeaderView extends BaseView { state = { - userName: null, avatarOpen: false, helpOpen: false, portalVersion: "", @@ -35,8 +34,8 @@ export default class HeaderView extends BaseView { render() { const settings = [ - { text: this.state.userName, dialog: null }, - { text: `Default Team: ${this.state.defaultTeam ? this.state.defaultTeam.teamName : 'Loading'}`, dialog: 'changeDefaultTeamIsShown' }, + { text: this.state.userInfo ? this.state.userInfo.userName : 'Loading', dialog: null }, + { text: `Default Team: ${this.state.userInfo && this.state.userInfo.defaultTeamName ? this.state.userInfo.defaultTeamName : 'Loading'}`, dialog: 'changeDefaultTeamIsShown' }, { text: 'Logout', dialog: null } ]; const helpSettings = [ @@ -93,7 +92,7 @@ export default class HeaderView extends BaseView { this.handleStatus("avatarOpen", true)} sx={{p: 0}}> - + this.handleStatus('selectedTeamId', select.target.value)} > {teamList ? null : No team available} {teamList ? teamList.map((team, index) => ( - {team.teamName} + {team.teamName} )) : null}
@@ -153,22 +152,6 @@ export default class HeaderView extends BaseView { } - getLoginInfo = () => { - axios.get('/api/auth/getUser').then(res => { - if (res.data && res.data.code === 200) { - const userInfo = res.data.content; - console.log(userInfo) - this.setState({ - userName: userInfo.userName - }) - } else { - this.snackBarFail(res) - } - }).catch((error) => { - this.snackBarError(error) - }) - } - portalCheck() { if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { window.location.href = '/'; @@ -204,8 +187,7 @@ export default class HeaderView extends BaseView { } componentDidMount() { - this.getLoginInfo() + this.getUserInfo() this.refreshTeamList() - this.getUserTeamInfo() } } \ No newline at end of file diff --git a/react/src/component/TeamManagement.jsx b/react/src/component/TeamManagement.jsx new file mode 100644 index 000000000..44b5b2e15 --- /dev/null +++ b/react/src/component/TeamManagement.jsx @@ -0,0 +1,321 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import React from 'react' +import axios from '@/axios' +import Table from '@material-ui/core/Table'; +import TableBody from '@material-ui/core/TableBody'; +import TableCell from '@material-ui/core/TableCell'; +import TableContainer from '@material-ui/core/TableContainer'; +import TableHead from '@material-ui/core/TableHead'; +import TableRow from '@material-ui/core/TableRow'; +import Typography from '@material-ui/core/Typography'; +import 'bootstrap/dist/css/bootstrap.css' +import {withStyles} from '@material-ui/core/styles'; +import Button from '@mui/material/Button'; +import IconButton from '@mui/material/IconButton'; +import TextField from '@mui/material/TextField'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import Snackbar from '@mui/material/Snackbar'; +import Alert from '@mui/material/Alert'; +import Stack from "@mui/material/Stack"; +import Skeleton from '@mui/material/Skeleton'; +import BaseView from "@/component/BaseView"; +import moment from 'moment'; +import TeamUserManagement from "@/component/TeamUserManagement"; + + +/** + * Palette + * https://material-ui.com/customization/palette/ + */ +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.primary.dark, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); +withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.primary.main, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); +const StyledTableRow = withStyles((theme) => ({ + root: { + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.selected, + }, + }, +}))(TableRow); + +export default class TeamManagement extends BaseView { + state = { + hideSkeleton: true, + teamCreateDialogIsShown: false, + + teamDeleteDialogIsShown: false, + toBeDeletedTeamId: null, + + teamMemberDetailIsShown: false, + selectedTeamId: null, + selectedTeamManageable: false, + + authorizedTeamList: null, + teamName: null, + + } + + render() { + const { snackbarIsShown, snackbarSeverity, snackbarMessage } = this.state + + const teamHeadItems = ['Team Name', 'Create Time', 'Operation'] + const teamHeads = [] + const teamRows = [] + + const { userInfo, authorizedTeamList } = this.state + + let admin = false; + + if (userInfo) { + admin = (userInfo.roleName === 'ADMIN' || userInfo.roleName === 'SUPER_ADMIN') + } + + if (authorizedTeamList) { + authorizedTeamList.sort((a, b) => a.createTime > b.createTime ? 1 : -1) + authorizedTeamList.forEach((t) => { + teamRows.push( + + {t.teamName} + + + {moment(t.createTime).format('yyyy-MM-DD HH:mm:ss')} + + + this.showTeamInfo(t.teamId, t.manageable)} disabled={t.teamName === 'Default' && !t.manageable}> + info + + this.openDeleteTeamDialog(t.teamId)} disabled={t.teamName === 'Default' || !t.manageable}> + delete + + + ) + }) + } + + teamHeadItems.forEach((k) => teamHeads.push( + {k} + )) + + return
+ + + + + + + {`Team Management (${this.state.userInfo ? this.state.userInfo.roleName : 'User'})`} + + + + + + + + + {teamHeads} + + + + + {teamRows} + +
+
+ this.handleStatus("teamCreateDialogIsShown", false)}> + Create Team + +
+
+ + + + +
+ this.handleStatus("teamDeleteDialogIsShown", false)} + > + Delete this team? + + + Please confirm if you want to delete this team, this operation is irreversible + + + + + + + + this.handleStatus("teamMemberDetailIsShown", false)}> + + + + + + + + this.handleStatus("snackbarIsShown", false)}> + this.handleStatus("snackbarIsShown", false)} + severity={snackbarSeverity} + sx={{width: '100%'}}> + {snackbarMessage} + + +
+ } + + createTeam = () => { + console.log("createTeam") + + this.setState({ + teamCreateDialogIsShown: false + }) + + const formParams = new URLSearchParams() + formParams.append("teamName", this.state.teamName) + + axios.post('/api/team/create', formParams, { + headers: {'content-type': 'application/x-www-form-urlencoded'} + }).then(res => { + if (res.data && res.data.code === 200) { + this.setState({ + snackbarSeverity: "success", + snackbarMessage: "Team successfully created", + snackbarIsShown: true, + }) + this.refreshAuthorizedTeamList() + } else { + this.snackBarFail(res) + } + }).catch((error) => { + this.snackBarError(error) + }) + } + + openDeleteTeamDialog = (teamId) => { + console.log() + this.handleStatus("teamDeleteDialogIsShown", true); + this.handleStatus('toBeDeletedTeamId', teamId) + } + + deleteTeam = () => { + this.setState({ + teamDeleteDialogIsShown: false + }) + + const formParams = new URLSearchParams() + formParams.append("teamId", this.state.toBeDeletedTeamId) + + axios.post('/api/team/delete', formParams, { + headers: {'content-type': 'application/x-www-form-urlencoded'} + }).then(res => { + if (res.data && res.data.code === 200) { + this.setState({ + snackbarSeverity: "success", + snackbarMessage: "Team deleted!", + snackbarIsShown: true, + toBeDeletedTeamId: null + }) + this.refreshAuthorizedTeamList() + } else { + this.snackBarFail(res) + } + }).catch((error) => { + this.snackBarError(error) + }) + } + + showTeamInfo(teamId, manageable) { + console.log(teamId) + this.setState({ + teamMemberDetailIsShown: true, + selectedTeamId: teamId, + selectedTeamManageable: manageable + }) + } + + refreshAuthorizedTeamList() { + this.setState({ + authorizedTeamList: null, + }) + axios.get('/api/userTeam/listAuthorizedTeam').then(res => { + console.log(res.data) + if (res.data && res.data.code === 200) { + this.setState({ + authorizedTeamList: res.data.content, + }) + } else { + this.snackBarFail(res) + } + }).catch(this.snackBarError) + } + + + componentDidMount() { + this.getUserInfo() + this.refreshAuthorizedTeamList() + } + + componentWillUnmount() { + // cancel requests + } +} \ No newline at end of file diff --git a/react/src/component/TeamUserManagement.jsx b/react/src/component/TeamUserManagement.jsx new file mode 100644 index 000000000..91206b411 --- /dev/null +++ b/react/src/component/TeamUserManagement.jsx @@ -0,0 +1,293 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import React from 'react' +import axios from '@/axios' +import Table from '@material-ui/core/Table'; +import TableBody from '@material-ui/core/TableBody'; +import TableCell from '@material-ui/core/TableCell'; +import TableContainer from '@material-ui/core/TableContainer'; +import TableHead from '@material-ui/core/TableHead'; +import TableRow from '@material-ui/core/TableRow'; +import Typography from '@material-ui/core/Typography'; +import 'bootstrap/dist/css/bootstrap.css' +import {withStyles} from '@material-ui/core/styles'; +import Button from '@mui/material/Button'; +import IconButton from '@mui/material/IconButton'; +import TextField from '@mui/material/TextField'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import Stack from "@mui/material/Stack"; +import Skeleton from '@mui/material/Skeleton'; +import BaseView from "@/component/BaseView"; +import FormControlLabel from "@material-ui/core/FormControlLabel"; +import Checkbox from "@mui/material/Checkbox"; + + +/** + * Palette + * https://material-ui.com/customization/palette/ + */ +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.primary.dark, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); +withStyles((theme) => ({ + head: { + backgroundColor: theme.palette.primary.main, + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); +const StyledTableRow = withStyles((theme) => ({ + root: { + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.selected, + }, + }, +}))(TableRow); + +export default class TeamUserManagement extends BaseView { + state = { + teamId: this.props.teamId, + manageable: this.props.manageable, + + userList: null, + + userAddDialogIsShown: false, + toBeAddedUserMailAddress: null, + toBeAddedUserRole: false, + + userRemoveDialogIsShown: false, + toBeRemovedUserId: null, + + hideSkeleton: true + } + + render() { + const userHeadItems = ['User Name', 'Role', 'Operation'] + const userHeads = [] + const userRows = [] + + const { manageable, userList } = this.state + + if (userList) { + userList.sort((a, b) => { + if ((a.teamAdmin && b.teamAdmin) || (!a.teamAdmin && !b.teamAdmin)) { + return a.name > b.name ? 1 : -1 + } + else { + return a.teamAdmin ? -1 : 1 + } + }) + userList.forEach((t) => { + userRows.push( + + {t.name} + + + {t.teamAdmin ? "Manager" : "Member"} + + + this.openRemoveUserDialog(t.userId)} disabled={!manageable && this.state.userInfo && this.state.userInfo.userId === t.userId}> + delete + + + ) + }) + } + + userHeadItems.forEach((k) => userHeads.push( + {k} + )) + + return
+ + + + + + + {`Members`} + + + + + + + + + {userHeads} + + + + + {userRows} + +
+
+ this.handleStatus("userAddDialogIsShown", false)}> + Add Member + +
+ {this.setState({toBeAddedUserRole: event.target.checked});}} />} + /> +
+ + + + +
+ this.handleStatus("userRemoveDialogIsShown", false)} + > + Remove this member? + + + Please confirm if you want to remove this member. + + + + + + + +
+ } + + addMember = () => { + this.setState({ + userAddDialogIsShown: false + }) + + const formParams = new URLSearchParams() + formParams.append("mailAddress", this.state.toBeAddedUserMailAddress) + formParams.append("teamId", this.state.teamId) + formParams.append("isTeamAdmin", this.state.toBeAddedUserRole) + + axios.post('/api/userTeam/addRelation', formParams, { + headers: {'content-type': 'application/x-www-form-urlencoded'} + }).then(res => { + if (res.data && res.data.code === 200) { + this.setState({ + snackbarSeverity: "success", + snackbarMessage: "Member Added!", + snackbarIsShown: true, + toBeAddedUserMailAddress: null + }) + this.getTeamMemberInfo() + } else { + this.snackBarFail(res) + } + }).catch((error) => { + this.snackBarError(error) + }) + } + + openRemoveUserDialog = (userId) => { + console.log(userId) + this.handleStatus("userRemoveDialogIsShown", true); + this.handleStatus('toBeRemovedUserId', userId) + } + + removeMember = () => { + this.setState({ + userRemoveDialogIsShown: false + }) + + const formParams = new URLSearchParams() + formParams.append("userId", this.state.toBeRemovedUserId) + formParams.append("teamId", this.state.teamId) + + axios.post('/api/userTeam/deleteRelation', formParams, { + headers: {'content-type': 'application/x-www-form-urlencoded'} + }).then(res => { + if (res.data && res.data.code === 200) { + this.setState({ + snackbarSeverity: "success", + snackbarMessage: "Member removed!", + snackbarIsShown: true, + toBeRemovedUserId: null + }) + this.getTeamMemberInfo() + } else { + this.snackBarFail(res) + } + }).catch((error) => { + this.snackBarError(error) + }) + } + + getTeamMemberInfo(teamId) { + this.setState({ + hideSkeleton: false + }) + + const formParams = new URLSearchParams() + formParams.append("teamId", this.state.teamId) + + axios.post('/api/userTeam/queryUsers', formParams, { + headers: {'content-type': 'application/x-www-form-urlencoded'} + }).then(res => { + if (res.data && res.data.code === 200) { + this.setState({ + userList: res.data.content, + hideSkeleton: true + }) + } else { + this.snackBarFail(res) + } + }).catch((error) => { + this.snackBarError(error) + }) + + } + + componentDidMount() { + this.getUserInfo() + this.getTeamMemberInfo() + } + + componentWillUnmount() { + // cancel requests + } +} \ No newline at end of file