mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-19 01:44:13 +00:00
[org admin - departments] fixup & improvements (#7330)
- improved the UX for 'member item' - added confirm dialog for 'delete member' - fixed API & displaying error msgs for adding multiple members - fixed 'delete repo' - fixed sorting members ascending by 'role' - removed 'store the sort way in localStorage' - removed 'v2' from code & file names
This commit is contained in:
@@ -1,77 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Modal, ModalBody, ModalFooter, Alert } from 'reactstrap';
|
||||
import { gettext, orgID } from '../../utils/constants';
|
||||
import { orgAdminAPI } from '../../utils/org-admin-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../toast';
|
||||
import UserSelect from '../user-select';
|
||||
import SeahubModalHeader from '@/components/common/seahub-modal-header';
|
||||
|
||||
const propTypes = {
|
||||
toggle: PropTypes.func.isRequired,
|
||||
groupID: PropTypes.string.isRequired,
|
||||
onAddNewMembers: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class AddMemberDialog extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOption: null,
|
||||
errMessage: '',
|
||||
};
|
||||
}
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({ selectedOption: option });
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
if (!this.state.selectedOption) return;
|
||||
const email = this.state.selectedOption[0].email;
|
||||
this.refs.orgSelect.clearSelect();
|
||||
this.setState({ errMessage: [] });
|
||||
orgAdminAPI.orgAdminAddGroupMember(orgID, this.props.groupID, email).then((res) => {
|
||||
this.setState({ selectedOption: null });
|
||||
if (res.data.failed.length > 0) {
|
||||
this.setState({ errMessage: res.data.failed[0].error_msg });
|
||||
}
|
||||
if (res.data.success.length > 0) {
|
||||
this.props.onAddNewMembers(res.data.success);
|
||||
this.props.toggle();
|
||||
}
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { errMessage } = this.state;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.props.toggle}>
|
||||
<SeahubModalHeader toggle={this.props.toggle}>{gettext('Add Member')}</SeahubModalHeader>
|
||||
<ModalBody>
|
||||
<UserSelect
|
||||
placeholder={gettext('Search users')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
ref="orgSelect"
|
||||
isMulti={true}
|
||||
className='org-add-member-select'
|
||||
/>
|
||||
{errMessage && <Alert color="danger" className="mt-2">{errMessage}</Alert>}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddMemberDialog.propTypes = propTypes;
|
||||
|
||||
export default AddMemberDialog;
|
@@ -1,56 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Modal, ModalBody, ModalFooter } from 'reactstrap';
|
||||
import { gettext, orgID } from '../../utils/constants';
|
||||
import { orgAdminAPI } from '../../utils/org-admin-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../toast';
|
||||
import SeahubModalHeader from '@/components/common/seahub-modal-header';
|
||||
|
||||
const propTypes = {
|
||||
member: PropTypes.object.isRequired,
|
||||
groupID: PropTypes.string,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
onMemberChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class DeleteMemberDialog extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
deleteMember = () => {
|
||||
const userEmail = this.props.member.email;
|
||||
orgAdminAPI.orgAdminDeleteGroupMember(orgID, this.props.groupID, userEmail).then((res) => {
|
||||
if (res.data.success) {
|
||||
this.props.onMemberChanged();
|
||||
this.props.toggle();
|
||||
}
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
let subtitle = gettext('Are you sure you want to delete {placeholder} ?');
|
||||
subtitle = subtitle.replace('{placeholder}', '<span class="op-target">' + Utils.HTMLescape(this.props.member.name) + '</span>');
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.props.toggle}>
|
||||
<SeahubModalHeader toggle={this.props.toggle}>{gettext('Delete Member')}</SeahubModalHeader>
|
||||
<ModalBody>
|
||||
<div dangerouslySetInnerHTML={{ __html: subtitle }}></div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.deleteMember}>{gettext('Delete')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DeleteMemberDialog.propTypes = propTypes;
|
||||
|
||||
export default DeleteMemberDialog;
|
@@ -18,7 +18,6 @@ class DeleteRepoDialog extends React.Component {
|
||||
orgAdminAPI.orgAdminDeleteDepartmentRepo(orgID, this.props.groupID, this.props.repo.repo_id).then((res) => {
|
||||
if (res.data.success) {
|
||||
this.props.onDeleteRepo(repo.repo_id);
|
||||
this.props.toggle();
|
||||
}
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
@@ -47,7 +46,7 @@ class DeleteRepoDialog extends React.Component {
|
||||
const propTypes = {
|
||||
repo: PropTypes.object.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
groupID: PropTypes.string,
|
||||
groupID: PropTypes.number,
|
||||
onDeleteRepo: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Modal, ModalBody, ModalFooter } from 'reactstrap';
|
||||
import toaster from '../../../components/toast';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||
import { orgAdminAPI } from '../../../utils/org-admin-api';
|
||||
@@ -20,7 +21,7 @@ export default class AddDepartMemberV2Dialog extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOptions: [],
|
||||
errMessage: '',
|
||||
errMsgs: '',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -39,7 +40,7 @@ export default class AddDepartMemberV2Dialog extends React.Component {
|
||||
req.then((res) => {
|
||||
this.setState({ selectedOptions: [] });
|
||||
if (res.data.failed.length > 0) {
|
||||
this.setState({ errMessage: res.data.failed[0].error_msg });
|
||||
this.setState({ errMsgs: res.data.failed.map(item => item.error_msg) });
|
||||
}
|
||||
if (res.data.success.length > 0) {
|
||||
this.props.onMemberChanged();
|
||||
@@ -47,21 +48,28 @@ export default class AddDepartMemberV2Dialog extends React.Component {
|
||||
}
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
this.setState({ errMessage });
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { errMsgs } = this.state;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.props.toggle}>
|
||||
<SeahubModalHeader toggle={this.props.toggle}>{gettext('Add member')}</SeahubModalHeader>
|
||||
<SeahubModalHeader toggle={this.props.toggle}>{gettext('Add members')}</SeahubModalHeader>
|
||||
<ModalBody>
|
||||
<UserSelect
|
||||
placeholder={gettext('Search users')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
isMulti={true}
|
||||
/>
|
||||
{this.state.errMessage && <p className="error mt-2">{this.state.errMessage}</p> }
|
||||
{errMsgs.length > 0 && (
|
||||
<ul className="list-unstyled">
|
||||
{errMsgs.map((item, index) => {
|
||||
return <li key={index} className="error mt-2">{item}</li>;
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
|
||||
|
@@ -1,120 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||
import RoleSelector from '../../../components/single-selector';
|
||||
import { gettext, siteRoot } from '../../../utils/constants';
|
||||
|
||||
const propTypes = {
|
||||
isItemFreezed: PropTypes.bool,
|
||||
member: PropTypes.object,
|
||||
setMemberStaff: PropTypes.func,
|
||||
deleteMember: PropTypes.func,
|
||||
unfreezeItem: PropTypes.func,
|
||||
freezeItem: PropTypes.func,
|
||||
toggleItemFreezed: PropTypes.func,
|
||||
};
|
||||
|
||||
class DepartmentsV2MembersItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
dropdownOpenEmail: '',
|
||||
isShowDropdownMenu: false,
|
||||
isItemMenuShow: false,
|
||||
};
|
||||
this.roleOptions = [
|
||||
{ value: 'Admin', text: gettext('Admin'), isSelected: false },
|
||||
{ value: 'Member', text: gettext('Member'), isSelected: false }
|
||||
];
|
||||
}
|
||||
|
||||
onMouseEnter = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({ isShowDropdownMenu: true });
|
||||
}
|
||||
};
|
||||
|
||||
onMouseLeave = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({ isShowDropdownMenu: false });
|
||||
}
|
||||
};
|
||||
|
||||
setMemberStaff = (role) => {
|
||||
this.props.setMemberStaff(this.props.member.email, role.value === 'Admin');
|
||||
};
|
||||
|
||||
deleteMember = (e) => {
|
||||
e.stopPropagation();
|
||||
const { member } = this.props;
|
||||
this.props.deleteMember(member.email);
|
||||
};
|
||||
|
||||
toggleDropdownMenu = () => {
|
||||
this.setState({
|
||||
isItemMenuShow: !this.state.isItemMenuShow
|
||||
}, () => {
|
||||
if (this.state.isItemMenuShow && typeof(this.props.freezeItem) === 'function') {
|
||||
this.props.freezeItem();
|
||||
} else if (!this.state.isItemMenuShow && typeof(this.props.unfreezeItem) === 'function') {
|
||||
this.props.unfreezeItem();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { member, freezeItem, unfreezeItem } = this.props;
|
||||
const { isShowDropdownMenu, isItemMenuShow } = this.state;
|
||||
|
||||
this.roleOptions = this.roleOptions.map(item => {
|
||||
item.isSelected = item.value == member.role;
|
||||
return item;
|
||||
});
|
||||
const currentSelectedOption = this.roleOptions.filter(item => item.isSelected)[0];
|
||||
|
||||
return (
|
||||
<tr className="departments-members-item" key={member.email} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<td><img className="avatar" src={member.avatar_url} alt="" /></td>
|
||||
<td className='text-truncate'>
|
||||
<a href={`${siteRoot}org/useradmin/info/${encodeURIComponent(member.email)}/`}>{member.name}</a>
|
||||
</td>
|
||||
<td>
|
||||
<RoleSelector
|
||||
isDropdownToggleShown={isShowDropdownMenu}
|
||||
currentSelectedOption={currentSelectedOption}
|
||||
options={this.roleOptions}
|
||||
selectOption={this.setMemberStaff}
|
||||
toggleItemFreezed={(freeze) => { freeze ? freezeItem() : unfreezeItem(); }}
|
||||
/>
|
||||
</td>
|
||||
<td>{member.contact_email}</td>
|
||||
<td>
|
||||
{isShowDropdownMenu &&
|
||||
<Dropdown
|
||||
isOpen={isItemMenuShow}
|
||||
toggle={this.toggleDropdownMenu}
|
||||
direction="down"
|
||||
>
|
||||
<DropdownToggle
|
||||
tag='a'
|
||||
role="button"
|
||||
className='attr-action-icon sf3-font sf3-font-more-vertical'
|
||||
title={gettext('More operations')}
|
||||
aria-label={gettext('More operations')}
|
||||
data-toggle="dropdown"
|
||||
/>
|
||||
<DropdownMenu right={true}>
|
||||
<DropdownItem key='delete' onClick={this.deleteMember}>{gettext('Delete')}</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DepartmentsV2MembersItem.propTypes = propTypes;
|
||||
|
||||
export default DepartmentsV2MembersItem;
|
@@ -4,7 +4,7 @@ import { Table, Dropdown, DropdownToggle } from 'reactstrap';
|
||||
import Loading from '../../../components/loading';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import DepartmentsV2MembersItem from './departments-v2-members-item';
|
||||
import MemberItem from './member-item';
|
||||
import RepoItem from './repo-item';
|
||||
import DepartmentNodeMenu from './departments-node-dropdown-menu';
|
||||
|
||||
@@ -21,7 +21,7 @@ const propTypes = {
|
||||
getRepos: PropTypes.func,
|
||||
};
|
||||
|
||||
class DepartmentsV2MembersList extends React.Component {
|
||||
class Department extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -160,7 +160,7 @@ class DepartmentsV2MembersList extends React.Component {
|
||||
{isMembersListLoading && <Loading />}
|
||||
{!isMembersListLoading && membersList.length > 0 &&
|
||||
<div className='cur-view-content'>
|
||||
<Table hover>
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="60px"></th>
|
||||
@@ -173,7 +173,7 @@ class DepartmentsV2MembersList extends React.Component {
|
||||
<tbody>
|
||||
{membersList.map((item, index) => {
|
||||
return (
|
||||
<DepartmentsV2MembersItem
|
||||
<MemberItem
|
||||
key={index}
|
||||
member={item}
|
||||
deleteMember={this.props.deleteMember}
|
||||
@@ -229,6 +229,6 @@ class DepartmentsV2MembersList extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
DepartmentsV2MembersList.propTypes = propTypes;
|
||||
Department.propTypes = propTypes;
|
||||
|
||||
export default DepartmentsV2MembersList;
|
||||
export default Department;
|
@@ -1,8 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import DepartmentsV2TreeNode from './departments-v2-tree-node';
|
||||
import TreeNode from './tree-node';
|
||||
|
||||
const DepartmentV2TreePanelPropTypes = {
|
||||
const DepartmentsTreePanelPropTypes = {
|
||||
rootNodes: PropTypes.array,
|
||||
checkedDepartmentId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
onChangeDepartment: PropTypes.func,
|
||||
@@ -14,14 +14,14 @@ const DepartmentV2TreePanelPropTypes = {
|
||||
toggleDelete: PropTypes.func
|
||||
};
|
||||
|
||||
class DepartmentV2TreePanel extends Component {
|
||||
class DepartmentsTreePanel extends Component {
|
||||
render() {
|
||||
const { rootNodes, checkedDepartmentId } = this.props;
|
||||
return (
|
||||
<div className="departments-tree-panel">
|
||||
{rootNodes.map(rootNode => {
|
||||
return (
|
||||
<DepartmentsV2TreeNode
|
||||
<TreeNode
|
||||
key={rootNode.id}
|
||||
node={rootNode}
|
||||
checkedDepartmentId={checkedDepartmentId}
|
||||
@@ -40,6 +40,6 @@ class DepartmentV2TreePanel extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
DepartmentV2TreePanel.propTypes = DepartmentV2TreePanelPropTypes;
|
||||
DepartmentsTreePanel.propTypes = DepartmentsTreePanelPropTypes;
|
||||
|
||||
export default DepartmentV2TreePanel;
|
||||
export default DepartmentsTreePanel;
|
@@ -1,27 +1,26 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Button } from 'reactstrap';
|
||||
import toaster from '../../../components/toast';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { gettext, orgID } from '../../../utils/constants';
|
||||
import Account from '../../../components/common/account';
|
||||
import DepartmentNode from './department-node';
|
||||
import DepartmentV2TreePanel from './departments-v2-tree-panel';
|
||||
import { orgAdminAPI } from '../../../utils/org-admin-api';
|
||||
import DepartmentsV2MembersList from './departments-v2-members-list';
|
||||
import AddDepartmentV2Dialog from '../../../components/dialog/sysadmin-dialog/add-department-v2-dialog';
|
||||
import AddDepartMemberV2Dialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-depart-member-v2-dialog';
|
||||
import RenameDepartmentV2Dialog from '../../../components/dialog/sysadmin-dialog/rename-department-v2-dialog';
|
||||
import DeleteDepartmentV2ConfirmDialog from '../../../components/dialog/sysadmin-dialog/delete-department-v2-confirm-dialog';
|
||||
import toaster from '../../../components/toast';
|
||||
import Account from '../../../components/common/account';
|
||||
import AddDepartmentDialog from '../../../components/dialog/sysadmin-dialog/add-department-v2-dialog';
|
||||
import AddDepartMemberDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-depart-member-v2-dialog';
|
||||
import RenameDepartmentDialog from '../../../components/dialog/sysadmin-dialog/rename-department-v2-dialog';
|
||||
import DeleteDepartmentConfirmDialog from '../../../components/dialog/sysadmin-dialog/delete-department-v2-confirm-dialog';
|
||||
import AddRepoDialog from '../../../components/dialog/org-add-repo-dialog';
|
||||
import Loading from '../../../components/loading';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import DepartmentNode from './department-node';
|
||||
import DepartmentsTreePanel from './departments-tree-panel';
|
||||
import Department from './department';
|
||||
|
||||
import '../../../css/system-departments-v2.css';
|
||||
|
||||
class DepartmentsV2 extends React.Component {
|
||||
class Departments extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const localSortItems = localStorage.getItem('departments-members-sort-items') || {};
|
||||
this.state = {
|
||||
rootNodes: [],
|
||||
checkedDepartmentId: -1,
|
||||
@@ -34,8 +33,8 @@ class DepartmentsV2 extends React.Component {
|
||||
membersList: [],
|
||||
isTopDepartmentLoading: false,
|
||||
isMembersListLoading: false,
|
||||
sortBy: localSortItems.sort_by || 'name', // 'name' or 'role'
|
||||
sortOrder: localSortItems.sort_order || 'asc', // 'asc' or 'desc',
|
||||
sortBy: 'name', // 'name' or 'role'
|
||||
sortOrder: 'asc', // 'asc' or 'desc',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -262,18 +261,15 @@ class DepartmentsV2 extends React.Component {
|
||||
break;
|
||||
case 'role-asc':
|
||||
comparator = function (a, b) {
|
||||
return a.is_staff && !b.is_staff ? -1 : 1;
|
||||
return a.role == 'Admin' ? -1 : 1;
|
||||
};
|
||||
break;
|
||||
case 'role-desc':
|
||||
comparator = function (a, b) {
|
||||
return a.is_staff && !b.is_staff ? 1 : -1;
|
||||
return a.role == 'Admin' ? 1 : -1;
|
||||
};
|
||||
break;
|
||||
default:
|
||||
comparator = function () {
|
||||
return true;
|
||||
};
|
||||
// no default
|
||||
}
|
||||
items.sort((a, b) => {
|
||||
return comparator(a, b);
|
||||
@@ -282,7 +278,6 @@ class DepartmentsV2 extends React.Component {
|
||||
};
|
||||
|
||||
sortItems = (sortBy, sortOrder) => {
|
||||
localStorage.setItem('departments-members-sort-items', { sort_by: sortBy, sort_order: sortOrder });
|
||||
this.setState({
|
||||
sortBy: sortBy,
|
||||
sortOrder: sortOrder,
|
||||
@@ -318,7 +313,7 @@ class DepartmentsV2 extends React.Component {
|
||||
{isTopDepartmentLoading && <Loading/>}
|
||||
{(!isTopDepartmentLoading && rootNodes.length > 0) &&
|
||||
<>
|
||||
<DepartmentV2TreePanel
|
||||
<DepartmentsTreePanel
|
||||
rootNodes={rootNodes}
|
||||
checkedDepartmentId={checkedDepartmentId}
|
||||
onChangeDepartment={this.onChangeDepartment}
|
||||
@@ -329,7 +324,7 @@ class DepartmentsV2 extends React.Component {
|
||||
toggleRename={this.toggleRename}
|
||||
toggleDelete={this.toggleDelete}
|
||||
/>
|
||||
<DepartmentsV2MembersList
|
||||
<Department
|
||||
rootNodes={rootNodes}
|
||||
checkedDepartmentId={checkedDepartmentId}
|
||||
membersList={membersList}
|
||||
@@ -362,7 +357,7 @@ class DepartmentsV2 extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
{isAddMembersDialogShow &&
|
||||
<AddDepartMemberV2Dialog
|
||||
<AddDepartMemberDialog
|
||||
orgID={orgID}
|
||||
toggle={this.toggleAddMembers}
|
||||
nodeId={operateNode.id}
|
||||
@@ -370,7 +365,7 @@ class DepartmentsV2 extends React.Component {
|
||||
/>
|
||||
}
|
||||
{isRenameDepartmentDialogShow &&
|
||||
<RenameDepartmentV2Dialog
|
||||
<RenameDepartmentDialog
|
||||
orgID={orgID}
|
||||
node={operateNode}
|
||||
toggle={this.toggleRename}
|
||||
@@ -378,14 +373,14 @@ class DepartmentsV2 extends React.Component {
|
||||
/>
|
||||
}
|
||||
{isDeleteDepartmentDialogShow &&
|
||||
<DeleteDepartmentV2ConfirmDialog
|
||||
<DeleteDepartmentConfirmDialog
|
||||
node={operateNode}
|
||||
toggle={this.toggleDelete}
|
||||
onDelete={this.onDelete}
|
||||
/>
|
||||
}
|
||||
{isAddDepartmentDialogShow &&
|
||||
<AddDepartmentV2Dialog
|
||||
<AddDepartmentDialog
|
||||
parentNode={operateNode}
|
||||
toggle={this.toggleAddDepartment}
|
||||
addDepartment={this.addDepartment}
|
||||
@@ -406,4 +401,4 @@ class DepartmentsV2 extends React.Component {
|
||||
|
||||
}
|
||||
|
||||
export default DepartmentsV2;
|
||||
export default Departments;
|
149
frontend/src/pages/org-admin/departments/member-item.js
Normal file
149
frontend/src/pages/org-admin/departments/member-item.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||
import RoleSelector from '../../../components/single-selector';
|
||||
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||
import { gettext, siteRoot } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
|
||||
const propTypes = {
|
||||
isItemFreezed: PropTypes.bool,
|
||||
member: PropTypes.object,
|
||||
setMemberStaff: PropTypes.func,
|
||||
deleteMember: PropTypes.func,
|
||||
unfreezeItem: PropTypes.func,
|
||||
freezeItem: PropTypes.func,
|
||||
toggleItemFreezed: PropTypes.func,
|
||||
};
|
||||
|
||||
class DepartmentsV2MembersItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
highlighted: false,
|
||||
isItemMenuShow: false,
|
||||
isDeleteMemberDialogOpen: false
|
||||
};
|
||||
this.roleOptions = [
|
||||
{ value: 'Admin', text: gettext('Admin'), isSelected: false },
|
||||
{ value: 'Member', text: gettext('Member'), isSelected: false }
|
||||
];
|
||||
}
|
||||
|
||||
handleMouseEnter = () => {
|
||||
if (this.props.isItemFreezed) return;
|
||||
this.setState({ highlighted: true });
|
||||
};
|
||||
|
||||
handleMouseLeave = () => {
|
||||
if (this.props.isItemFreezed) return;
|
||||
this.setState({ highlighted: false });
|
||||
};
|
||||
|
||||
setMemberStaff = (role) => {
|
||||
this.props.setMemberStaff(this.props.member.email, role.value === 'Admin');
|
||||
};
|
||||
|
||||
deleteMember = () => {
|
||||
const { member } = this.props;
|
||||
this.props.deleteMember(member.email);
|
||||
};
|
||||
|
||||
toggleDropdownMenu = () => {
|
||||
this.setState({
|
||||
isItemMenuShow: !this.state.isItemMenuShow
|
||||
}, () => {
|
||||
if (this.state.isItemMenuShow && typeof(this.props.freezeItem) === 'function') {
|
||||
this.props.freezeItem();
|
||||
} else if (!this.state.isItemMenuShow && typeof(this.props.unfreezeItem) === 'function') {
|
||||
this.props.unfreezeItem();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
toggleItemFreezed = (freezed) => {
|
||||
if (freezed) {
|
||||
this.props.freezeItem();
|
||||
} else {
|
||||
this.props.unfreezeItem();
|
||||
this.setState({ highlighted: false });
|
||||
}
|
||||
};
|
||||
|
||||
toggleDeleteMemberDialog = () => {
|
||||
this.setState({
|
||||
isDeleteMemberDialogOpen: !this.state.isDeleteMemberDialogOpen
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { member } = this.props;
|
||||
const { highlighted, isItemMenuShow, isDeleteMemberDialogOpen } = this.state;
|
||||
|
||||
this.roleOptions = this.roleOptions.map(item => {
|
||||
item.isSelected = item.value == member.role;
|
||||
return item;
|
||||
});
|
||||
const currentSelectedOption = this.roleOptions.filter(item => item.isSelected)[0];
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr className={`departments-members-item ${highlighted ? 'tr-highlight' : ''}`} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<td><img className="avatar" src={member.avatar_url} alt="" /></td>
|
||||
<td className='text-truncate'>
|
||||
<a href={`${siteRoot}org/useradmin/info/${encodeURIComponent(member.email)}/`}>{member.name}</a>
|
||||
</td>
|
||||
<td>
|
||||
<RoleSelector
|
||||
isDropdownToggleShown={highlighted}
|
||||
currentSelectedOption={currentSelectedOption}
|
||||
options={this.roleOptions}
|
||||
selectOption={this.setMemberStaff}
|
||||
toggleItemFreezed={this.toggleItemFreezed}
|
||||
/>
|
||||
</td>
|
||||
<td>{member.contact_email}</td>
|
||||
<td>
|
||||
{highlighted &&
|
||||
<Dropdown
|
||||
isOpen={isItemMenuShow}
|
||||
toggle={this.toggleDropdownMenu}
|
||||
direction="down"
|
||||
>
|
||||
<DropdownToggle
|
||||
tag='a'
|
||||
role="button"
|
||||
className='attr-action-icon sf3-font sf3-font-more-vertical'
|
||||
title={gettext('More operations')}
|
||||
aria-label={gettext('More operations')}
|
||||
data-toggle="dropdown"
|
||||
/>
|
||||
<DropdownMenu right={true}>
|
||||
<DropdownItem key='delete' onClick={this.toggleDeleteMemberDialog}>{gettext('Delete')}</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
{isDeleteMemberDialogOpen && (
|
||||
<CommonOperationConfirmationDialog
|
||||
title={gettext('Delete Member')}
|
||||
message={
|
||||
gettext('Are you sure you want to delete {placeholder} ?')
|
||||
.replace('{placeholder}', '<span class="op-target">' + Utils.HTMLescape(member.name) + '</span>')
|
||||
}
|
||||
executeOperation={this.deleteMember}
|
||||
confirmBtnText={gettext('Delete')}
|
||||
toggleDialog={this.toggleDeleteMemberDialog}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DepartmentsV2MembersItem.propTypes = propTypes;
|
||||
|
||||
export default DepartmentsV2MembersItem;
|
@@ -64,7 +64,7 @@ class RepoItem extends React.Component {
|
||||
|
||||
const RepoItemPropTypes = {
|
||||
repo: PropTypes.object.isRequired,
|
||||
groupID: PropTypes.string.isRequired,
|
||||
groupID: PropTypes.number.isRequired,
|
||||
onDeleteRepo: PropTypes.func.isRequired,
|
||||
};
|
||||
|
@@ -29,7 +29,7 @@ import OrgTrashRepos from './libraries/org-repo-trash';
|
||||
import OrgInfo from './org-info';
|
||||
import OrgLinks from './org-links';
|
||||
|
||||
import DepartmentsV2 from './departments-v2/departments-v2';
|
||||
import Departments from './departments/departments';
|
||||
|
||||
import OrgLogs from './org-logs';
|
||||
import OrgLogsFileAudit from './org-logs-file-audit';
|
||||
@@ -120,7 +120,7 @@ class Org extends React.Component {
|
||||
<OrgAllRepos path={siteRoot + 'org/repoadmin'}/>
|
||||
<OrgTrashRepos path={siteRoot + 'org/repoadmin-trash'}/>
|
||||
<OrgLinks path={siteRoot + 'org/publinkadmin'}/>
|
||||
<DepartmentsV2 path={siteRoot + 'org/departmentadmin/'} />
|
||||
<Departments path={siteRoot + 'org/departmentadmin/'} />
|
||||
<OrgLogs path={siteRoot + 'org/logadmin'} currentTab={currentTab} tabItemClick={this.tabItemClick}>
|
||||
<OrgLogsFileAudit path='/' />
|
||||
<OrgLogsFileUpdate path='file-update' />
|
||||
|
@@ -464,10 +464,12 @@ class OrgAdminAPI {
|
||||
return this.req.delete(url);
|
||||
}
|
||||
|
||||
orgAdminAddGroupMember(orgID, groupID, userEmail) {
|
||||
orgAdminAddGroupMember(orgID, groupID, emails) {
|
||||
const url = this.server + '/api/v2.1/org/' + orgID + '/admin/groups/' + groupID + '/members/';
|
||||
let form = new FormData();
|
||||
form.append('email', userEmail);
|
||||
for (let i = 0; i < emails.length; i++) {
|
||||
form.append('email', emails[i]);
|
||||
}
|
||||
return this._sendPostRequest(url, form);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user