mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-09 10:50:24 +00:00
change system admin department UI (#7010)
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Form, FormGroup, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
|
||||
import toaster from '../../toast';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { Utils, validateName } from '../../../utils/utils';
|
||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||
|
||||
const propTypes = {
|
||||
parentNode: PropTypes.object,
|
||||
addDepartment: PropTypes.func,
|
||||
toggle: PropTypes.func,
|
||||
setRootNode: PropTypes.func
|
||||
};
|
||||
|
||||
class AddDepartmentV2Dialog extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
departName: '',
|
||||
isSubmitBtnActive: false
|
||||
};
|
||||
}
|
||||
|
||||
onKeyDown = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleSubmit();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
handleChange = (e) => {
|
||||
this.setState({
|
||||
departName: e.target.value
|
||||
}, () => {
|
||||
this.setState({ isSubmitBtnActive: !!this.state.departName.trim() });
|
||||
});
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
let response = validateName(this.state.departName.trim());
|
||||
if (!response.isValid) {
|
||||
this.setState({ errMessage: response.message });
|
||||
return;
|
||||
}
|
||||
const { parentNode } = this.props;
|
||||
const parentNodeId = parentNode ? parentNode.id : -1;
|
||||
systemAdminAPI.sysAdminAddNewDepartment(parentNodeId, this.state.departName.trim()).then((res) => {
|
||||
if (parentNode) {
|
||||
this.props.addDepartment(parentNode, res.data);
|
||||
} else {
|
||||
this.props.setRootNode(res.data);
|
||||
}
|
||||
this.props.toggle();
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { parentNode } = this.props;
|
||||
const { isSubmitBtnActive } = this.state;
|
||||
let title;
|
||||
if (parentNode) {
|
||||
title = gettext('Add department at') + ' ' + parentNode.name;
|
||||
} else {
|
||||
title = gettext('Create top department');
|
||||
}
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.props.toggle} autoFocus={false}>
|
||||
<ModalHeader toggle={this.props.toggle}>{title}</ModalHeader>
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<Label for="departmentName">{gettext('Name')}</Label>
|
||||
<Input
|
||||
id="departmentName"
|
||||
onKeyDown={this.onKeyDown}
|
||||
value={this.state.departName}
|
||||
onChange={this.handleChange}
|
||||
autoFocus
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.handleSubmit} disabled={!isSubmitBtnActive}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddDepartmentV2Dialog.propTypes = propTypes;
|
||||
|
||||
export default AddDepartmentV2Dialog;
|
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Modal, ModalBody, ModalFooter, ModalHeader, Button } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
|
||||
const propTypes = {
|
||||
node: PropTypes.object,
|
||||
toggle: PropTypes.func,
|
||||
onDelete: PropTypes.func
|
||||
};
|
||||
|
||||
class DeleteDepartmentV2ConfirmDialog extends React.Component {
|
||||
render() {
|
||||
const { node, toggle } = this.props;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={toggle}>
|
||||
<ModalHeader toggle={toggle}>
|
||||
{gettext('Delete department')}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<p>{gettext('Are you sure to delete')}{' '}<b>{node.name}</b> ?</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onMouseDown={toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onMouseDown={this.props.onDelete}>{gettext('Delete')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DeleteDepartmentV2ConfirmDialog.propTypes = propTypes;
|
||||
|
||||
export default DeleteDepartmentV2ConfirmDialog;
|
@@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Form, FormGroup, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
|
||||
import toaster from '../../toast';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { Utils, validateName } from '../../../utils/utils';
|
||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||
|
||||
const propTypes = {
|
||||
node: PropTypes.object,
|
||||
renameDepartment: PropTypes.func,
|
||||
toggle: PropTypes.func
|
||||
};
|
||||
|
||||
class RenameDepartmentV2Dialog extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
departName: props.node.name,
|
||||
isSubmitBtnActive: false
|
||||
};
|
||||
}
|
||||
|
||||
onKeyDown = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleSubmit();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
handleChange = (e) => {
|
||||
const value = e.target.value;
|
||||
this.setState({
|
||||
departName: value
|
||||
}, () => {
|
||||
this.setState({ isSubmitBtnActive: !!this.state.departName.trim() });
|
||||
});
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
let response = validateName(this.state.departName.trim());
|
||||
if (!response.isValid) {
|
||||
this.setState({ errMessage: response.message });
|
||||
return;
|
||||
}
|
||||
const { node } = this.props;
|
||||
systemAdminAPI.sysAdminRenameDepartment(node.id, this.state.departName.trim()).then((res) => {
|
||||
this.props.renameDepartment(node, res.data);
|
||||
this.props.toggle();
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isSubmitBtnActive } = this.state;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.props.toggle} autoFocus={false}>
|
||||
<ModalHeader toggle={this.props.toggle}>{gettext('Rename')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<Label for="departmentName">{gettext('Name')}</Label>
|
||||
<Input
|
||||
id="departmentName"
|
||||
onKeyDown={this.onKeyDown}
|
||||
value={this.state.departName}
|
||||
onChange={this.handleChange}
|
||||
autoFocus
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.handleSubmit} disabled={!isSubmitBtnActive}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RenameDepartmentV2Dialog.propTypes = propTypes;
|
||||
|
||||
export default RenameDepartmentV2Dialog;
|
@@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import UserSelect from '../../user-select';
|
||||
|
||||
export default class AddDepartMemberV2Dialog extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
toggle: PropTypes.func.isRequired,
|
||||
nodeId: PropTypes.number.isRequired,
|
||||
onMemberChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOptions: [],
|
||||
errMessage: '',
|
||||
};
|
||||
}
|
||||
|
||||
handleSelectChange = (options) => {
|
||||
this.setState({ selectedOptions: options });
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
const emails = this.state.selectedOptions.map(option => option.email);
|
||||
if (emails.length === 0) return;
|
||||
this.setState({ errMessage: '' });
|
||||
systemAdminAPI.sysAdminAddGroupMember(this.props.nodeId, emails).then((res) => {
|
||||
this.setState({ selectedOptions: [] });
|
||||
if (res.data.failed.length > 0) {
|
||||
this.setState({ errMessage: res.data.failed[0].error_msg });
|
||||
}
|
||||
if (res.data.success.length > 0) {
|
||||
this.props.onMemberChanged();
|
||||
this.props.toggle();
|
||||
}
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
this.setState({ errMessage });
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.props.toggle}>
|
||||
<ModalHeader toggle={this.props.toggle}>{gettext('Add member')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<UserSelect
|
||||
placeholder={gettext('Search users')}
|
||||
onSelectChange={this.handleSelectChange}
|
||||
isMulti={true}
|
||||
/>
|
||||
{this.state.errMessage && <p className="error mt-2">{this.state.errMessage}</p> }
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.props.toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
152
frontend/src/css/add-user-to-departments.css
Normal file
152
frontend/src/css/add-user-to-departments.css
Normal file
@@ -0,0 +1,152 @@
|
||||
.department-dialog .department-dialog-content {
|
||||
padding: 0;
|
||||
min-height: 30rem;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-wrap: nowrap;
|
||||
align-content: space-between;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.department-dialog .department-dialog-content > div {
|
||||
max-height: calc(100vh - 120px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.department-dialog-content .department-dialog-group {
|
||||
flex: 0 0 35%;
|
||||
padding: 1rem;
|
||||
border-right: 1px solid #eee;
|
||||
}
|
||||
|
||||
.department-dialog-content .department-dialog-group .tr-highlight .dtable-icon-groups {
|
||||
padding-right: 10px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.department-dialog-content .department-dialog-group .dtable-icon-groups {
|
||||
padding-right: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.department-dialog-content .department-dialog-member {
|
||||
display: flex;
|
||||
flex: 0 0 35%;
|
||||
border-right: 1px solid #eee;
|
||||
}
|
||||
|
||||
.department-dialog-content .department-dialog-member-selected {
|
||||
display: flex;
|
||||
flex: 0 0 65%;
|
||||
border-right: 1px solid #eee;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.department-dialog-content .department-dialog-member-selected .modal-footer {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.department-dialog-content .department-dialog-member-selected .dtable-icon-cancel {
|
||||
cursor: pointer;
|
||||
color: #959595;
|
||||
}
|
||||
|
||||
.department-dialog-content .department-dialog-group .group-item {
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.department-dialog-content .department-dialog-group .group-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.department-dialog-content .department-dialog-group .group-item.tr-highlight:hover,
|
||||
.department-dialog-content .department-dialog-group .tr-highlight {
|
||||
background-color: #ED7109;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.department-dialog-member-head {
|
||||
display: flex;
|
||||
padding: 0 0 12px 0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.department-dialog-member-head .department-name {
|
||||
font-size: 0.8125rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.department-dialog-member-head .select-all {
|
||||
cursor: pointer;
|
||||
font-size: 0.8125rem;
|
||||
color: #ED7109;
|
||||
}
|
||||
|
||||
.department-dialog-member-head .select-all-disable {
|
||||
font-size: 0.8125rem;
|
||||
color: rgb(248, 205, 160);
|
||||
}
|
||||
|
||||
.department-dialog-member-table td,
|
||||
.department-dialog-member-head td {
|
||||
border: none;
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.department-dialog-member-table {
|
||||
display: block;
|
||||
text-align: center;
|
||||
max-height: calc(100% - 32px);
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.department-dialog-member-table tr {
|
||||
display: table;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.department-dialog-content .avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.department-dialog-content tr td:first-child {
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.department-dialog-member-table tr td:first-child {
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.department-dialog-member-table tr td .dtable-icon-use-help {
|
||||
color: #bdbdbd;
|
||||
}
|
||||
|
||||
.department-dialog-member-table tr td .dtable-icon-use-help:hover {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.tooltip-inner {
|
||||
font-size: 13px;
|
||||
font-weight: lighter;
|
||||
text-align: justify;
|
||||
color: #fff;
|
||||
background-color: #303133;
|
||||
}
|
||||
|
||||
.department-dialog-member-selected tr td:last-child {
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.department-dialog-member-selected .modal-footer .btn {
|
||||
min-width: 80px;
|
||||
}
|
244
frontend/src/css/admin-common.css
Normal file
244
frontend/src/css/admin-common.css
Normal file
@@ -0,0 +1,244 @@
|
||||
.org-admin .info-item-heading,
|
||||
.sys-admin .info-item-heading {
|
||||
font-size: 15px;
|
||||
font-weight: normal;
|
||||
padding-bottom: 0.2em;
|
||||
border-bottom: 1px solid #eee;
|
||||
margin: 24px 0 0.7em;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.org-admin .side-nav-con svg.multicolor-icon,
|
||||
.sys-admin .side-nav-con svg.multicolor-icon {
|
||||
font-size: 20px;
|
||||
width: 2rem;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.org-admin .side-nav-con .active [class^='multicolor-icon'],
|
||||
.sys-admin .side-nav-con .active [class^='multicolor-icon'] {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.org-admin .side-nav-title,
|
||||
.sys-admin .side-nav-title {
|
||||
color: #666666;
|
||||
padding: 0 4px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.org-admin .main-panel .heading,
|
||||
.sys-admin .main-panel .heading {
|
||||
height: 40px;
|
||||
padding: 0 16px;
|
||||
background: #f9f9f9;
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
line-height: 40px;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.org-admin .main-panel .nav .nav-item .nav-link,
|
||||
.sys-admin .main-panel .nav .nav-item .nav-link {
|
||||
font-size: 16px;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.org-admin .main-panel .nav .nav-item .nav-link.active,
|
||||
.sys-admin .main-panel .nav .nav-item .nav-link.active {
|
||||
color: #ED7109;
|
||||
text-decoration: none;
|
||||
border-bottom: 2px solid #ED7109;
|
||||
}
|
||||
|
||||
.org-admin .main-panel .cur-view-path:after,
|
||||
.sys-admin .main-panel .cur-view-path:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.org-admin .text-secondary,
|
||||
.sys-admin .text-secondary {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
.org-admin .side-nav-con .nav .nav-item,
|
||||
.sys-admin .side-nav-con .nav .nav-item {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.org-admin .side-nav-con .nav .nav-item .nav-link,
|
||||
.sys-admin .side-nav-con .nav .nav-item .nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.org-table-icon {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.org-info-content {
|
||||
padding: 0rem 1rem;
|
||||
background-color: #f5f5f5;
|
||||
flex: 1 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.org-info-content .info-header-content {
|
||||
height: 106px;
|
||||
}
|
||||
|
||||
.info-header-content .info-header-id,
|
||||
.info-header-content .info-header-name {
|
||||
width: calc(50% - 5px);
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #e4e4e4;
|
||||
}
|
||||
|
||||
.info-header-content .info-header-id img,
|
||||
.info-header-content .info-header-name img {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
margin: 30px 0 0 30px;
|
||||
}
|
||||
|
||||
.info-header-content .id-content,
|
||||
.info-header-content .name-content {
|
||||
width: 70%;
|
||||
height: 50%;
|
||||
margin: 28px 0 0 15px;
|
||||
}
|
||||
|
||||
.info-header-content .id-content p,
|
||||
.info-header-content .name-content p {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-header-content .id-content span,
|
||||
.info-header-content .name-content span {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.org-info-content .info-user-content {
|
||||
height: 180px;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #e4e4e4;
|
||||
}
|
||||
|
||||
.info-user-content .user-content-detail {
|
||||
flex-direction: column;
|
||||
width: calc(100% / 3);
|
||||
}
|
||||
|
||||
.info-user-content .user-content-detail p:first-child {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-top: 45px;
|
||||
}
|
||||
|
||||
.info-user-content .user-content-detail p:last-child {
|
||||
font-size: 36px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.org-info-content .used-storage-content {
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
.used-storage-content .used-space {
|
||||
width: calc(50% - 5px);
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #e4e4e4;
|
||||
}
|
||||
|
||||
.used-storage-content .used-space p:first-child {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin: 30px 0 0 30px;
|
||||
}
|
||||
|
||||
.used-storage-content .used-space p:nth-child(2) {
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
margin: 5px 0 0 30px;
|
||||
}
|
||||
|
||||
.used-storage-content .used-space span {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: #aaa;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
/* mobile */
|
||||
@media (max-width: 767px) {
|
||||
.org-info-content .info-header-content {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.info-header-content .info-header-name,
|
||||
.info-header-content .info-header-id {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: inline-flex;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.info-header-content .info-header-id {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.info-header-content .info-header-id img,
|
||||
.info-header-content .info-header-name img {
|
||||
margin: 30px 0 30px 30px;
|
||||
}
|
||||
|
||||
.org-info-content .used-storage-content {
|
||||
height: auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.used-storage-content .used-space {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.used-storage-content .used-space span {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Process bar style */
|
||||
.org-info-content .am-progress-outer {
|
||||
height: 6px !important;
|
||||
background: #eee;
|
||||
margin-left: 30px;
|
||||
width: 86%;
|
||||
border-radius: 10px;
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.org-info-content .am-progress-bar {
|
||||
height: 6px !important;
|
||||
border-radius: 5px;
|
||||
background-color: #ED7109;
|
||||
}
|
||||
|
||||
.dtable-icon-use-help {
|
||||
color: #bdbdbd;
|
||||
}
|
||||
|
||||
.dtable-icon-use-help:hover {
|
||||
color: #888;
|
||||
}
|
131
frontend/src/css/system-departments-v2.css
Normal file
131
frontend/src/css/system-departments-v2.css
Normal file
@@ -0,0 +1,131 @@
|
||||
.cur-view-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.departments-tree-panel {
|
||||
width: 25%;
|
||||
padding: 8px;
|
||||
border-right: 1px solid #eee;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.departments-tree-panel .departments-v2-hight-light {
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
background-color: #ED7109 !important;
|
||||
}
|
||||
|
||||
.departments-tree-panel .departments-v2-hight-light i {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.top-department-button-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.departments-v2-tree-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.departments-v2-tree-item:hover {
|
||||
background-color: #ffefb2;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.departments-v2-tree-item .departments-v2-tree-icon {
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
color: #b0b0b0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.departments-v2-tree-item .departments-v2-tree-node-text {
|
||||
flex: 1;
|
||||
padding-right: 4px;
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.departments-v2-tree-item .department-dropdown-menu {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #b0b0b0;
|
||||
}
|
||||
|
||||
.departments-v2-tree-item .department-action-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.departments-v2-tree-item .department-action-icon:hover {
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.departments-tree-panel .department-children {
|
||||
padding-left: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.department-content-main {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.department-content-main:hover {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.department-content-main table td {
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.department-content-main .department-content-main-name {
|
||||
height: 40px;
|
||||
padding: 0 16px;
|
||||
background: #f9f9f9;
|
||||
font-size: 1rem;
|
||||
line-height: 40px;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.department-content-main .create-group-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.department-content-main .cur-view-content .table {
|
||||
margin-bottom: 6rem;
|
||||
}
|
||||
|
||||
.department-content-main .cur-view-content .sort-dirent {
|
||||
transform: scale(0.8);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.department-content-main table .dtable-icon-check-mark {
|
||||
color: #21bc2e;
|
||||
}
|
||||
|
||||
/* 顶部的样式:背景色和边界去掉,和 seatable 保持一致 */
|
@@ -44,6 +44,7 @@ import OrgSubscription from './org-subscription';
|
||||
|
||||
import '../../css/layout.css';
|
||||
import '../../css/toolbar.css';
|
||||
import '../../css/admin-common.css';
|
||||
|
||||
class Org extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -94,7 +95,7 @@ class Org extends React.Component {
|
||||
render() {
|
||||
let { isSidePanelClosed, currentTab } = this.state;
|
||||
return (
|
||||
<div id="main">
|
||||
<div id="main" className="org-admin">
|
||||
<SidePanel isSidePanelClosed={isSidePanelClosed} onCloseSidePanel={this.onCloseSidePanel} currentTab={currentTab} tabItemClick={this.tabItemClick}/>
|
||||
<div className="main-panel">
|
||||
<Router className="reach-router">
|
||||
|
@@ -0,0 +1,38 @@
|
||||
class DepartmentNode {
|
||||
|
||||
constructor(props) {
|
||||
this.id = props.id || '';
|
||||
this.name = props.name || '';
|
||||
this.children = props.children || [];
|
||||
this.parentNode = props.parentNode || null;
|
||||
this.orgId = props.orgId || '';
|
||||
}
|
||||
|
||||
findNodeById(nodeId) {
|
||||
if (this.id === nodeId) return this;
|
||||
const s = [...this.children];
|
||||
while (s.length > 0) {
|
||||
const node = s.shift();
|
||||
if (node.id === nodeId) return node;
|
||||
s.push(...node.children);
|
||||
}
|
||||
}
|
||||
|
||||
addChildren(nodes) {
|
||||
this.children.push(...nodes);
|
||||
}
|
||||
|
||||
setChildren(nodes) {
|
||||
this.children = nodes;
|
||||
}
|
||||
|
||||
hasChildren() {
|
||||
return this.children.length > 0;
|
||||
}
|
||||
|
||||
deleteChildById(nodeId) {
|
||||
this.children = this.children.filter(nodeItem => nodeItem.id !== nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
export default DepartmentNode;
|
@@ -0,0 +1,124 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from '@gatsbyjs/reach-router';
|
||||
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||
import RoleSelector from '../../../components/single-selector';
|
||||
import { gettext, siteRoot } from '../../../utils/constants';
|
||||
import { getRoleOptions } from './role-status-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 = {
|
||||
dropdownOpenEmail: '',
|
||||
isShowDropdownMenu: false,
|
||||
isItemMenuShow: false,
|
||||
};
|
||||
this.roles = ['Admin', 'Member'];
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
translateRole = (role) => {
|
||||
if (role === 'Admin') {
|
||||
return gettext('Admin');
|
||||
} else if (role === 'Member') {
|
||||
return gettext('Default member');
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { member, freezeItem, unfreezeItem } = this.props;
|
||||
const { isShowDropdownMenu, isItemMenuShow } = this.state;
|
||||
const currentRole = member.role;
|
||||
const options = getRoleOptions(this.roles) || [];
|
||||
const option = options.find(item => item.value === currentRole) || {};
|
||||
|
||||
return (
|
||||
<tr key={member.email} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<td><img className="avatar" src={member.avatar_url} alt=""></img></td>
|
||||
<td className='text-truncate'>
|
||||
<Link to={`${siteRoot}sys/users/${encodeURIComponent(member.email)}/`}>{member.name}</Link>
|
||||
</td>
|
||||
<td>
|
||||
<RoleSelector
|
||||
isDropdownToggleShown={isShowDropdownMenu}
|
||||
currentSelectedOption={option}
|
||||
options={options}
|
||||
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'
|
||||
title={gettext('More operations')}
|
||||
aria-label={gettext('More operations')}
|
||||
data-toggle="dropdown"
|
||||
/>
|
||||
<DropdownMenu className="dtable-dropdown-menu dropdown-menu mt-2 mr-2" right={true}>
|
||||
<DropdownItem key='delete' onClick={this.deleteMember}>{gettext('Delete')}</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DepartmentsV2MembersItem.propTypes = propTypes;
|
||||
|
||||
export default DepartmentsV2MembersItem;
|
@@ -0,0 +1,224 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Table } 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 RepoItem from '../departments/repo-item';
|
||||
import ModalPortal from '../../../components/modal-portal';
|
||||
import DeleteRepoDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-repo-dialog';
|
||||
|
||||
const propTypes = {
|
||||
rootNodes: PropTypes.array,
|
||||
checkedDepartmentId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
membersList: PropTypes.array,
|
||||
isMembersListLoading: PropTypes.bool,
|
||||
setMemberStaff: PropTypes.func,
|
||||
sortItems: PropTypes.func,
|
||||
sortOrder: PropTypes.string,
|
||||
sortBy: PropTypes.string,
|
||||
deleteMember: PropTypes.func,
|
||||
getRepos: PropTypes.func,
|
||||
};
|
||||
|
||||
class DepartmentsV2MembersList extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isItemFreezed: false,
|
||||
activeNav: 'members',
|
||||
repos: [],
|
||||
deletedRepo: {},
|
||||
showDeleteRepoDialog: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getRepos(this.props.checkedDepartmentId);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
if (this.props.checkedDepartmentId !== nextProps.checkedDepartmentId || this.props.isAddNewRepo !== nextProps.isAddNewRepo) {
|
||||
this.getRepos(nextProps.checkedDepartmentId);
|
||||
}
|
||||
}
|
||||
|
||||
showDeleteRepoDialog = (repo) => {
|
||||
this.setState({
|
||||
showDeleteRepoDialog: true,
|
||||
deletedRepo: repo,
|
||||
});
|
||||
};
|
||||
|
||||
toggleCancel = () => {
|
||||
this.setState({
|
||||
showDeleteRepoDialog: false,
|
||||
deletedRepo: {},
|
||||
});
|
||||
};
|
||||
|
||||
onRepoChanged = () => {
|
||||
this.getRepos(this.props.checkedDepartmentId);
|
||||
};
|
||||
|
||||
freezeItem = () => {
|
||||
this.setState({ isItemFreezed: true });
|
||||
};
|
||||
|
||||
unfreezeItem = () => {
|
||||
this.setState({ isItemFreezed: false });
|
||||
};
|
||||
|
||||
toggleItemFreezed = () => {
|
||||
this.setState({ isItemFreezed: !this.state.isItemFreezed });
|
||||
};
|
||||
|
||||
getDepartmentName = () => {
|
||||
const { rootNodes, checkedDepartmentId } = this.props;
|
||||
if (!rootNodes) return '';
|
||||
let name = '';
|
||||
let arr = [...rootNodes];
|
||||
while (!name && arr.length > 0) {
|
||||
let curr = arr.shift();
|
||||
if (curr.id === checkedDepartmentId) {
|
||||
name = curr.name;
|
||||
} else if (curr.children && curr.children.length > 0) {
|
||||
arr.push(...curr.children);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
sortByName = (e) => {
|
||||
e.preventDefault();
|
||||
const sortBy = 'name';
|
||||
let { sortOrder } = this.props;
|
||||
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
|
||||
this.props.sortItems(sortBy, sortOrder);
|
||||
};
|
||||
|
||||
sortByRole = (e) => {
|
||||
e.preventDefault();
|
||||
const sortBy = 'role';
|
||||
let { sortOrder } = this.props;
|
||||
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
|
||||
this.props.sortItems(sortBy, sortOrder);
|
||||
};
|
||||
|
||||
changeActiveNav = (activeNav) => {
|
||||
this.setState({ activeNav });
|
||||
};
|
||||
|
||||
getRepos = (id) => {
|
||||
this.props.getRepos(id, (repos) => {
|
||||
this.setState({ repos });
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { activeNav, repos } = this.state;
|
||||
const { membersList, isMembersListLoading, sortBy, sortOrder } = this.props;
|
||||
const sortByName = sortBy === 'name';
|
||||
const sortByRole = sortBy === 'role';
|
||||
const sortIcon = <span className={`sort-dirent sf3-font sf3-font-down ${sortOrder === 'asc' ? 'rotate-180' : ''}`}></span>;
|
||||
|
||||
return (
|
||||
<div className="department-content-main">
|
||||
<div className="department-content-main-name">{this.getDepartmentName()}</div>
|
||||
|
||||
<div className="cur-view-path tab-nav-container">
|
||||
<ul className="nav">
|
||||
<li className="nav-item">
|
||||
<span className={`nav-link ${activeNav === 'members' ? 'active' : ''}`} onClick={() => this.changeActiveNav('members')}>{gettext('Members')}</span>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<span className={`nav-link ${activeNav === 'repos' ? 'active' : ''}`} onClick={() => this.changeActiveNav('repos')}>{gettext('Libraries')}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{activeNav === 'members' &&
|
||||
<>
|
||||
{isMembersListLoading && <Loading />}
|
||||
{!isMembersListLoading && membersList.length > 0 &&
|
||||
<div className='cur-view-content'>
|
||||
<Table hover>
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
<th width="60px"></th>
|
||||
<th width="25%" onClick={this.sortByName}>{gettext('Name')}{' '}{sortByName && sortIcon}</th>
|
||||
<th width="23%" onClick={this.sortByRole}>{gettext('Role')}{' '}{sortByRole && sortIcon}</th>
|
||||
<th width="35%">{gettext('Contact email')}</th>
|
||||
<th width="calc(17% - 60px)">{/* Operations */}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{membersList.map((item, index) => {
|
||||
return (
|
||||
<DepartmentsV2MembersItem
|
||||
key={index}
|
||||
member={item}
|
||||
deleteMember={this.props.deleteMember}
|
||||
setMemberStaff={this.props.setMemberStaff}
|
||||
unfreezeItem={this.unfreezeItem}
|
||||
freezeItem={this.freezeItem}
|
||||
toggleItemFreezed={this.toggleItemFreezed}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
}
|
||||
{!isMembersListLoading && membersList.length === 0 &&
|
||||
<EmptyTip text={gettext('No members')} />
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
{(activeNav === 'repos' && repos.length > 0) &&
|
||||
<div className="cur-view-content">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%"></th>
|
||||
<th width="50%">{gettext('Name')}</th>
|
||||
<th width="30%">{gettext('Size')}</th>
|
||||
<th width="15%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{repos.map((repo, index) => {
|
||||
return (
|
||||
<RepoItem key={index} repo={repo} showDeleteRepoDialog={this.showDeleteRepoDialog} />
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
{(activeNav === 'repos' && repos.length === 0) &&
|
||||
<EmptyTip text={gettext('No libraries')} />
|
||||
}
|
||||
{this.state.showDeleteRepoDialog && (
|
||||
<ModalPortal>
|
||||
<DeleteRepoDialog
|
||||
toggle={this.toggleCancel}
|
||||
onRepoChanged={this.onRepoChanged}
|
||||
repo={this.state.deletedRepo}
|
||||
groupID={this.props.checkedDepartmentId}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DepartmentsV2MembersList.propTypes = propTypes;
|
||||
|
||||
export default DepartmentsV2MembersList;
|
@@ -0,0 +1,226 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
|
||||
const departmentsV2TreeNodePropTypes = {
|
||||
node: PropTypes.object,
|
||||
checkedDepartmentId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
listSubDepartments: PropTypes.func,
|
||||
onChangeDepartment: PropTypes.func,
|
||||
toggleAddDepartment: PropTypes.func,
|
||||
toggleAddLibrary: PropTypes.func,
|
||||
toggleAddMembers: PropTypes.func,
|
||||
toggleRename: PropTypes.func,
|
||||
toggleDelete: PropTypes.func
|
||||
};
|
||||
|
||||
class DepartmentsV2TreeNode extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isShowTreeIcon: true,
|
||||
isChildrenShow: false,
|
||||
dropdownOpen: false,
|
||||
active: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { node } = this.props;
|
||||
if (node.id === -1) {
|
||||
this.listSubDepartments();
|
||||
}
|
||||
}
|
||||
|
||||
toggleChildren = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (this.state.isChildrenShow) {
|
||||
this.setState({ isChildrenShow: false });
|
||||
return;
|
||||
}
|
||||
this.listSubDepartments();
|
||||
};
|
||||
|
||||
listSubDepartments = () => {
|
||||
const { node } = this.props;
|
||||
this.props.listSubDepartments(node.id, (childrenNodes) => {
|
||||
if (Array.isArray(childrenNodes) && childrenNodes.length === 0) {
|
||||
this.setState({ isShowTreeIcon: false });
|
||||
}
|
||||
this.setState({ isChildrenShow: true });
|
||||
});
|
||||
};
|
||||
|
||||
dropdownToggle = (e) => {
|
||||
e.stopPropagation();
|
||||
this.setState({ dropdownOpen: !this.state.dropdownOpen });
|
||||
};
|
||||
|
||||
onMouseEnter = () => {
|
||||
this.setState({ active: true });
|
||||
};
|
||||
|
||||
onMouseLeave = () => {
|
||||
if (this.state.dropdownOpen) return;
|
||||
this.setState({ active: false });
|
||||
};
|
||||
|
||||
renderTreeNodes = (nodes) => {
|
||||
if (nodes.length > 0) {
|
||||
return nodes.map((node) => {
|
||||
return (
|
||||
<DepartmentsV2TreeNode
|
||||
key={node.id}
|
||||
node={node}
|
||||
onChangeDepartment={this.props.onChangeDepartment}
|
||||
checkedDepartmentId={this.props.checkedDepartmentId}
|
||||
listSubDepartments={this.props.listSubDepartments}
|
||||
toggleAddDepartment={this.props.toggleAddDepartment}
|
||||
toggleAddMembers={this.props.toggleAddMembers}
|
||||
toggleRename={this.props.toggleRename}
|
||||
toggleDelete={this.props.toggleDelete}
|
||||
toggleAddLibrary={this.props.toggleAddLibrary}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
changeDept = (nodeId) => {
|
||||
const { node, checkedDepartmentId } = this.props;
|
||||
const { isChildrenShow } = this.state;
|
||||
if (checkedDepartmentId !== node.id) {
|
||||
this.props.onChangeDepartment(nodeId);
|
||||
}
|
||||
if (checkedDepartmentId === node.id) {
|
||||
if (isChildrenShow) {
|
||||
this.setState({ isChildrenShow: false });
|
||||
return;
|
||||
}
|
||||
this.listSubDepartments();
|
||||
}
|
||||
};
|
||||
|
||||
toggleAddDepartment = (node) => {
|
||||
this.props.toggleAddDepartment(node);
|
||||
};
|
||||
|
||||
toggleAddMembers = (node) => {
|
||||
this.props.toggleAddMembers(node);
|
||||
};
|
||||
|
||||
toggleRename = (node) => {
|
||||
this.props.toggleRename(node);
|
||||
};
|
||||
|
||||
toggleDelete = (node) => {
|
||||
this.props.toggleDelete(node);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { node, checkedDepartmentId } = this.props;
|
||||
const { isChildrenShow, dropdownOpen, active } = this.state;
|
||||
let nodeInnerClass = classNames({
|
||||
'departments-v2-tree-item': true,
|
||||
'departments-v2-hight-light': checkedDepartmentId === node.id
|
||||
});
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
className={nodeInnerClass}
|
||||
onClick={() => this.changeDept(node.id)}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
>
|
||||
{this.state.isShowTreeIcon ?
|
||||
<span className="departments-v2-tree-icon" onClick={(e) => this.toggleChildren(e)}>
|
||||
<i className={`sf3-font sf3-font-down ${isChildrenShow ? '' : 'rotate-270'}`}></i>
|
||||
</span>
|
||||
:
|
||||
<span style={{ width: 24 }}></span>
|
||||
}
|
||||
<span className="departments-v2-tree-node-text text-truncate">{node.name}</span>
|
||||
{active && node.id !== 'other_users' &&
|
||||
<Dropdown
|
||||
isOpen={dropdownOpen}
|
||||
toggle={(e) => this.dropdownToggle(e)}
|
||||
direction="down"
|
||||
className="department-dropdown-menu"
|
||||
>
|
||||
<DropdownToggle
|
||||
tag='span'
|
||||
role="button"
|
||||
className='department-action-icon'
|
||||
title={gettext('More operations')}
|
||||
aria-label={gettext('More operations')}
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<i className="sf3-font sf3-font-more"></i>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu
|
||||
className="dtable-dropdown-menu dropdown-menu drop-list"
|
||||
right={true}
|
||||
modifiers={{ preventOverflow: { boundariesElement: document.body } }}
|
||||
positionFixed={true}
|
||||
>
|
||||
<DropdownItem
|
||||
key={`${node.id}-add-department`}
|
||||
onClick={this.toggleAddDepartment.bind(this, node)}
|
||||
>
|
||||
{gettext('Add sub-department')}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key={`${node.id}-add-repo`}
|
||||
onClick={this.props.toggleAddLibrary.bind(this, node)}
|
||||
>
|
||||
{gettext('Add Library')}
|
||||
</DropdownItem>
|
||||
{node.id !== -1 && (
|
||||
<Fragment>
|
||||
<DropdownItem
|
||||
key={`${node.id}-add-members`}
|
||||
onClick={this.toggleAddMembers.bind(this, node)}
|
||||
>
|
||||
{gettext('Add members')}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key={`${node.id}-rename`}
|
||||
onClick={this.toggleRename.bind(this, node)}
|
||||
>
|
||||
{gettext('Rename')}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key={`${node.id}-delete`}
|
||||
onClick={this.toggleDelete.bind(this, node)}
|
||||
>
|
||||
{gettext('Delete')}
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key={`${node.id}-id`}
|
||||
disabled={true}
|
||||
>
|
||||
{`${gettext('Department ID')} : ${node.id}`}
|
||||
</DropdownItem>
|
||||
</Fragment>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
</div>
|
||||
{this.state.isChildrenShow &&
|
||||
<div className="department-children">
|
||||
{node.children && this.renderTreeNodes(node.children)}
|
||||
</div>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DepartmentsV2TreeNode.propTypes = departmentsV2TreeNodePropTypes;
|
||||
|
||||
export default DepartmentsV2TreeNode;
|
@@ -0,0 +1,45 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import DepartmentsV2TreeNode from './departments-v2-tree-node';
|
||||
|
||||
const DepartmentV2TreePanelPropTypes = {
|
||||
rootNodes: PropTypes.array,
|
||||
checkedDepartmentId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
onChangeDepartment: PropTypes.func,
|
||||
listSubDepartments: PropTypes.func,
|
||||
toggleAddDepartment: PropTypes.func,
|
||||
toggleAddLibrary: PropTypes.func,
|
||||
toggleAddMembers: PropTypes.func,
|
||||
toggleRename: PropTypes.func,
|
||||
toggleDelete: PropTypes.func
|
||||
};
|
||||
|
||||
class DepartmentV2TreePanel extends Component {
|
||||
render() {
|
||||
const { rootNodes, checkedDepartmentId } = this.props;
|
||||
return (
|
||||
<div className="departments-tree-panel">
|
||||
{rootNodes.map(rootNode => {
|
||||
return (
|
||||
<DepartmentsV2TreeNode
|
||||
key={rootNode.id}
|
||||
node={rootNode}
|
||||
checkedDepartmentId={checkedDepartmentId}
|
||||
onChangeDepartment={this.props.onChangeDepartment}
|
||||
listSubDepartments={this.props.listSubDepartments}
|
||||
toggleAddDepartment={this.props.toggleAddDepartment}
|
||||
toggleAddLibrary={this.props.toggleAddLibrary}
|
||||
toggleAddMembers={this.props.toggleAddMembers}
|
||||
toggleRename={this.props.toggleRename}
|
||||
toggleDelete={this.props.toggleDelete}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DepartmentV2TreePanel.propTypes = DepartmentV2TreePanelPropTypes;
|
||||
|
||||
export default DepartmentV2TreePanel;
|
394
frontend/src/pages/sys-admin/departments-v2/departments-v2.js
Normal file
394
frontend/src/pages/sys-admin/departments-v2/departments-v2.js
Normal file
@@ -0,0 +1,394 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Button } from 'reactstrap';
|
||||
import toaster from '../../../components/toast';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import Account from '../../../components/common/account';
|
||||
import DepartmentNode from './department-node';
|
||||
import DepartmentV2TreePanel from './departments-v2-tree-panel';
|
||||
import { systemAdminAPI } from '../../../utils/system-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 AddRepoDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-repo-dialog';
|
||||
import Loading from '../../../components/loading';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
|
||||
import '../../../css/system-departments-v2.css';
|
||||
|
||||
class DepartmentsV2 extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const localSortItems = localStorage.getItem('departments-members-sort-items') || {};
|
||||
this.state = {
|
||||
rootNodes: [],
|
||||
checkedDepartmentId: -1,
|
||||
operateNode: null,
|
||||
isAddDepartmentDialogShow: false,
|
||||
isAddMembersDialogShow: false,
|
||||
isRenameDepartmentDialogShow: false,
|
||||
isDeleteDepartmentDialogShow: false,
|
||||
isShowAddRepoDialog: false,
|
||||
membersList: [],
|
||||
isTopDepartmentLoading: false,
|
||||
isMembersListLoading: false,
|
||||
sortBy: localSortItems.sort_by || 'name', // 'name' or 'role'
|
||||
sortOrder: localSortItems.sort_order || 'asc', // 'asc' or 'desc',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({ isTopDepartmentLoading: true }, () => {
|
||||
systemAdminAPI.sysAdminListAllDepartments().then(res => {
|
||||
const department_list = res.data.data;
|
||||
if (department_list && department_list.length > 0) {
|
||||
const rootNodes = department_list.map(item => {
|
||||
const node = new DepartmentNode({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
orgId: item.org_id,
|
||||
});
|
||||
return node;
|
||||
});
|
||||
this.setState({
|
||||
rootNodes,
|
||||
checkedDepartmentId: rootNodes[0].id,
|
||||
});
|
||||
this.loadDepartmentMembers(rootNodes[0].id);
|
||||
}
|
||||
this.setState({ isTopDepartmentLoading: false });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onChangeDepartment = (nodeId) => {
|
||||
this.setState({ checkedDepartmentId: nodeId }, this.loadDepartmentMembers(nodeId));
|
||||
};
|
||||
|
||||
loadDepartmentMembers = (nodeId) => {
|
||||
if (nodeId === -1) {
|
||||
this.setState({
|
||||
isMembersListLoading: false,
|
||||
membersList: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState({ isMembersListLoading: true });
|
||||
systemAdminAPI.sysAdminListGroupMembers(nodeId, 0, 100).then(res => {
|
||||
this.setState({
|
||||
membersList: res.data.members,
|
||||
isMembersListLoading: false
|
||||
});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
listSubDepartments = (nodeId, cb) => {
|
||||
const { rootNodes } = this.state;
|
||||
let node = null;
|
||||
rootNodes.forEach(rootNode => {
|
||||
if (!node) {
|
||||
node = rootNode.findNodeById(nodeId);
|
||||
}
|
||||
});
|
||||
if (!node) return;
|
||||
systemAdminAPI.sysAdminGetDepartmentInfo(nodeId).then(res => {
|
||||
const childrenNodes = res.data.groups.map(department => new DepartmentNode({
|
||||
id: department.id,
|
||||
name: department.name,
|
||||
parentGroupId: department.parent_group_id,
|
||||
orgId: department.org_id,
|
||||
parentNode: node,
|
||||
}));
|
||||
node.setChildren(childrenNodes);
|
||||
cb && cb(childrenNodes);
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
getRepos = (nodeId, cb) => {
|
||||
systemAdminAPI.sysAdminListGroupRepos(nodeId).then(res => {
|
||||
cb && cb(res.data.libraries);
|
||||
}).catch(error => {
|
||||
if (error.response && error.response.status === 404) {
|
||||
cb && cb(null);
|
||||
return;
|
||||
}
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
toggleAddLibrary = (node) => {
|
||||
this.setState({
|
||||
operateNode: node,
|
||||
isShowAddRepoDialog: !this.state.isShowAddRepoDialog
|
||||
});
|
||||
};
|
||||
|
||||
toggleAddDepartment = (node) => {
|
||||
this.setState({ operateNode: node, isAddDepartmentDialogShow: !this.state.isAddDepartmentDialogShow });
|
||||
};
|
||||
|
||||
toggleAddMembers = (node) => {
|
||||
this.setState({ operateNode: node, isAddMembersDialogShow: !this.state.isAddMembersDialogShow });
|
||||
};
|
||||
|
||||
toggleRename = (node) => {
|
||||
this.setState({ operateNode: node, isRenameDepartmentDialogShow: !this.state.isRenameDepartmentDialogShow });
|
||||
};
|
||||
|
||||
toggleDelete = (node) => {
|
||||
this.setState({ operateNode: node, isDeleteDepartmentDialogShow: !this.state.isDeleteDepartmentDialogShow });
|
||||
};
|
||||
|
||||
addDepartment = (parentNode, department) => {
|
||||
parentNode.addChildren([new DepartmentNode({
|
||||
id: department.id,
|
||||
name: department.name,
|
||||
parentNode: parentNode,
|
||||
orgId: department.org_id,
|
||||
})]);
|
||||
};
|
||||
|
||||
setRootNode = (department) => {
|
||||
const newRootNode = new DepartmentNode({
|
||||
id: department.id,
|
||||
name: department.name,
|
||||
orgId: department.org_id,
|
||||
});
|
||||
const rootNodes = this.state.rootNodes.slice(0);
|
||||
rootNodes.push(newRootNode);
|
||||
this.setState({
|
||||
rootNodes: rootNodes,
|
||||
checkedDepartmentId: newRootNode.id,
|
||||
});
|
||||
this.loadDepartmentMembers(newRootNode.id);
|
||||
};
|
||||
|
||||
renameDepartment = (node, department) => {
|
||||
node.id = department.id;
|
||||
node.name = department.name;
|
||||
};
|
||||
|
||||
onDelete = () => {
|
||||
const { operateNode, checkedDepartmentId } = this.state;
|
||||
systemAdminAPI.sysAdminDeleteDepartment(operateNode.id).then(() => {
|
||||
this.toggleDelete();
|
||||
if (operateNode.parentNode) {
|
||||
operateNode.parentNode.deleteChildById(operateNode.id);
|
||||
if (operateNode.id === checkedDepartmentId && operateNode.parentNode.id !== -1) {
|
||||
this.onChangeDepartment(operateNode.parentNode.id);
|
||||
}
|
||||
} else {
|
||||
let rootNodes = this.state.rootNodes.slice(0);
|
||||
let rootIndex = rootNodes.findIndex(node => node.id === operateNode.id);
|
||||
rootNodes.splice(rootIndex, 1);
|
||||
this.setState({
|
||||
rootNodes,
|
||||
checkedDepartmentId: rootNodes[0].id,
|
||||
});
|
||||
this.loadDepartmentMembers(rootNodes[0].id);
|
||||
}
|
||||
}).catch(error => {
|
||||
if (error.response && error.response.status === 400) {
|
||||
toaster.danger(error.response.data.error_msg);
|
||||
return;
|
||||
}
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
onMemberChanged = () => {
|
||||
const { checkedDepartmentId, operateNode } = this.state;
|
||||
if (checkedDepartmentId && operateNode && checkedDepartmentId !== operateNode.id) return;
|
||||
this.loadDepartmentMembers(operateNode.id);
|
||||
};
|
||||
|
||||
setMemberStaff = (email, isAdmin) => {
|
||||
const { checkedDepartmentId, membersList } = this.state;
|
||||
systemAdminAPI.sysAdminUpdateGroupMemberRole(checkedDepartmentId, email, isAdmin).then(res => {
|
||||
const member = res.data;
|
||||
const newMembersList = membersList.map(memberItem => {
|
||||
if (memberItem.email === email) return member;
|
||||
return memberItem;
|
||||
});
|
||||
this.setState({ membersList: newMembersList });
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
deleteMember = (email) => {
|
||||
const { checkedDepartmentId, membersList } = this.state;
|
||||
systemAdminAPI.sysAdminDeleteGroupMember(checkedDepartmentId, email).then(() => {
|
||||
const newMembersList = membersList.filter(memberItem => memberItem.email !== email);
|
||||
this.setState({ membersList: newMembersList });
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
sortMembers = (items, sortBy, sortOrder) => {
|
||||
let comparator;
|
||||
switch (`${sortBy}-${sortOrder}`) {
|
||||
case 'name-asc':
|
||||
comparator = function (a, b) {
|
||||
var result = Utils.compareTwoWord(a.name, b.name);
|
||||
return result;
|
||||
};
|
||||
break;
|
||||
case 'name-desc':
|
||||
comparator = function (a, b) {
|
||||
var result = Utils.compareTwoWord(a.name, b.name);
|
||||
return -result;
|
||||
};
|
||||
break;
|
||||
case 'role-asc':
|
||||
comparator = function (a, b) {
|
||||
return a.is_staff && !b.is_staff ? -1 : 1;
|
||||
};
|
||||
break;
|
||||
case 'role-desc':
|
||||
comparator = function (a, b) {
|
||||
return a.is_staff && !b.is_staff ? 1 : -1;
|
||||
};
|
||||
break;
|
||||
default:
|
||||
comparator = function () {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
items.sort((a, b) => {
|
||||
return comparator(a, b);
|
||||
});
|
||||
return items;
|
||||
};
|
||||
|
||||
sortItems = (sortBy, sortOrder) => {
|
||||
localStorage.setItem('departments-members-sort-items', { sort_by: sortBy, sort_order: sortOrder });
|
||||
this.setState({
|
||||
sortBy: sortBy,
|
||||
sortOrder: sortOrder,
|
||||
membersList: this.sortMembers(this.state.membersList, sortBy, sortOrder),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
render() {
|
||||
const { rootNodes, operateNode, checkedDepartmentId, isAddDepartmentDialogShow, isAddMembersDialogShow,
|
||||
membersList, isMembersListLoading, isTopDepartmentLoading, isRenameDepartmentDialogShow,
|
||||
isDeleteDepartmentDialogShow, sortBy, sortOrder } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
|
||||
<div className="main-panel-north border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
|
||||
<div className="operation">
|
||||
<button className='btn btn-secondary operation-item' title={gettext('New Department')} onClick={() => {this.toggleAddDepartment(null);}}>{gettext('New Department')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="common-toolbar">
|
||||
<Account isAdminPanel={true}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="main-panel-center">
|
||||
<div className="cur-view-container">
|
||||
<h2 className="heading">{gettext('Departments')}</h2>
|
||||
<div className="cur-view-content d-flex flex-row p-0">
|
||||
{isTopDepartmentLoading && <Loading/>}
|
||||
{(!isTopDepartmentLoading && rootNodes.length > 0) &&
|
||||
<>
|
||||
<DepartmentV2TreePanel
|
||||
rootNodes={rootNodes}
|
||||
checkedDepartmentId={checkedDepartmentId}
|
||||
onChangeDepartment={this.onChangeDepartment}
|
||||
listSubDepartments={this.listSubDepartments}
|
||||
toggleAddDepartment={this.toggleAddDepartment}
|
||||
toggleAddLibrary={this.toggleAddLibrary}
|
||||
toggleAddMembers={this.toggleAddMembers}
|
||||
toggleRename={this.toggleRename}
|
||||
toggleDelete={this.toggleDelete}
|
||||
/>
|
||||
<DepartmentsV2MembersList
|
||||
rootNodes={rootNodes}
|
||||
checkedDepartmentId={checkedDepartmentId}
|
||||
membersList={membersList}
|
||||
isMembersListLoading={isMembersListLoading}
|
||||
isAddNewRepo={this.state.isAddNewRepo}
|
||||
setMemberStaff={this.setMemberStaff}
|
||||
deleteMember={this.deleteMember}
|
||||
sortItems={this.sortItems}
|
||||
sortBy={sortBy}
|
||||
sortOrder={sortOrder}
|
||||
getRepos={this.getRepos}
|
||||
deleteGroup={this.deleteGroup}
|
||||
createGroup={this.createGroup}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{(!isTopDepartmentLoading && rootNodes.length === 0) &&
|
||||
<div className="top-department-button-container h-100 w-100">
|
||||
<Button onClick={this.toggleAddDepartment.bind(this, null)}>
|
||||
{gettext('Enable departments feature')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isAddMembersDialogShow &&
|
||||
<AddDepartMemberV2Dialog
|
||||
toggle={this.toggleAddMembers}
|
||||
nodeId={operateNode.id}
|
||||
onMemberChanged={this.onMemberChanged}
|
||||
/>
|
||||
}
|
||||
{isRenameDepartmentDialogShow &&
|
||||
<RenameDepartmentV2Dialog
|
||||
node={operateNode}
|
||||
toggle={this.toggleRename}
|
||||
renameDepartment={this.renameDepartment}
|
||||
/>
|
||||
}
|
||||
{isDeleteDepartmentDialogShow &&
|
||||
<DeleteDepartmentV2ConfirmDialog
|
||||
node={operateNode}
|
||||
toggle={this.toggleDelete}
|
||||
onDelete={this.onDelete}
|
||||
/>
|
||||
}
|
||||
{isAddDepartmentDialogShow &&
|
||||
<AddDepartmentV2Dialog
|
||||
parentNode={operateNode}
|
||||
toggle={this.toggleAddDepartment}
|
||||
addDepartment={this.addDepartment}
|
||||
setRootNode={this.setRootNode}
|
||||
/>
|
||||
}
|
||||
{this.state.isShowAddRepoDialog && (
|
||||
<AddRepoDialog
|
||||
toggle={this.toggleAddLibrary}
|
||||
onAddNewRepo={() => {this.setState({ isAddNewRepo: !this.state.isAddNewRepo });}}
|
||||
groupID={String(operateNode.id)}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default DepartmentsV2;
|
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
|
||||
const getRoleOptions = (roles) => {
|
||||
return Array.isArray(roles) && roles.map(role => ({
|
||||
value: role,
|
||||
text: translateRole(role),
|
||||
label: (
|
||||
<div className="label-container">
|
||||
<span>{translateRole(role)}</span>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
const translateRole = (role) => {
|
||||
switch (role) {
|
||||
case 'Admin':
|
||||
return gettext('Admin');
|
||||
case 'Member':
|
||||
return gettext('Member');
|
||||
case 'default':
|
||||
return gettext('Default');
|
||||
case 'guest':
|
||||
return gettext('Guest');
|
||||
case 'default_admin':
|
||||
return gettext('Default admin');
|
||||
case 'system_admin':
|
||||
return gettext('System admin');
|
||||
case 'daily_admin':
|
||||
return gettext('Daily admin');
|
||||
case 'audit_admin':
|
||||
return gettext('Audit admin');
|
||||
default:
|
||||
return role;
|
||||
}
|
||||
};
|
||||
|
||||
export { getRoleOptions };
|
@@ -45,11 +45,7 @@ import SearchGroups from './groups/search-groups';
|
||||
import GroupRepos from './groups/group-repos';
|
||||
import GroupMembers from './groups/group-members';
|
||||
|
||||
import Departments from './departments/departments';
|
||||
import DepartmentList from './departments/department-list';
|
||||
import SubDepartments from './departments/sub-departments';
|
||||
import DepartmentMembers from './departments/department-members';
|
||||
import DepartmentLibraries from './departments/department-libraries';
|
||||
import DepartmentsV2 from './departments-v2/departments-v2';
|
||||
|
||||
import ShareLinks from './links/share-links';
|
||||
import UploadLinks from './links/upload-links';
|
||||
@@ -89,6 +85,7 @@ import AbuseReports from './abuse-reports';
|
||||
|
||||
import '../../css/layout.css';
|
||||
import '../../css/toolbar.css';
|
||||
import '../../css/admin-common.css';
|
||||
|
||||
class SysAdmin extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -209,7 +206,7 @@ class SysAdmin extends React.Component {
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="main">
|
||||
<div id="main" className="sys-admin">
|
||||
<SidePanel
|
||||
isSidePanelClosed={isSidePanelClosed}
|
||||
onCloseSidePanel={this.onCloseSidePanel}
|
||||
@@ -238,12 +235,7 @@ class SysAdmin extends React.Component {
|
||||
<SearchGroups path={siteRoot + 'sys/search-groups'} {...commonProps} />
|
||||
<GroupRepos path={siteRoot + 'sys/groups/:groupID/libraries'} {...commonProps} />
|
||||
<GroupMembers path={siteRoot + 'sys/groups/:groupID/members'} {...commonProps} />
|
||||
<Departments path={siteRoot + 'sys/departments'}>
|
||||
<DepartmentList path='/' {...commonProps} />
|
||||
<SubDepartments path='/:groupID' {...commonProps} />
|
||||
<DepartmentMembers path='/:groupID/members' {...commonProps} />
|
||||
<DepartmentLibraries path='/:groupID/libraries' {...commonProps} />
|
||||
</Departments>
|
||||
<DepartmentsV2 path={siteRoot + 'sys/departments/'} onCloseSidePanel={this.onCloseSidePanel} />
|
||||
<ShareLinks path={siteRoot + 'sys/share-links'} {...commonProps} />
|
||||
<UploadLinks path={siteRoot + 'sys/upload-links'} {...commonProps} />
|
||||
<Orgs path={siteRoot + 'sys/organizations'} {...commonProps} />
|
||||
|
@@ -1092,6 +1092,7 @@ a.table-sort-op:hover {
|
||||
.dropdown-item {
|
||||
height: 32px;
|
||||
padding: 0.25rem 1rem;
|
||||
line-height: 1.5;
|
||||
cursor: pointer;
|
||||
color: #212529;
|
||||
font-size: 14px;
|
||||
@@ -1490,15 +1491,15 @@ a.table-sort-op:hover {
|
||||
}
|
||||
|
||||
.rotate-90 {
|
||||
transform: rotate(90deg);
|
||||
transform: rotate(90deg) !important;
|
||||
}
|
||||
|
||||
.rotate-180 {
|
||||
transform: rotate(180deg);
|
||||
transform: rotate(180deg) !important;
|
||||
}
|
||||
|
||||
.rotate-270 {
|
||||
transform: rotate(270deg);
|
||||
transform: rotate(270deg) !important;
|
||||
}
|
||||
|
||||
/* All triangle icon use 12px */
|
||||
|
Reference in New Issue
Block a user