1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-09 02:42:47 +00:00

change system admin department UI (#7010)

This commit is contained in:
Michael An
2024-11-15 20:50:16 +08:00
committed by GitHub
parent d4fc55b76d
commit f40ea0540c
17 changed files with 1914 additions and 16 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>
);
}
}

View 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;
}

View 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;
}

View 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 保持一致 */

View File

@@ -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">

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View 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;

View File

@@ -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 };

View File

@@ -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} />

View File

@@ -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 */