mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-06 17:33:18 +00:00
[system admin - departments] fixup & improvements (#7350)
- improved the UX for 'member item' - added paginator for the member list - fixed sorting members by 'role' - fixed member name link - added confirm dialog for 'delete member' - fixed displaying error msgs for adding multiple members - removed 'store the sort way in localStorage' - removed 'v2' from code & file names - cleaned up files. removed files for the previous version, and etc. - fixed 'delete repo' - improved the scroll of the main content area (make the department name bar & the tabs bar fixed) - fixup for mobile
This commit is contained in:
@@ -47,7 +47,7 @@ class DeleteRepoDialog extends React.Component {
|
||||
const propTypes = {
|
||||
repo: PropTypes.object.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
groupID: PropTypes.string,
|
||||
groupID: PropTypes.number,
|
||||
onRepoChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
@@ -100,9 +100,8 @@ class Paginator extends Component {
|
||||
<span className="sf3-font sf3-font-down rotate-270 d-inline-block"></span>
|
||||
</button>
|
||||
|
||||
<Dropdown isOpen={this.state.isMenuShow} toggle={this.toggleOperationMenu} direction="up" className="paginator-dropdown">
|
||||
<Dropdown isOpen={this.state.isMenuShow} toggle={this.toggleOperationMenu} direction="up" className="paginator-dropdown ml-6">
|
||||
<DropdownToggle
|
||||
className="ml-6"
|
||||
data-toggle="dropdown"
|
||||
aria-expanded={this.state.isMenuShow}
|
||||
onClick={this.toggleOperationMenu}
|
||||
|
@@ -84,11 +84,6 @@
|
||||
.department-content-main {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.department-content-main:hover {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.department-content-main table td {
|
||||
@@ -113,10 +108,6 @@
|
||||
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;
|
||||
|
@@ -51,10 +51,6 @@ class Department extends React.Component {
|
||||
this.setState({ isItemFreezed: false });
|
||||
};
|
||||
|
||||
toggleItemFreezed = () => {
|
||||
this.setState({ isItemFreezed: !this.state.isItemFreezed });
|
||||
};
|
||||
|
||||
getCurrentDepartment = () => {
|
||||
const { rootNodes, checkedDepartmentId } = this.props;
|
||||
if (!rootNodes) return {};
|
||||
@@ -116,7 +112,7 @@ class Department extends React.Component {
|
||||
const currentDepartment = this.getCurrentDepartment();
|
||||
|
||||
return (
|
||||
<div className="department-content-main">
|
||||
<div className="department-content-main d-flex flex-column">
|
||||
<div className="department-content-main-name">
|
||||
{currentDepartment.name}
|
||||
<Dropdown
|
||||
@@ -180,7 +176,6 @@ class Department extends React.Component {
|
||||
setMemberStaff={this.props.setMemberStaff}
|
||||
unfreezeItem={this.unfreezeItem}
|
||||
freezeItem={this.freezeItem}
|
||||
toggleItemFreezed={this.toggleItemFreezed}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
/>
|
||||
);
|
||||
|
@@ -13,7 +13,6 @@ const propTypes = {
|
||||
deleteMember: PropTypes.func,
|
||||
unfreezeItem: PropTypes.func,
|
||||
freezeItem: PropTypes.func,
|
||||
toggleItemFreezed: PropTypes.func,
|
||||
};
|
||||
|
||||
class DepartmentsV2MembersItem extends React.Component {
|
||||
@@ -87,7 +86,6 @@ class DepartmentsV2MembersItem extends React.Component {
|
||||
});
|
||||
const currentSelectedOption = this.roleOptions.filter(item => item.isSelected)[0];
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr className={`departments-members-item ${highlighted ? 'tr-highlight' : ''}`} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
|
@@ -1,124 +0,0 @@
|
||||
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 className="departments-members-item" 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-vertical'
|
||||
title={gettext('More operations')}
|
||||
aria-label={gettext('More operations')}
|
||||
data-toggle="dropdown"
|
||||
/>
|
||||
<DropdownMenu right={true}>
|
||||
<DropdownItem key='delete' onClick={this.deleteMember}>{gettext('Delete')}</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DepartmentsV2MembersItem.propTypes = propTypes;
|
||||
|
||||
export default DepartmentsV2MembersItem;
|
@@ -1,257 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Table, Dropdown, DropdownToggle } from 'reactstrap';
|
||||
import Loading from '../../../components/loading';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import DepartmentsV2MembersItem from './departments-v2-members-item';
|
||||
import RepoItem from '../departments/repo-item';
|
||||
import ModalPortal from '../../../components/modal-portal';
|
||||
import DeleteRepoDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-repo-dialog';
|
||||
import DepartmentNodeMenu from './departments-node-dropdown-menu';
|
||||
|
||||
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,
|
||||
dropdownOpen: 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 });
|
||||
};
|
||||
|
||||
getCurrentDepartment = () => {
|
||||
const { rootNodes, checkedDepartmentId } = this.props;
|
||||
if (!rootNodes) return {};
|
||||
let currentDepartment = {};
|
||||
let arr = [...rootNodes];
|
||||
while (!currentDepartment.id && arr.length > 0) {
|
||||
let curr = arr.shift();
|
||||
if (curr.id === checkedDepartmentId) {
|
||||
currentDepartment = curr;
|
||||
} else if (curr.children && curr.children.length > 0) {
|
||||
arr.push(...curr.children);
|
||||
}
|
||||
}
|
||||
return currentDepartment;
|
||||
};
|
||||
|
||||
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 });
|
||||
});
|
||||
};
|
||||
|
||||
dropdownToggle = (e) => {
|
||||
e.stopPropagation();
|
||||
this.setState({ dropdownOpen: !this.state.dropdownOpen });
|
||||
};
|
||||
|
||||
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>;
|
||||
const currentDepartment = this.getCurrentDepartment();
|
||||
|
||||
return (
|
||||
<div className="department-content-main">
|
||||
<div className="department-content-main-name">
|
||||
{currentDepartment.name}
|
||||
<Dropdown
|
||||
isOpen={this.state.dropdownOpen}
|
||||
toggle={(e) => this.dropdownToggle(e)}
|
||||
direction="down"
|
||||
className="department-dropdown-menu"
|
||||
>
|
||||
<DropdownToggle
|
||||
tag='span'
|
||||
role="button"
|
||||
className='sf3-font-down sf3-font ml-1 sf-dropdown-toggle'
|
||||
title={gettext('More operations')}
|
||||
aria-label={gettext('More operations')}
|
||||
data-toggle="dropdown"
|
||||
/>
|
||||
<DepartmentNodeMenu
|
||||
node={currentDepartment}
|
||||
toggleAddDepartment={this.props.toggleAddDepartment}
|
||||
toggleAddLibrary={this.props.toggleAddLibrary}
|
||||
toggleAddMembers={this.props.toggleAddMembers}
|
||||
toggleRename={this.props.toggleRename}
|
||||
toggleDelete={this.props.toggleDelete}
|
||||
/>
|
||||
</Dropdown>
|
||||
</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;
|
@@ -1,406 +0,0 @@
|
||||
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);
|
||||
if (rootNodes.length === 0) {
|
||||
this.setState({
|
||||
rootNodes,
|
||||
checkedDepartmentId: -1
|
||||
});
|
||||
} else {
|
||||
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}
|
||||
toggleAddDepartment={this.toggleAddDepartment}
|
||||
toggleAddLibrary={this.toggleAddLibrary}
|
||||
toggleAddMembers={this.toggleAddMembers}
|
||||
toggleRename={this.toggleRename}
|
||||
toggleDelete={this.toggleDelete}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{(!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;
|
@@ -1,39 +0,0 @@
|
||||
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 };
|
@@ -1,119 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import dayjs from 'dayjs';
|
||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import toaster from '../../../components/toast';
|
||||
import ModalPortal from '../../../components/modal-portal';
|
||||
import DeleteRepoDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-repo-dialog';
|
||||
import { gettext, lang } from '../../../utils/constants';
|
||||
import RepoItem from './repo-item';
|
||||
import Department from './department';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import '../../../css/org-department-item.css';
|
||||
|
||||
dayjs.locale(lang);
|
||||
|
||||
const DepartmentDetailPropTypes = {
|
||||
groupID: PropTypes.string,
|
||||
};
|
||||
|
||||
class DepartmentDetail extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
repos: [],
|
||||
deletedRepo: {},
|
||||
showDeleteRepoDialog: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { groupID } = this.props;
|
||||
this.listGroupRepo(groupID);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
if (this.props.groupID !== nextProps.groupID) {
|
||||
this.listGroupRepo(nextProps.groupID);
|
||||
}
|
||||
}
|
||||
|
||||
listGroupRepo = (groupID) => {
|
||||
systemAdminAPI.sysAdminListGroupRepos(groupID).then(res => {
|
||||
this.setState({ repos: res.data.libraries });
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
toggleCancel = () => {
|
||||
this.setState({
|
||||
showDeleteRepoDialog: false
|
||||
});
|
||||
};
|
||||
|
||||
onRepoChanged = () => {
|
||||
this.listGroupRepo(this.props.groupID);
|
||||
};
|
||||
|
||||
showDeleteRepoDialog = (repo) => {
|
||||
this.setState({ showDeleteRepoDialog: true, deletedRepo: repo });
|
||||
};
|
||||
|
||||
onAddNewRepo = (newRepo) => {
|
||||
const { repos } = this.state;
|
||||
repos.unshift(newRepo);
|
||||
this.setState({ repos });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { repos } = this.state;
|
||||
const { groupID } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Department groupID={groupID} currentItem="repos" onAddNewRepo={this.onAddNewRepo}>
|
||||
{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>
|
||||
: <EmptyTip text={gettext('No libraries')} />
|
||||
}
|
||||
</Department>
|
||||
{this.state.showDeleteRepoDialog && (
|
||||
<ModalPortal>
|
||||
<DeleteRepoDialog
|
||||
toggle={this.toggleCancel}
|
||||
onRepoChanged={this.onRepoChanged}
|
||||
repo={this.state.deletedRepo}
|
||||
groupID={groupID}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DepartmentDetail.propTypes = DepartmentDetailPropTypes;
|
||||
|
||||
export default DepartmentDetail;
|
@@ -1,150 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
import ModalPortal from '../../../components/modal-portal';
|
||||
import AddDepartDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-department-dialog';
|
||||
import { gettext, lang } from '../../../utils/constants';
|
||||
import GroupItem from './group-item';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import '../../../css/org-department-item.css';
|
||||
|
||||
dayjs.locale(lang);
|
||||
|
||||
class DepartmentList extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
groups: null,
|
||||
groupID: '',
|
||||
isShowAddDepartDialog: false,
|
||||
isItemFreezed: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.listDepartments();
|
||||
}
|
||||
|
||||
onFreezedItem = () => {
|
||||
this.setState({ isItemFreezed: true });
|
||||
};
|
||||
|
||||
onUnfreezedItem = () => {
|
||||
this.setState({ isItemFreezed: false });
|
||||
};
|
||||
|
||||
listDepartments = () => {
|
||||
systemAdminAPI.sysAdminListAllDepartments().then(res => {
|
||||
this.setState({ groups: res.data.data });
|
||||
});
|
||||
};
|
||||
|
||||
toggleAddDepartDialog = () => {
|
||||
this.setState({ isShowAddDepartDialog: !this.state.isShowAddDepartDialog });
|
||||
};
|
||||
|
||||
onDepartmentNameChanged = (dept) => {
|
||||
this.setState({
|
||||
groups: this.state.groups.map(item => {
|
||||
if (item.id == dept.id) {
|
||||
item.name = dept.name;
|
||||
}
|
||||
return item;
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
onAddNewDepartment = (newDepartment) => {
|
||||
const { groups } = this.state;
|
||||
groups.unshift(newDepartment);
|
||||
this.setState({
|
||||
groups: groups
|
||||
});
|
||||
};
|
||||
|
||||
onDeleteDepartment = (id) => {
|
||||
const { groups } = this.state;
|
||||
this.setState({
|
||||
groups: groups.filter((item) => item.id != id)
|
||||
});
|
||||
};
|
||||
|
||||
onSetDepartmentQuota = (target) => {
|
||||
const { groups } = this.state;
|
||||
this.setState({
|
||||
groups: groups.map((item) => {
|
||||
if (item.id == target.id) {
|
||||
item.quota = target.quota;
|
||||
}
|
||||
return item;
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const groups = this.state.groups;
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar {...this.props}>
|
||||
<button className='btn btn-secondary operation-item' title={gettext('New Department')} onClick={this.toggleAddDepartDialog}>{gettext('New Department')}
|
||||
</button>
|
||||
{this.state.isShowAddDepartDialog && (
|
||||
<ModalPortal>
|
||||
<AddDepartDialog
|
||||
onAddNewDepartment={this.onAddNewDepartment}
|
||||
groupID={this.state.groupID}
|
||||
toggle={this.toggleAddDepartDialog}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</MainPanelTopbar>
|
||||
<div className="main-panel-center flex-row h-100">
|
||||
<div className="cur-view-container o-auto">
|
||||
<div className="cur-view-path">
|
||||
<div className="fleft">
|
||||
<h3 className="sf-heading">{gettext('Departments')}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className="cur-view-content pb-8">
|
||||
{groups && groups.length > 0 ?
|
||||
<table className='mb-8'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40%">{gettext('Name')}</th>
|
||||
<th width="25%">{gettext('Created At')}</th>
|
||||
<th width="20%">{gettext('Quota')}</th>
|
||||
<th width="15%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{groups.map((group, index) => {
|
||||
return (
|
||||
<Fragment key={group.id}>
|
||||
<GroupItem
|
||||
group={group}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
onFreezedItem={this.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
onDepartmentNameChanged={this.onDepartmentNameChanged}
|
||||
onDeleteDepartment={this.onDeleteDepartment}
|
||||
onSetDepartmentQuota={this.onSetDepartmentQuota}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
:
|
||||
<EmptyTip text={gettext('No departments')}/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DepartmentList;
|
@@ -1,181 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import dayjs from 'dayjs';
|
||||
import Paginator from '../../../components/paginator';
|
||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import ModalPortal from '../../../components/modal-portal';
|
||||
import DeleteMemberDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-member-dialog';
|
||||
import { gettext, lang } from '../../../utils/constants';
|
||||
import MemberItem from './member-item';
|
||||
import Department from './department';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import '../../../css/org-department-item.css';
|
||||
|
||||
dayjs.locale(lang);
|
||||
|
||||
const DepartmentMembersPropTypes = {
|
||||
groupID: PropTypes.string,
|
||||
};
|
||||
|
||||
class DepartmentMembers extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isItemFreezed: false,
|
||||
members: [],
|
||||
membersErrorMsg: '',
|
||||
currentPageInfo: {
|
||||
},
|
||||
currentPage: 1,
|
||||
perPage: 100,
|
||||
deletedMember: {},
|
||||
showDeleteMemberDialog: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let urlParams = (new URL(window.location)).searchParams;
|
||||
const { currentPage, perPage } = this.state;
|
||||
this.setState({
|
||||
perPage: parseInt(urlParams.get('per_page') || perPage),
|
||||
currentPage: parseInt(urlParams.get('page') || currentPage)
|
||||
}, () => {
|
||||
const { groupID } = this.props;
|
||||
this.listMembers(groupID, this.state.currentPage, this.state.perPage);
|
||||
});
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
if (this.props.groupID !== nextProps.groupID) {
|
||||
this.listMembers(nextProps.groupID, this.state.currentPage, this.state.perPage);
|
||||
}
|
||||
}
|
||||
|
||||
listMembers = (groupID, page, perPage) => {
|
||||
systemAdminAPI.sysAdminListGroupMembers(groupID, page, perPage).then((res) => {
|
||||
this.setState({
|
||||
members: res.data.members,
|
||||
currentPageInfo: res.data.page_info
|
||||
});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
this.setState({ membersErrorMsg: errMessage });
|
||||
});
|
||||
};
|
||||
|
||||
getPreviousPageList = () => {
|
||||
this.listMembers(this.props.groupID, this.state.currentPageInfo.current_page - 1, this.state.perPage);
|
||||
};
|
||||
|
||||
getNextPageList = () => {
|
||||
this.listMembers(this.props.groupID, this.state.currentPageInfo.current_page + 1, this.state.perPage);
|
||||
};
|
||||
|
||||
resetPerPage = (perPage) => {
|
||||
this.setState({
|
||||
perPage: perPage
|
||||
}, () => {
|
||||
this.listMembers(this.props.groupID, 1, perPage);
|
||||
});
|
||||
};
|
||||
|
||||
toggleCancel = () => {
|
||||
this.setState({
|
||||
showDeleteMemberDialog: false,
|
||||
});
|
||||
};
|
||||
|
||||
onMemberChanged = () => {
|
||||
this.listMembers(this.props.groupID, this.state.currentPageInfo.current_page, this.state.perPage);
|
||||
};
|
||||
|
||||
toggleItemFreezed = (isFreezed) => {
|
||||
this.setState({ isItemFreezed: isFreezed });
|
||||
};
|
||||
|
||||
showDeleteMemberDialog = (member) => {
|
||||
this.setState({ showDeleteMemberDialog: true, deletedMember: member });
|
||||
};
|
||||
|
||||
onAddNewMembers = (newMembers) => {
|
||||
const { members } = this.state;
|
||||
members.unshift(...newMembers);
|
||||
this.setState({ members });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { members, membersErrorMsg } = this.state;
|
||||
const { groupID } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Department
|
||||
groupID={groupID}
|
||||
currentItem="members"
|
||||
onAddNewMembers={this.onAddNewMembers}
|
||||
>
|
||||
<div className="cur-view-content">
|
||||
{membersErrorMsg ? <p className="error text-center">{membersErrorMsg}</p> :
|
||||
members.length == 0 ?
|
||||
<EmptyTip text={gettext('No members')}/> :
|
||||
<Fragment>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%"></th>
|
||||
<th width="50%">{gettext('Name')}</th>
|
||||
<th width="15%">{gettext('Role')}</th>
|
||||
<th width="30%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{members.map((member, index) => {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<MemberItem
|
||||
member={member}
|
||||
showDeleteMemberDialog={this.showDeleteMemberDialog}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
onMemberChanged={this.onMemberChanged}
|
||||
toggleItemFreezed={this.toggleItemFreezed}
|
||||
groupID={groupID}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{this.state.currentPageInfo &&
|
||||
<Paginator
|
||||
gotoPreviousPage={this.getPreviousPageList}
|
||||
gotoNextPage={this.getNextPageList}
|
||||
currentPage={this.state.currentPageInfo.current_page}
|
||||
hasNextPage={this.state.currentPageInfo.has_next_page}
|
||||
curPerPage={this.state.perPage}
|
||||
resetPerPage={this.resetPerPage}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
</Department>
|
||||
{this.state.showDeleteMemberDialog && (
|
||||
<ModalPortal>
|
||||
<DeleteMemberDialog
|
||||
toggle={this.toggleCancel}
|
||||
onMemberChanged={this.onMemberChanged}
|
||||
member={this.state.deletedMember}
|
||||
groupID={groupID}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DepartmentMembers.propTypes = DepartmentMembersPropTypes;
|
||||
|
||||
export default DepartmentMembers;
|
@@ -1,29 +1,33 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import dayjs from 'dayjs';
|
||||
import { Link } from '@gatsbyjs/reach-router';
|
||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import toaster from '../../../components/toast';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
import { Table, Dropdown, DropdownToggle } from 'reactstrap';
|
||||
import Paginator from '../../../components/paginator';
|
||||
import Loading from '../../../components/loading';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import MemberItem from './member-item';
|
||||
import RepoItem from './repo-item';
|
||||
import ModalPortal from '../../../components/modal-portal';
|
||||
import AddDepartmentDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-department-dialog';
|
||||
import RenameDepartmentDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-rename-department-dialog';
|
||||
import AddMemberDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-member-dialog';
|
||||
import AddRepoDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-repo-dialog';
|
||||
import { siteRoot, gettext, lang } from '../../../utils/constants';
|
||||
import DeleteRepoDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-repo-dialog';
|
||||
import DepartmentNodeMenu from './departments-node-dropdown-menu';
|
||||
|
||||
import '../../../css/org-department-item.css';
|
||||
|
||||
dayjs.locale(lang);
|
||||
|
||||
const DepartmentDetailPropTypes = {
|
||||
groupID: PropTypes.string,
|
||||
currentItem: PropTypes.string.isRequired,
|
||||
onAddNewDepartment: PropTypes.func,
|
||||
onAddNewMembers: PropTypes.func,
|
||||
onAddNewRepo: PropTypes.func,
|
||||
children: PropTypes.object
|
||||
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,
|
||||
getPreviousPageList: PropTypes.func,
|
||||
getNextPageList: PropTypes.func,
|
||||
resetPerPage: PropTypes.func,
|
||||
currentPageInfo: PropTypes.object,
|
||||
perPage: PropTypes.number,
|
||||
};
|
||||
|
||||
class Department extends React.Component {
|
||||
@@ -31,161 +35,239 @@ class Department extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
groupName: '',
|
||||
ancestorGroups: [],
|
||||
isShowAddDepartmentDialog: false,
|
||||
isShowAddMemberDialog: false,
|
||||
isShowRenameDepartmentDialog: false,
|
||||
isShowAddRepoDialog: false
|
||||
isItemFreezed: false,
|
||||
activeNav: 'members',
|
||||
repos: [],
|
||||
deletedRepo: {},
|
||||
showDeleteRepoDialog: false,
|
||||
dropdownOpen: false,
|
||||
};
|
||||
|
||||
this.navItems = [
|
||||
{ name: 'subDepartments', urlPart: '/', text: gettext('Sub-departments') },
|
||||
{ name: 'members', urlPart: '/members/', text: gettext('Members') },
|
||||
{ name: 'repos', urlPart: '/libraries/', text: gettext('Libraries') }
|
||||
];
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const groupID = this.props.groupID;
|
||||
this.getDepartmentInfo(groupID);
|
||||
this.getRepos(this.props.checkedDepartmentId);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
if (this.props.groupID !== nextProps.groupID) {
|
||||
this.getDepartmentInfo(nextProps.groupID);
|
||||
if (this.props.checkedDepartmentId !== nextProps.checkedDepartmentId || this.props.isAddNewRepo !== nextProps.isAddNewRepo) {
|
||||
this.getRepos(nextProps.checkedDepartmentId);
|
||||
}
|
||||
}
|
||||
|
||||
getDepartmentInfo = (groupID) => {
|
||||
systemAdminAPI.sysAdminGetDepartmentInfo(groupID, true).then(res => {
|
||||
showDeleteRepoDialog = (repo) => {
|
||||
this.setState({
|
||||
ancestorGroups: res.data.ancestor_groups,
|
||||
groupName: res.data.name,
|
||||
});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
showDeleteRepoDialog: true,
|
||||
deletedRepo: repo,
|
||||
});
|
||||
};
|
||||
|
||||
onDepartmentNameChanged = (dept) => {
|
||||
toggleCancel = () => {
|
||||
this.setState({
|
||||
groupName: dept.name
|
||||
showDeleteRepoDialog: false,
|
||||
deletedRepo: {},
|
||||
});
|
||||
};
|
||||
|
||||
toggleRenameDepartmentDialog = () => {
|
||||
this.setState({ isShowRenameDepartmentDialog: !this.state.isShowRenameDepartmentDialog });
|
||||
onRepoChanged = () => {
|
||||
this.getRepos(this.props.checkedDepartmentId);
|
||||
};
|
||||
|
||||
toggleAddRepoDialog = () => {
|
||||
this.setState({ isShowAddRepoDialog: !this.state.isShowAddRepoDialog });
|
||||
freezeItem = () => {
|
||||
this.setState({ isItemFreezed: true });
|
||||
};
|
||||
|
||||
toggleAddMemberDialog = () => {
|
||||
this.setState({ isShowAddMemberDialog: !this.state.isShowAddMemberDialog });
|
||||
unfreezeItem = () => {
|
||||
this.setState({ isItemFreezed: false });
|
||||
};
|
||||
|
||||
toggleAddDepartmentDialog = () => {
|
||||
this.setState({ isShowAddDepartmentDialog: !this.state.isShowAddDepartmentDialog });
|
||||
getCurrentDepartment = () => {
|
||||
const { rootNodes, checkedDepartmentId } = this.props;
|
||||
if (!rootNodes) return {};
|
||||
let currentDepartment = {};
|
||||
let arr = [...rootNodes];
|
||||
while (!currentDepartment.id && arr.length > 0) {
|
||||
let curr = arr.shift();
|
||||
if (curr.id === checkedDepartmentId) {
|
||||
currentDepartment = curr;
|
||||
} else if (curr.children && curr.children.length > 0) {
|
||||
arr.push(...curr.children);
|
||||
}
|
||||
}
|
||||
return currentDepartment;
|
||||
};
|
||||
|
||||
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 });
|
||||
});
|
||||
};
|
||||
|
||||
dropdownToggle = (e) => {
|
||||
e.stopPropagation();
|
||||
this.setState({ dropdownOpen: !this.state.dropdownOpen });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { groupID, currentItem } = this.props;
|
||||
const { groupName } = this.state;
|
||||
|
||||
const topBtn = 'btn btn-secondary operation-item';
|
||||
const topbarChildren = (
|
||||
<Fragment>
|
||||
{groupID &&
|
||||
<Fragment>
|
||||
<button className={topBtn} title={gettext('Rename Department')} onClick={this.toggleRenameDepartmentDialog}>{gettext('Rename Department')}</button>
|
||||
{currentItem == 'subDepartments' && <button className={topBtn} title={gettext('New Sub-department')} onClick={this.toggleAddDepartmentDialog}>{gettext('New Sub-department')}</button>}
|
||||
{currentItem == 'members' && <button className={topBtn} title={gettext('Add Member')} onClick={this.toggleAddMemberDialog}>{gettext('Add Member')}</button>}
|
||||
{currentItem == 'repos' && <button className={topBtn} onClick={this.toggleAddRepoDialog} title={gettext('New Library')}>{gettext('New Library')}</button>}
|
||||
</Fragment>
|
||||
}
|
||||
{this.state.isShowRenameDepartmentDialog && (
|
||||
<ModalPortal>
|
||||
<RenameDepartmentDialog
|
||||
groupID={groupID}
|
||||
name={groupName}
|
||||
toggle={this.toggleRenameDepartmentDialog}
|
||||
onDepartmentNameChanged={this.onDepartmentNameChanged}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isShowAddMemberDialog && (
|
||||
<ModalPortal>
|
||||
<AddMemberDialog
|
||||
toggle={this.toggleAddMemberDialog}
|
||||
onAddNewMembers={this.props.onAddNewMembers}
|
||||
groupID={groupID}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isShowAddRepoDialog && (
|
||||
<ModalPortal>
|
||||
<AddRepoDialog
|
||||
toggle={this.toggleAddRepoDialog}
|
||||
onAddNewRepo={this.props.onAddNewRepo}
|
||||
groupID={groupID}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{this.state.isShowAddDepartmentDialog && (
|
||||
<ModalPortal>
|
||||
<AddDepartmentDialog
|
||||
onAddNewDepartment={this.props.onAddNewDepartment}
|
||||
parentGroupID={groupID}
|
||||
toggle={this.toggleAddDepartmentDialog}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
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>;
|
||||
const currentDepartment = this.getCurrentDepartment();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar {...this.props}>
|
||||
{topbarChildren}
|
||||
</MainPanelTopbar>
|
||||
<div className="main-panel-center flex-row h-100">
|
||||
<div className="cur-view-container o-auto">
|
||||
<div className="cur-view-path">
|
||||
<div className="fleft">
|
||||
<h3 className="sf-heading">
|
||||
{groupID ?
|
||||
<Link to={siteRoot + 'sys/departments/'}>{gettext('Departments')}</Link>
|
||||
: <span>{gettext('Departments')}</span>
|
||||
}
|
||||
{this.state.ancestorGroups.map(ancestor => {
|
||||
let newHref = siteRoot + 'sys/departments/' + ancestor.id + '/';
|
||||
return <span key={ancestor.id}>{' / '}<Link to={newHref}>{ancestor.name}</Link></span>;
|
||||
})}
|
||||
{groupID && <span>{' / '}{groupName}</span>}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="department-content-main d-flex flex-column">
|
||||
<div className="department-content-main-name">
|
||||
{currentDepartment.name}
|
||||
<Dropdown
|
||||
isOpen={this.state.dropdownOpen}
|
||||
toggle={(e) => this.dropdownToggle(e)}
|
||||
direction="down"
|
||||
className="department-dropdown-menu"
|
||||
>
|
||||
<DropdownToggle
|
||||
tag='span'
|
||||
role="button"
|
||||
className='sf3-font-down sf3-font ml-1 sf-dropdown-toggle'
|
||||
title={gettext('More operations')}
|
||||
aria-label={gettext('More operations')}
|
||||
data-toggle="dropdown"
|
||||
/>
|
||||
<DepartmentNodeMenu
|
||||
node={currentDepartment}
|
||||
toggleAddDepartment={this.props.toggleAddDepartment}
|
||||
toggleAddLibrary={this.props.toggleAddLibrary}
|
||||
toggleAddMembers={this.props.toggleAddMembers}
|
||||
toggleRename={this.props.toggleRename}
|
||||
toggleDelete={this.props.toggleDelete}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<ul className="nav border-bottom mx-4">
|
||||
{this.navItems.map((item, index) => {
|
||||
return (
|
||||
<li className="nav-item mr-2" key={index}>
|
||||
<Link to={`${siteRoot}sys/departments/${groupID}${item.urlPart}`} className={`nav-link ${currentItem == item.name ? ' active' : ''}`}>{item.text}</Link>
|
||||
<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' && (
|
||||
<div className='cur-view-content'>
|
||||
{isMembersListLoading
|
||||
? <Loading />
|
||||
: membersList.length == 0
|
||||
? <EmptyTip text={gettext('No members')} />
|
||||
: (
|
||||
<div className="w-xs-250">
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="10%"></th>
|
||||
<th width="25%" onClick={this.sortByName}>{gettext('Name')}{' '}{sortByName && sortIcon}</th>
|
||||
<th width="25%" onClick={this.sortByRole}>{gettext('Role')}{' '}{sortByRole && sortIcon}</th>
|
||||
<th width="30%">{gettext('Contact email')}</th>
|
||||
<th width="10%">{/* Operations */}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{membersList.map((item, index) => {
|
||||
return (
|
||||
<MemberItem
|
||||
key={index}
|
||||
member={item}
|
||||
deleteMember={this.props.deleteMember}
|
||||
setMemberStaff={this.props.setMemberStaff}
|
||||
unfreezeItem={this.unfreezeItem}
|
||||
freezeItem={this.freezeItem}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
{this.props.children}
|
||||
</tbody>
|
||||
</Table>
|
||||
{this.props.currentPageInfo &&
|
||||
<Paginator
|
||||
gotoPreviousPage={this.props.getPreviousPageList}
|
||||
gotoNextPage={this.props.getNextPageList}
|
||||
currentPage={this.props.currentPageInfo.current_page}
|
||||
hasNextPage={this.props.currentPageInfo.has_next_page}
|
||||
curPerPage={this.props.perPage}
|
||||
resetPerPage={this.props.resetPerPage}
|
||||
noURLUpdate={true}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeNav === 'repos' && (
|
||||
<div className="cur-view-content">
|
||||
{repos.length == 0
|
||||
? <EmptyTip text={gettext('No libraries')} />
|
||||
: (
|
||||
<>
|
||||
<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>
|
||||
{this.state.showDeleteRepoDialog && (
|
||||
<ModalPortal>
|
||||
<DeleteRepoDialog
|
||||
toggle={this.toggleCancel}
|
||||
onRepoChanged={this.onRepoChanged}
|
||||
repo={this.state.deletedRepo}
|
||||
groupID={this.props.checkedDepartmentId}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Department.propTypes = DepartmentDetailPropTypes;
|
||||
Department.propTypes = propTypes;
|
||||
|
||||
export default Department;
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import DepartmentsV2TreeNode from './departments-v2-tree-node';
|
||||
import DepartmentTreeNode from './tree-node';
|
||||
|
||||
const DepartmentV2TreePanelPropTypes = {
|
||||
const DepartmentsTreePanelPropTypes = {
|
||||
rootNodes: PropTypes.array,
|
||||
checkedDepartmentId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
onChangeDepartment: PropTypes.func,
|
||||
@@ -14,14 +14,14 @@ const DepartmentV2TreePanelPropTypes = {
|
||||
toggleDelete: PropTypes.func
|
||||
};
|
||||
|
||||
class DepartmentV2TreePanel extends Component {
|
||||
class DepartmentsTreePanel extends Component {
|
||||
render() {
|
||||
const { rootNodes, checkedDepartmentId } = this.props;
|
||||
return (
|
||||
<div className="departments-tree-panel">
|
||||
{rootNodes.map(rootNode => {
|
||||
return (
|
||||
<DepartmentsV2TreeNode
|
||||
<DepartmentTreeNode
|
||||
key={rootNode.id}
|
||||
node={rootNode}
|
||||
checkedDepartmentId={checkedDepartmentId}
|
||||
@@ -40,6 +40,6 @@ class DepartmentV2TreePanel extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
DepartmentV2TreePanel.propTypes = DepartmentV2TreePanelPropTypes;
|
||||
DepartmentsTreePanel.propTypes = DepartmentsTreePanelPropTypes;
|
||||
|
||||
export default DepartmentV2TreePanel;
|
||||
export default DepartmentsTreePanel;
|
@@ -1,19 +1,428 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import '../../../css/org-department-item.css';
|
||||
import React, { Fragment } from 'react';
|
||||
import { Button } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||
import Loading from '../../../components/loading';
|
||||
import toaster from '../../../components/toast';
|
||||
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 DepartmentNode from './department-node';
|
||||
import DepartmentsTreePanel from './departments-tree-panel';
|
||||
import Department from './department';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
|
||||
import '../../../css/system-departments-v2.css';
|
||||
|
||||
class Departments extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="h-100 org-departments">
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
rootNodes: [],
|
||||
checkedDepartmentId: -1,
|
||||
operateNode: null,
|
||||
isAddDepartmentDialogShow: false,
|
||||
isAddMembersDialogShow: false,
|
||||
isRenameDepartmentDialogShow: false,
|
||||
isDeleteDepartmentDialogShow: false,
|
||||
isShowAddRepoDialog: false,
|
||||
membersList: [],
|
||||
isTopDepartmentLoading: false,
|
||||
isMembersListLoading: false,
|
||||
sortBy: 'name', // 'name' or 'role'
|
||||
sortOrder: 'asc', // 'asc' or 'desc',
|
||||
currentPageInfo: null,
|
||||
currentPage: 1,
|
||||
perPage: 100
|
||||
};
|
||||
}
|
||||
|
||||
Departments.propTypes = {
|
||||
children: PropTypes.any.isRequired,
|
||||
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 });
|
||||
const { currentPage, perPage } = this.state;
|
||||
systemAdminAPI.sysAdminListGroupMembers(nodeId, currentPage, perPage).then(res => {
|
||||
const { members, page_info } = res.data;
|
||||
this.setState({
|
||||
membersList: members,
|
||||
currentPageInfo: page_info,
|
||||
isMembersListLoading: false
|
||||
});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
getPreviousPageList = () => {
|
||||
const { checkedDepartmentId, currentPageInfo } = this.state;
|
||||
const { current_page } = currentPageInfo;
|
||||
this.setState({ currentPage: current_page - 1 }, () => {
|
||||
this.loadDepartmentMembers(checkedDepartmentId);
|
||||
});
|
||||
};
|
||||
|
||||
getNextPageList = () => {
|
||||
const { checkedDepartmentId, currentPageInfo } = this.state;
|
||||
const { current_page } = currentPageInfo;
|
||||
this.setState({ currentPage: current_page + 1 }, () => {
|
||||
this.loadDepartmentMembers(checkedDepartmentId);
|
||||
});
|
||||
};
|
||||
|
||||
resetPerPage = (perPage) => {
|
||||
this.setState({
|
||||
perPage: perPage,
|
||||
currentPage: 1
|
||||
}, () => {
|
||||
const { checkedDepartmentId } = this.state;
|
||||
this.loadDepartmentMembers(checkedDepartmentId);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
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);
|
||||
if (rootNodes.length === 0) {
|
||||
this.setState({
|
||||
rootNodes,
|
||||
checkedDepartmentId: -1
|
||||
});
|
||||
} else {
|
||||
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.role == 'Admin' ? -1 : 1;
|
||||
};
|
||||
break;
|
||||
case 'role-desc':
|
||||
comparator = function (a, b) {
|
||||
return a.role == 'Admin' ? 1 : -1;
|
||||
};
|
||||
break;
|
||||
// no default
|
||||
}
|
||||
items.sort((a, b) => {
|
||||
return comparator(a, b);
|
||||
});
|
||||
return items;
|
||||
};
|
||||
|
||||
sortItems = (sortBy, 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>
|
||||
<MainPanelTopbar {...this.props}>
|
||||
<button className='btn btn-secondary operation-item' title={gettext('New Department')} onClick={() => {this.toggleAddDepartment(null);}}>{gettext('New Department')}</button>
|
||||
</MainPanelTopbar>
|
||||
<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) &&
|
||||
<>
|
||||
<DepartmentsTreePanel
|
||||
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}
|
||||
/>
|
||||
<Department
|
||||
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}
|
||||
toggleAddDepartment={this.toggleAddDepartment}
|
||||
toggleAddLibrary={this.toggleAddLibrary}
|
||||
toggleAddMembers={this.toggleAddMembers}
|
||||
toggleRename={this.toggleRename}
|
||||
toggleDelete={this.toggleDelete}
|
||||
getPreviousPageList={this.getPreviousPageList}
|
||||
getNextPageList={this.getNextPageList}
|
||||
resetPerPage={this.resetPerPage}
|
||||
currentPageInfo={this.state.currentPageInfo}
|
||||
perPage={this.state.perPage}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{(!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 Departments;
|
||||
|
@@ -1,170 +0,0 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { Link } from '@gatsbyjs/reach-router';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { siteRoot, gettext } from '../../../utils/constants';
|
||||
import OpMenu from '../../../components/dialog/op-menu';
|
||||
import ModalPortal from '../../../components/modal-portal';
|
||||
import RenameDepartmentDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-rename-department-dialog';
|
||||
import DeleteDepartmentDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-department-dialog';
|
||||
import SetGroupQuotaDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-set-group-quota-dialog';
|
||||
|
||||
const GroupItemPropTypes = {
|
||||
isItemFreezed: PropTypes.bool.isRequired,
|
||||
onFreezedItem: PropTypes.func.isRequired,
|
||||
onUnfreezedItem: PropTypes.func.isRequired,
|
||||
group: PropTypes.object.isRequired,
|
||||
onDepartmentNameChanged: PropTypes.func.isRequired,
|
||||
onDeleteDepartment: PropTypes.func.isRequired,
|
||||
onSetDepartmentQuota: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
class GroupItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpIconShown: false,
|
||||
highlight: false,
|
||||
isSetQuotaDialogOpen: false,
|
||||
isDeleteDialogOpen: false,
|
||||
isRenameDialogOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
isOpIconShown: true,
|
||||
highlight: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseOut = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
isOpIconShown: false,
|
||||
highlight: false
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onUnfreezedItem = () => {
|
||||
this.setState({
|
||||
highlight: false,
|
||||
isOpIconShow: false
|
||||
});
|
||||
this.props.onUnfreezedItem();
|
||||
};
|
||||
|
||||
translateOperations = (item) => {
|
||||
let translateResult = '';
|
||||
switch (item) {
|
||||
case 'Rename':
|
||||
translateResult = gettext('Rename');
|
||||
break;
|
||||
case 'Delete':
|
||||
translateResult = gettext('Delete');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return translateResult;
|
||||
};
|
||||
|
||||
onMenuItemClick = (operation) => {
|
||||
switch (operation) {
|
||||
case 'Rename':
|
||||
this.toggleRenameDialog();
|
||||
break;
|
||||
case 'Delete':
|
||||
this.toggleDeleteDialog();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
toggleRenameDialog = () => {
|
||||
this.setState({
|
||||
isRenameDialogOpen: !this.state.isRenameDialogOpen
|
||||
});
|
||||
};
|
||||
|
||||
toggleDeleteDialog = () => {
|
||||
this.setState({
|
||||
isDeleteDialogOpen: !this.state.isDeleteDialogOpen
|
||||
});
|
||||
};
|
||||
|
||||
toggleSetQuotaDialog = () => {
|
||||
this.setState({
|
||||
isSetQuotaDialogOpen: !this.state.isSetQuotaDialogOpen
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { group } = this.props;
|
||||
const { highlight, isOpIconShown, isRenameDialogOpen, isDeleteDialogOpen, isSetQuotaDialogOpen } = this.state;
|
||||
const newHref = siteRoot + 'sys/departments/' + group.id + '/';
|
||||
return (
|
||||
<Fragment>
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<td><Link to={newHref}>{group.name}</Link></td>
|
||||
<td>{dayjs(group.created_at).fromNow()}</td>
|
||||
<td>
|
||||
{Utils.bytesToSize(group.quota)}{' '}
|
||||
<span onClick={this.toggleSetQuotaDialog} title={gettext('Edit')} className={`sf3-font sf3-font-rename attr-action-icon ${highlight ? '' : 'vh'}`}></span>
|
||||
</td>
|
||||
<td>
|
||||
{isOpIconShown &&
|
||||
<OpMenu
|
||||
operations={['Rename', 'Delete']}
|
||||
translateOperations={this.translateOperations}
|
||||
onMenuItemClick={this.onMenuItemClick}
|
||||
onFreezedItem={this.props.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
/>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
{isDeleteDialogOpen && (
|
||||
<ModalPortal>
|
||||
<DeleteDepartmentDialog
|
||||
group={group}
|
||||
onDeleteDepartment={this.props.onDeleteDepartment}
|
||||
toggle={this.toggleDeleteDialog}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{isSetQuotaDialogOpen && (
|
||||
<ModalPortal>
|
||||
<SetGroupQuotaDialog
|
||||
groupID={group.id}
|
||||
onSetQuota={this.props.onSetDepartmentQuota}
|
||||
toggle={this.toggleSetQuotaDialog}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
{isRenameDialogOpen && (
|
||||
<RenameDepartmentDialog
|
||||
groupID={group.id}
|
||||
name={group.name}
|
||||
toggle={this.toggleRenameDialog}
|
||||
onDepartmentNameChanged={this.props.onDepartmentNameChanged}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
GroupItem.propTypes = GroupItemPropTypes;
|
||||
|
||||
export default GroupItem;
|
@@ -1,19 +1,18 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import toaster from '../../../components/toast';
|
||||
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||
import RoleSelector from '../../../components/single-selector';
|
||||
import UserLink from '../user-link';
|
||||
import { gettext, siteRoot } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
|
||||
const MemberItemPropTypes = {
|
||||
groupID: PropTypes.string,
|
||||
member: PropTypes.object.isRequired,
|
||||
isItemFreezed: PropTypes.bool.isRequired,
|
||||
onMemberChanged: PropTypes.func.isRequired,
|
||||
showDeleteMemberDialog: PropTypes.func.isRequired,
|
||||
toggleItemFreezed: PropTypes.func.isRequired,
|
||||
const propTypes = {
|
||||
isItemFreezed: PropTypes.bool,
|
||||
member: PropTypes.object,
|
||||
setMemberStaff: PropTypes.func,
|
||||
deleteMember: PropTypes.func,
|
||||
unfreezeItem: PropTypes.func,
|
||||
freezeItem: PropTypes.func,
|
||||
};
|
||||
|
||||
class MemberItem extends React.Component {
|
||||
@@ -21,7 +20,8 @@ class MemberItem extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
highlight: false
|
||||
highlighted: false,
|
||||
isItemMenuShow: false,
|
||||
};
|
||||
this.roleOptions = [
|
||||
{ value: 'Admin', text: gettext('Admin'), isSelected: false },
|
||||
@@ -30,58 +30,119 @@ class MemberItem extends React.Component {
|
||||
}
|
||||
|
||||
onMouseEnter = () => {
|
||||
if (this.props.isItemFreezed) return;
|
||||
this.setState({ highlight: true });
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({ highlighted: true });
|
||||
}
|
||||
};
|
||||
|
||||
onMouseLeave = () => {
|
||||
if (this.props.isItemFreezed) return;
|
||||
this.setState({ highlight: false });
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({ highlighted: false });
|
||||
}
|
||||
};
|
||||
|
||||
onChangeUserRole = (roleOption) => {
|
||||
let isAdmin = roleOption.value === 'Admin' ? true : false;
|
||||
systemAdminAPI.sysAdminUpdateGroupMemberRole(this.props.groupID, this.props.member.email, isAdmin).then((res) => {
|
||||
this.props.onMemberChanged();
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
setMemberStaff = (role) => {
|
||||
this.props.setMemberStaff(this.props.member.email, role.value === 'Admin');
|
||||
};
|
||||
|
||||
deleteMember = () => {
|
||||
const { member } = this.props;
|
||||
this.props.deleteMember(member.email);
|
||||
};
|
||||
|
||||
toggleDropdownMenu = () => {
|
||||
this.setState({
|
||||
highlight: false
|
||||
isItemMenuShow: !this.state.isItemMenuShow
|
||||
}, () => {
|
||||
if (this.state.isItemMenuShow && typeof(this.props.freezeItem) === 'function') {
|
||||
this.props.freezeItem();
|
||||
} else if (!this.state.isItemMenuShow && typeof(this.props.unfreezeItem) === 'function') {
|
||||
this.props.unfreezeItem();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
toggleItemFreezed = (freezed) => {
|
||||
if (freezed) {
|
||||
this.props.freezeItem();
|
||||
} else {
|
||||
this.props.unfreezeItem();
|
||||
this.setState({ highlighted: false });
|
||||
}
|
||||
};
|
||||
|
||||
toggleDeleteMemberDialog = () => {
|
||||
this.setState({
|
||||
isDeleteMemberDialogOpen: !this.state.isDeleteMemberDialogOpen
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const member = this.props.member;
|
||||
const highlight = this.state.highlight;
|
||||
if (member.role === 'Owner') return null;
|
||||
const { member } = this.props;
|
||||
const { highlighted, isItemMenuShow, isDeleteMemberDialogOpen } = this.state;
|
||||
|
||||
this.roleOptions = this.roleOptions.map(item => {
|
||||
item.isSelected = item.value == member.role;
|
||||
return item;
|
||||
});
|
||||
const currentSelectedOption = this.roleOptions.filter(item => item.isSelected)[0];
|
||||
|
||||
return (
|
||||
<tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<td><img src={member.avatar_url} alt="member-header" width="24" className="avatar"/></td>
|
||||
<td><UserLink email={member.email} name={member.name} /></td>
|
||||
<>
|
||||
<tr className={`departments-members-item ${highlighted ? 'tr-highlight' : ''}`} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<td><img className="avatar" src={member.avatar_url} alt="" /></td>
|
||||
<td className='text-truncate'>
|
||||
<a href={`${siteRoot}sys/users/${encodeURIComponent(member.email)}/`}>{member.name}</a>
|
||||
</td>
|
||||
<td>
|
||||
<RoleSelector
|
||||
isDropdownToggleShown={highlight}
|
||||
isDropdownToggleShown={highlighted}
|
||||
currentSelectedOption={currentSelectedOption}
|
||||
options={this.roleOptions}
|
||||
selectOption={this.onChangeUserRole}
|
||||
toggleItemFreezed={this.props.toggleItemFreezed}
|
||||
selectOption={this.setMemberStaff}
|
||||
toggleItemFreezed={this.toggleItemFreezed}
|
||||
/>
|
||||
</td>
|
||||
<td className="cursor-pointer text-center" onClick={this.props.showDeleteMemberDialog.bind(this, member)}>
|
||||
<span className={`sf2-icon-x3 action-icon ${highlight ? '' : 'vh'}`} title="Delete"></span>
|
||||
<td>{member.contact_email}</td>
|
||||
<td>
|
||||
{highlighted &&
|
||||
<Dropdown
|
||||
isOpen={isItemMenuShow}
|
||||
toggle={this.toggleDropdownMenu}
|
||||
direction="down"
|
||||
>
|
||||
<DropdownToggle
|
||||
tag='a'
|
||||
role="button"
|
||||
className='attr-action-icon sf3-font sf3-font-more-vertical'
|
||||
title={gettext('More operations')}
|
||||
aria-label={gettext('More operations')}
|
||||
data-toggle="dropdown"
|
||||
/>
|
||||
<DropdownMenu right={true}>
|
||||
<DropdownItem onClick={this.toggleDeleteMemberDialog}>{gettext('Delete')}</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
{isDeleteMemberDialogOpen && (
|
||||
<CommonOperationConfirmationDialog
|
||||
title={gettext('Delete Member')}
|
||||
message={
|
||||
gettext('Are you sure you want to delete {placeholder} ?')
|
||||
.replace('{placeholder}', '<span class="op-target">' + Utils.HTMLescape(member.name) + '</span>')
|
||||
}
|
||||
executeOperation={this.deleteMember}
|
||||
confirmBtnText={gettext('Delete')}
|
||||
toggleDialog={this.toggleDeleteMemberDialog}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MemberItem.propTypes = MemberItemPropTypes;
|
||||
MemberItem.propTypes = propTypes;
|
||||
|
||||
export default MemberItem;
|
||||
|
@@ -34,15 +34,16 @@ class RepoItem extends React.Component {
|
||||
let iconUrl = Utils.getLibIconUrl(repo);
|
||||
return (
|
||||
<tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<td><img src={iconUrl} width="24" alt={gettext('icon')}/></td>
|
||||
{ enableSysAdminViewRepo ?
|
||||
<td><a href={`${siteRoot}sys/libraries/${repo.repo_id}/${encodeURIComponent(repoName)}/`}>{repoName}</a></td>
|
||||
:
|
||||
<td>{repoName}</td>
|
||||
<td><img src={iconUrl} width="24" alt="" /></td>
|
||||
<td>
|
||||
{enableSysAdminViewRepo
|
||||
? <a href={`${siteRoot}sys/libraries/${repo.repo_id}/${encodeURIComponent(repoName)}/`}>{repoName}</a>
|
||||
: <span>{repoName}</span>
|
||||
}
|
||||
</td>
|
||||
<td>{Utils.bytesToSize(repo.size)}</td>
|
||||
<td className="cursor-pointer text-center" onClick={this.props.showDeleteRepoDialog.bind(this, repo)}>
|
||||
<span className={`sf3-font-delete1 sf3-font op-icon ${highlight ? '' : 'vh'}`} title="Delete"></span>
|
||||
<span className={`sf3-font-delete1 sf3-font op-icon ${highlight ? '' : 'vh'}`} title={gettext('Delete')}></span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
@@ -1,143 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import dayjs from 'dayjs';
|
||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import toaster from '../../../components/toast';
|
||||
import { gettext, lang } from '../../../utils/constants';
|
||||
import GroupItem from './group-item';
|
||||
import Department from './department';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
|
||||
import '../../../css/org-department-item.css';
|
||||
|
||||
dayjs.locale(lang);
|
||||
|
||||
const SubDepartmentsPropTypes = {
|
||||
groupID: PropTypes.string
|
||||
};
|
||||
|
||||
class SubDepartments extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isItemFreezed: false,
|
||||
groups: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.listSubDepartments(this.props.groupID);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
if (this.props.groupID !== nextProps.groupID) {
|
||||
this.listSubDepartments(nextProps.groupID);
|
||||
}
|
||||
}
|
||||
|
||||
listSubDepartments = (groupID) => {
|
||||
systemAdminAPI.sysAdminGetDepartmentInfo(groupID, true).then(res => {
|
||||
this.setState({ groups: res.data.groups });
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
};
|
||||
|
||||
onSubDepartmentNameChanged = (dept) => {
|
||||
this.setState({
|
||||
groups: this.state.groups.map(item => {
|
||||
if (item.id == dept.id) {
|
||||
item.name = dept.name;
|
||||
}
|
||||
return item;
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
onFreezedItem = () => {
|
||||
this.setState({ isItemFreezed: true });
|
||||
};
|
||||
|
||||
onUnfreezedItem = () => {
|
||||
this.setState({ isItemFreezed: false });
|
||||
};
|
||||
|
||||
onAddNewDepartment = (newDepartment) => {
|
||||
const { groups } = this.state;
|
||||
groups.unshift(newDepartment);
|
||||
this.setState({
|
||||
groups: groups
|
||||
});
|
||||
};
|
||||
|
||||
onDeleteDepartment = (id) => {
|
||||
const { groups } = this.state;
|
||||
this.setState({
|
||||
groups: groups.filter(item => item.id != id)
|
||||
});
|
||||
};
|
||||
|
||||
onSetDepartmentQuota = (target) => {
|
||||
const { groups } = this.state;
|
||||
this.setState({
|
||||
groups: groups.map((item) => {
|
||||
if (item.id == target.id) {
|
||||
item.quota = target.quota;
|
||||
}
|
||||
return item;
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { groups } = this.state;
|
||||
const { groupID } = this.props;
|
||||
|
||||
return (
|
||||
<Department
|
||||
groupID={groupID}
|
||||
currentItem="subDepartments"
|
||||
onAddNewDepartment={this.onAddNewDepartment}
|
||||
>
|
||||
<div className="cur-view-content">
|
||||
{groups && groups.length > 0 ?
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40%">{gettext('Name')}</th>
|
||||
<th width="25%">{gettext('Created At')}</th>
|
||||
<th width="20%">{gettext('Quota')}</th>
|
||||
<th width="15%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{groups.map((group, index) => {
|
||||
return (
|
||||
<GroupItem
|
||||
key={group.id}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
onFreezedItem={this.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
onDepartmentNameChanged={this.onSubDepartmentNameChanged}
|
||||
group={group}
|
||||
onDeleteDepartment={this.onDeleteDepartment}
|
||||
onSetDepartmentQuota={this.onSetDepartmentQuota}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
: <EmptyTip text={gettext('No sub-departments')}/>
|
||||
}
|
||||
</div>
|
||||
</Department>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SubDepartments.propTypes = SubDepartmentsPropTypes;
|
||||
|
||||
export default SubDepartments;
|
@@ -5,7 +5,7 @@ import { Dropdown, DropdownToggle } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import DepartmentNodeMenu from './departments-node-dropdown-menu';
|
||||
|
||||
const departmentsV2TreeNodePropTypes = {
|
||||
const departmentsTreeNodePropTypes = {
|
||||
node: PropTypes.object,
|
||||
checkedDepartmentId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
listSubDepartments: PropTypes.func,
|
||||
@@ -17,7 +17,7 @@ const departmentsV2TreeNodePropTypes = {
|
||||
toggleDelete: PropTypes.func
|
||||
};
|
||||
|
||||
class DepartmentsV2TreeNode extends Component {
|
||||
class DepartmentsTreeNode extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -74,7 +74,7 @@ class DepartmentsV2TreeNode extends Component {
|
||||
if (nodes.length > 0) {
|
||||
return nodes.map((node) => {
|
||||
return (
|
||||
<DepartmentsV2TreeNode
|
||||
<DepartmentsTreeNode
|
||||
key={node.id}
|
||||
node={node}
|
||||
onChangeDepartment={this.props.onChangeDepartment}
|
||||
@@ -167,6 +167,6 @@ class DepartmentsV2TreeNode extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
DepartmentsV2TreeNode.propTypes = departmentsV2TreeNodePropTypes;
|
||||
DepartmentsTreeNode.propTypes = departmentsTreeNodePropTypes;
|
||||
|
||||
export default DepartmentsV2TreeNode;
|
||||
export default DepartmentsTreeNode;
|
@@ -46,7 +46,7 @@ import SearchGroups from './groups/search-groups';
|
||||
import GroupRepos from './groups/group-repos';
|
||||
import GroupMembers from './groups/group-members';
|
||||
|
||||
import DepartmentsV2 from './departments-v2/departments-v2';
|
||||
import Departments from './departments/departments';
|
||||
|
||||
import ShareLinks from './links/share-links';
|
||||
import UploadLinks from './links/upload-links';
|
||||
@@ -237,7 +237,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} />
|
||||
<DepartmentsV2 path={siteRoot + 'sys/departments/'} onCloseSidePanel={this.onCloseSidePanel} />
|
||||
<Departments path={siteRoot + 'sys/departments/'} {...commonProps} />
|
||||
<ShareLinks path={siteRoot + 'sys/share-links'} {...commonProps} />
|
||||
<UploadLinks path={siteRoot + 'sys/upload-links'} {...commonProps} />
|
||||
<Orgs path={siteRoot + 'sys/organizations'} {...commonProps} />
|
||||
|
Reference in New Issue
Block a user