mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-20 02:48:51 +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) => {
|
orgAdminAPI.orgAdminDeleteDepartmentRepo(orgID, this.props.groupID, this.props.repo.repo_id).then((res) => {
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
this.props.onDeleteRepo(repo.repo_id);
|
this.props.onDeleteRepo(repo.repo_id);
|
||||||
this.props.toggle();
|
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
@@ -47,7 +46,7 @@ class DeleteRepoDialog extends React.Component {
|
|||||||
const propTypes = {
|
const propTypes = {
|
||||||
repo: PropTypes.object.isRequired,
|
repo: PropTypes.object.isRequired,
|
||||||
toggle: PropTypes.func.isRequired,
|
toggle: PropTypes.func.isRequired,
|
||||||
groupID: PropTypes.string,
|
groupID: PropTypes.number,
|
||||||
onDeleteRepo: PropTypes.func.isRequired
|
onDeleteRepo: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, Modal, ModalBody, ModalFooter } from 'reactstrap';
|
import { Button, Modal, ModalBody, ModalFooter } from 'reactstrap';
|
||||||
|
import toaster from '../../../components/toast';
|
||||||
import { gettext } from '../../../utils/constants';
|
import { gettext } from '../../../utils/constants';
|
||||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||||
import { orgAdminAPI } from '../../../utils/org-admin-api';
|
import { orgAdminAPI } from '../../../utils/org-admin-api';
|
||||||
@@ -20,7 +21,7 @@ export default class AddDepartMemberV2Dialog extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
selectedOptions: [],
|
selectedOptions: [],
|
||||||
errMessage: '',
|
errMsgs: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ export default class AddDepartMemberV2Dialog extends React.Component {
|
|||||||
req.then((res) => {
|
req.then((res) => {
|
||||||
this.setState({ selectedOptions: [] });
|
this.setState({ selectedOptions: [] });
|
||||||
if (res.data.failed.length > 0) {
|
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) {
|
if (res.data.success.length > 0) {
|
||||||
this.props.onMemberChanged();
|
this.props.onMemberChanged();
|
||||||
@@ -47,21 +48,28 @@ export default class AddDepartMemberV2Dialog extends React.Component {
|
|||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
this.setState({ errMessage });
|
toaster.danger(errMessage);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { errMsgs } = this.state;
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={true} toggle={this.props.toggle}>
|
<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>
|
<ModalBody>
|
||||||
<UserSelect
|
<UserSelect
|
||||||
placeholder={gettext('Search users')}
|
placeholder={gettext('Search users')}
|
||||||
onSelectChange={this.handleSelectChange}
|
onSelectChange={this.handleSelectChange}
|
||||||
isMulti={true}
|
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>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
|
<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 Loading from '../../../components/loading';
|
||||||
import EmptyTip from '../../../components/empty-tip';
|
import EmptyTip from '../../../components/empty-tip';
|
||||||
import { gettext } from '../../../utils/constants';
|
import { gettext } from '../../../utils/constants';
|
||||||
import DepartmentsV2MembersItem from './departments-v2-members-item';
|
import MemberItem from './member-item';
|
||||||
import RepoItem from './repo-item';
|
import RepoItem from './repo-item';
|
||||||
import DepartmentNodeMenu from './departments-node-dropdown-menu';
|
import DepartmentNodeMenu from './departments-node-dropdown-menu';
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ const propTypes = {
|
|||||||
getRepos: PropTypes.func,
|
getRepos: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
class DepartmentsV2MembersList extends React.Component {
|
class Department extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -160,7 +160,7 @@ class DepartmentsV2MembersList extends React.Component {
|
|||||||
{isMembersListLoading && <Loading />}
|
{isMembersListLoading && <Loading />}
|
||||||
{!isMembersListLoading && membersList.length > 0 &&
|
{!isMembersListLoading && membersList.length > 0 &&
|
||||||
<div className='cur-view-content'>
|
<div className='cur-view-content'>
|
||||||
<Table hover>
|
<Table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="60px"></th>
|
<th width="60px"></th>
|
||||||
@@ -173,7 +173,7 @@ class DepartmentsV2MembersList extends React.Component {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{membersList.map((item, index) => {
|
{membersList.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<DepartmentsV2MembersItem
|
<MemberItem
|
||||||
key={index}
|
key={index}
|
||||||
member={item}
|
member={item}
|
||||||
deleteMember={this.props.deleteMember}
|
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 React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import DepartmentsV2TreeNode from './departments-v2-tree-node';
|
import TreeNode from './tree-node';
|
||||||
|
|
||||||
const DepartmentV2TreePanelPropTypes = {
|
const DepartmentsTreePanelPropTypes = {
|
||||||
rootNodes: PropTypes.array,
|
rootNodes: PropTypes.array,
|
||||||
checkedDepartmentId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
checkedDepartmentId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||||
onChangeDepartment: PropTypes.func,
|
onChangeDepartment: PropTypes.func,
|
||||||
@@ -14,14 +14,14 @@ const DepartmentV2TreePanelPropTypes = {
|
|||||||
toggleDelete: PropTypes.func
|
toggleDelete: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
class DepartmentV2TreePanel extends Component {
|
class DepartmentsTreePanel extends Component {
|
||||||
render() {
|
render() {
|
||||||
const { rootNodes, checkedDepartmentId } = this.props;
|
const { rootNodes, checkedDepartmentId } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="departments-tree-panel">
|
<div className="departments-tree-panel">
|
||||||
{rootNodes.map(rootNode => {
|
{rootNodes.map(rootNode => {
|
||||||
return (
|
return (
|
||||||
<DepartmentsV2TreeNode
|
<TreeNode
|
||||||
key={rootNode.id}
|
key={rootNode.id}
|
||||||
node={rootNode}
|
node={rootNode}
|
||||||
checkedDepartmentId={checkedDepartmentId}
|
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 React, { Fragment } from 'react';
|
||||||
import { Button } from 'reactstrap';
|
import { Button } from 'reactstrap';
|
||||||
import toaster from '../../../components/toast';
|
import { Utils } from '../../../utils/utils';
|
||||||
import { gettext, orgID } from '../../../utils/constants';
|
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 { orgAdminAPI } from '../../../utils/org-admin-api';
|
||||||
import DepartmentsV2MembersList from './departments-v2-members-list';
|
import toaster from '../../../components/toast';
|
||||||
import AddDepartmentV2Dialog from '../../../components/dialog/sysadmin-dialog/add-department-v2-dialog';
|
import Account from '../../../components/common/account';
|
||||||
import AddDepartMemberV2Dialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-depart-member-v2-dialog';
|
import AddDepartmentDialog from '../../../components/dialog/sysadmin-dialog/add-department-v2-dialog';
|
||||||
import RenameDepartmentV2Dialog from '../../../components/dialog/sysadmin-dialog/rename-department-v2-dialog';
|
import AddDepartMemberDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-depart-member-v2-dialog';
|
||||||
import DeleteDepartmentV2ConfirmDialog from '../../../components/dialog/sysadmin-dialog/delete-department-v2-confirm-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 AddRepoDialog from '../../../components/dialog/org-add-repo-dialog';
|
||||||
import Loading from '../../../components/loading';
|
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';
|
import '../../../css/system-departments-v2.css';
|
||||||
|
|
||||||
class DepartmentsV2 extends React.Component {
|
class Departments extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const localSortItems = localStorage.getItem('departments-members-sort-items') || {};
|
|
||||||
this.state = {
|
this.state = {
|
||||||
rootNodes: [],
|
rootNodes: [],
|
||||||
checkedDepartmentId: -1,
|
checkedDepartmentId: -1,
|
||||||
@@ -34,8 +33,8 @@ class DepartmentsV2 extends React.Component {
|
|||||||
membersList: [],
|
membersList: [],
|
||||||
isTopDepartmentLoading: false,
|
isTopDepartmentLoading: false,
|
||||||
isMembersListLoading: false,
|
isMembersListLoading: false,
|
||||||
sortBy: localSortItems.sort_by || 'name', // 'name' or 'role'
|
sortBy: 'name', // 'name' or 'role'
|
||||||
sortOrder: localSortItems.sort_order || 'asc', // 'asc' or 'desc',
|
sortOrder: 'asc', // 'asc' or 'desc',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,18 +261,15 @@ class DepartmentsV2 extends React.Component {
|
|||||||
break;
|
break;
|
||||||
case 'role-asc':
|
case 'role-asc':
|
||||||
comparator = function (a, b) {
|
comparator = function (a, b) {
|
||||||
return a.is_staff && !b.is_staff ? -1 : 1;
|
return a.role == 'Admin' ? -1 : 1;
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case 'role-desc':
|
case 'role-desc':
|
||||||
comparator = function (a, b) {
|
comparator = function (a, b) {
|
||||||
return a.is_staff && !b.is_staff ? 1 : -1;
|
return a.role == 'Admin' ? 1 : -1;
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
default:
|
// no default
|
||||||
comparator = function () {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
items.sort((a, b) => {
|
items.sort((a, b) => {
|
||||||
return comparator(a, b);
|
return comparator(a, b);
|
||||||
@@ -282,7 +278,6 @@ class DepartmentsV2 extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
sortItems = (sortBy, sortOrder) => {
|
sortItems = (sortBy, sortOrder) => {
|
||||||
localStorage.setItem('departments-members-sort-items', { sort_by: sortBy, sort_order: sortOrder });
|
|
||||||
this.setState({
|
this.setState({
|
||||||
sortBy: sortBy,
|
sortBy: sortBy,
|
||||||
sortOrder: sortOrder,
|
sortOrder: sortOrder,
|
||||||
@@ -318,7 +313,7 @@ class DepartmentsV2 extends React.Component {
|
|||||||
{isTopDepartmentLoading && <Loading/>}
|
{isTopDepartmentLoading && <Loading/>}
|
||||||
{(!isTopDepartmentLoading && rootNodes.length > 0) &&
|
{(!isTopDepartmentLoading && rootNodes.length > 0) &&
|
||||||
<>
|
<>
|
||||||
<DepartmentV2TreePanel
|
<DepartmentsTreePanel
|
||||||
rootNodes={rootNodes}
|
rootNodes={rootNodes}
|
||||||
checkedDepartmentId={checkedDepartmentId}
|
checkedDepartmentId={checkedDepartmentId}
|
||||||
onChangeDepartment={this.onChangeDepartment}
|
onChangeDepartment={this.onChangeDepartment}
|
||||||
@@ -329,7 +324,7 @@ class DepartmentsV2 extends React.Component {
|
|||||||
toggleRename={this.toggleRename}
|
toggleRename={this.toggleRename}
|
||||||
toggleDelete={this.toggleDelete}
|
toggleDelete={this.toggleDelete}
|
||||||
/>
|
/>
|
||||||
<DepartmentsV2MembersList
|
<Department
|
||||||
rootNodes={rootNodes}
|
rootNodes={rootNodes}
|
||||||
checkedDepartmentId={checkedDepartmentId}
|
checkedDepartmentId={checkedDepartmentId}
|
||||||
membersList={membersList}
|
membersList={membersList}
|
||||||
@@ -362,7 +357,7 @@ class DepartmentsV2 extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isAddMembersDialogShow &&
|
{isAddMembersDialogShow &&
|
||||||
<AddDepartMemberV2Dialog
|
<AddDepartMemberDialog
|
||||||
orgID={orgID}
|
orgID={orgID}
|
||||||
toggle={this.toggleAddMembers}
|
toggle={this.toggleAddMembers}
|
||||||
nodeId={operateNode.id}
|
nodeId={operateNode.id}
|
||||||
@@ -370,7 +365,7 @@ class DepartmentsV2 extends React.Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{isRenameDepartmentDialogShow &&
|
{isRenameDepartmentDialogShow &&
|
||||||
<RenameDepartmentV2Dialog
|
<RenameDepartmentDialog
|
||||||
orgID={orgID}
|
orgID={orgID}
|
||||||
node={operateNode}
|
node={operateNode}
|
||||||
toggle={this.toggleRename}
|
toggle={this.toggleRename}
|
||||||
@@ -378,14 +373,14 @@ class DepartmentsV2 extends React.Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{isDeleteDepartmentDialogShow &&
|
{isDeleteDepartmentDialogShow &&
|
||||||
<DeleteDepartmentV2ConfirmDialog
|
<DeleteDepartmentConfirmDialog
|
||||||
node={operateNode}
|
node={operateNode}
|
||||||
toggle={this.toggleDelete}
|
toggle={this.toggleDelete}
|
||||||
onDelete={this.onDelete}
|
onDelete={this.onDelete}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{isAddDepartmentDialogShow &&
|
{isAddDepartmentDialogShow &&
|
||||||
<AddDepartmentV2Dialog
|
<AddDepartmentDialog
|
||||||
parentNode={operateNode}
|
parentNode={operateNode}
|
||||||
toggle={this.toggleAddDepartment}
|
toggle={this.toggleAddDepartment}
|
||||||
addDepartment={this.addDepartment}
|
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 = {
|
const RepoItemPropTypes = {
|
||||||
repo: PropTypes.object.isRequired,
|
repo: PropTypes.object.isRequired,
|
||||||
groupID: PropTypes.string.isRequired,
|
groupID: PropTypes.number.isRequired,
|
||||||
onDeleteRepo: PropTypes.func.isRequired,
|
onDeleteRepo: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
@@ -29,7 +29,7 @@ import OrgTrashRepos from './libraries/org-repo-trash';
|
|||||||
import OrgInfo from './org-info';
|
import OrgInfo from './org-info';
|
||||||
import OrgLinks from './org-links';
|
import OrgLinks from './org-links';
|
||||||
|
|
||||||
import DepartmentsV2 from './departments-v2/departments-v2';
|
import Departments from './departments/departments';
|
||||||
|
|
||||||
import OrgLogs from './org-logs';
|
import OrgLogs from './org-logs';
|
||||||
import OrgLogsFileAudit from './org-logs-file-audit';
|
import OrgLogsFileAudit from './org-logs-file-audit';
|
||||||
@@ -120,7 +120,7 @@ class Org extends React.Component {
|
|||||||
<OrgAllRepos path={siteRoot + 'org/repoadmin'}/>
|
<OrgAllRepos path={siteRoot + 'org/repoadmin'}/>
|
||||||
<OrgTrashRepos path={siteRoot + 'org/repoadmin-trash'}/>
|
<OrgTrashRepos path={siteRoot + 'org/repoadmin-trash'}/>
|
||||||
<OrgLinks path={siteRoot + 'org/publinkadmin'}/>
|
<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}>
|
<OrgLogs path={siteRoot + 'org/logadmin'} currentTab={currentTab} tabItemClick={this.tabItemClick}>
|
||||||
<OrgLogsFileAudit path='/' />
|
<OrgLogsFileAudit path='/' />
|
||||||
<OrgLogsFileUpdate path='file-update' />
|
<OrgLogsFileUpdate path='file-update' />
|
||||||
|
@@ -464,10 +464,12 @@ class OrgAdminAPI {
|
|||||||
return this.req.delete(url);
|
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/';
|
const url = this.server + '/api/v2.1/org/' + orgID + '/admin/groups/' + groupID + '/members/';
|
||||||
let form = new FormData();
|
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);
|
return this._sendPostRequest(url, form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user