mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-09 10:50:24 +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 = {
|
const propTypes = {
|
||||||
repo: PropTypes.object.isRequired,
|
repo: PropTypes.object.isRequired,
|
||||||
toggle: PropTypes.func.isRequired,
|
toggle: PropTypes.func.isRequired,
|
||||||
groupID: PropTypes.string,
|
groupID: PropTypes.number,
|
||||||
onRepoChanged: PropTypes.func.isRequired
|
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>
|
<span className="sf3-font sf3-font-down rotate-270 d-inline-block"></span>
|
||||||
</button>
|
</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
|
<DropdownToggle
|
||||||
className="ml-6"
|
|
||||||
data-toggle="dropdown"
|
data-toggle="dropdown"
|
||||||
aria-expanded={this.state.isMenuShow}
|
aria-expanded={this.state.isMenuShow}
|
||||||
onClick={this.toggleOperationMenu}
|
onClick={this.toggleOperationMenu}
|
||||||
|
@@ -84,11 +84,6 @@
|
|||||||
.department-content-main {
|
.department-content-main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.department-content-main:hover {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.department-content-main table td {
|
.department-content-main table td {
|
||||||
@@ -113,10 +108,6 @@
|
|||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.department-content-main .cur-view-content .table {
|
|
||||||
margin-bottom: 6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.department-content-main .cur-view-content .sort-dirent {
|
.department-content-main .cur-view-content .sort-dirent {
|
||||||
transform: scale(0.8);
|
transform: scale(0.8);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
@@ -51,10 +51,6 @@ class Department extends React.Component {
|
|||||||
this.setState({ isItemFreezed: false });
|
this.setState({ isItemFreezed: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleItemFreezed = () => {
|
|
||||||
this.setState({ isItemFreezed: !this.state.isItemFreezed });
|
|
||||||
};
|
|
||||||
|
|
||||||
getCurrentDepartment = () => {
|
getCurrentDepartment = () => {
|
||||||
const { rootNodes, checkedDepartmentId } = this.props;
|
const { rootNodes, checkedDepartmentId } = this.props;
|
||||||
if (!rootNodes) return {};
|
if (!rootNodes) return {};
|
||||||
@@ -116,7 +112,7 @@ class Department extends React.Component {
|
|||||||
const currentDepartment = this.getCurrentDepartment();
|
const currentDepartment = this.getCurrentDepartment();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="department-content-main">
|
<div className="department-content-main d-flex flex-column">
|
||||||
<div className="department-content-main-name">
|
<div className="department-content-main-name">
|
||||||
{currentDepartment.name}
|
{currentDepartment.name}
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -180,7 +176,6 @@ class Department extends React.Component {
|
|||||||
setMemberStaff={this.props.setMemberStaff}
|
setMemberStaff={this.props.setMemberStaff}
|
||||||
unfreezeItem={this.unfreezeItem}
|
unfreezeItem={this.unfreezeItem}
|
||||||
freezeItem={this.freezeItem}
|
freezeItem={this.freezeItem}
|
||||||
toggleItemFreezed={this.toggleItemFreezed}
|
|
||||||
isItemFreezed={this.state.isItemFreezed}
|
isItemFreezed={this.state.isItemFreezed}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@@ -13,7 +13,6 @@ const propTypes = {
|
|||||||
deleteMember: PropTypes.func,
|
deleteMember: PropTypes.func,
|
||||||
unfreezeItem: PropTypes.func,
|
unfreezeItem: PropTypes.func,
|
||||||
freezeItem: PropTypes.func,
|
freezeItem: PropTypes.func,
|
||||||
toggleItemFreezed: PropTypes.func,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class DepartmentsV2MembersItem extends React.Component {
|
class DepartmentsV2MembersItem extends React.Component {
|
||||||
@@ -87,7 +86,6 @@ class DepartmentsV2MembersItem extends React.Component {
|
|||||||
});
|
});
|
||||||
const currentSelectedOption = this.roleOptions.filter(item => item.isSelected)[0];
|
const currentSelectedOption = this.roleOptions.filter(item => item.isSelected)[0];
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<tr className={`departments-members-item ${highlighted ? 'tr-highlight' : ''}`} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
<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 PropTypes from 'prop-types';
|
||||||
import dayjs from 'dayjs';
|
import { Table, Dropdown, DropdownToggle } from 'reactstrap';
|
||||||
import { Link } from '@gatsbyjs/reach-router';
|
import Paginator from '../../../components/paginator';
|
||||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
import Loading from '../../../components/loading';
|
||||||
import { Utils } from '../../../utils/utils';
|
import EmptyTip from '../../../components/empty-tip';
|
||||||
import toaster from '../../../components/toast';
|
|
||||||
import MainPanelTopbar from '../main-panel-topbar';
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import MemberItem from './member-item';
|
||||||
|
import RepoItem from './repo-item';
|
||||||
import ModalPortal from '../../../components/modal-portal';
|
import ModalPortal from '../../../components/modal-portal';
|
||||||
import AddDepartmentDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-department-dialog';
|
import DeleteRepoDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-repo-dialog';
|
||||||
import RenameDepartmentDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-rename-department-dialog';
|
import DepartmentNodeMenu from './departments-node-dropdown-menu';
|
||||||
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 '../../../css/org-department-item.css';
|
const propTypes = {
|
||||||
|
rootNodes: PropTypes.array,
|
||||||
dayjs.locale(lang);
|
checkedDepartmentId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||||
|
membersList: PropTypes.array,
|
||||||
const DepartmentDetailPropTypes = {
|
isMembersListLoading: PropTypes.bool,
|
||||||
groupID: PropTypes.string,
|
setMemberStaff: PropTypes.func,
|
||||||
currentItem: PropTypes.string.isRequired,
|
sortItems: PropTypes.func,
|
||||||
onAddNewDepartment: PropTypes.func,
|
sortOrder: PropTypes.string,
|
||||||
onAddNewMembers: PropTypes.func,
|
sortBy: PropTypes.string,
|
||||||
onAddNewRepo: PropTypes.func,
|
deleteMember: PropTypes.func,
|
||||||
children: PropTypes.object
|
getRepos: PropTypes.func,
|
||||||
|
getPreviousPageList: PropTypes.func,
|
||||||
|
getNextPageList: PropTypes.func,
|
||||||
|
resetPerPage: PropTypes.func,
|
||||||
|
currentPageInfo: PropTypes.object,
|
||||||
|
perPage: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Department extends React.Component {
|
class Department extends React.Component {
|
||||||
@@ -31,161 +35,239 @@ class Department extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
groupName: '',
|
isItemFreezed: false,
|
||||||
ancestorGroups: [],
|
activeNav: 'members',
|
||||||
isShowAddDepartmentDialog: false,
|
repos: [],
|
||||||
isShowAddMemberDialog: false,
|
deletedRepo: {},
|
||||||
isShowRenameDepartmentDialog: false,
|
showDeleteRepoDialog: false,
|
||||||
isShowAddRepoDialog: 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() {
|
componentDidMount() {
|
||||||
const groupID = this.props.groupID;
|
this.getRepos(this.props.checkedDepartmentId);
|
||||||
this.getDepartmentInfo(groupID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
if (this.props.groupID !== nextProps.groupID) {
|
if (this.props.checkedDepartmentId !== nextProps.checkedDepartmentId || this.props.isAddNewRepo !== nextProps.isAddNewRepo) {
|
||||||
this.getDepartmentInfo(nextProps.groupID);
|
this.getRepos(nextProps.checkedDepartmentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getDepartmentInfo = (groupID) => {
|
showDeleteRepoDialog = (repo) => {
|
||||||
systemAdminAPI.sysAdminGetDepartmentInfo(groupID, true).then(res => {
|
|
||||||
this.setState({
|
|
||||||
ancestorGroups: res.data.ancestor_groups,
|
|
||||||
groupName: res.data.name,
|
|
||||||
});
|
|
||||||
}).catch(error => {
|
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
|
||||||
toaster.danger(errMessage);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onDepartmentNameChanged = (dept) => {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
groupName: dept.name
|
showDeleteRepoDialog: true,
|
||||||
|
deletedRepo: repo,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleRenameDepartmentDialog = () => {
|
toggleCancel = () => {
|
||||||
this.setState({ isShowRenameDepartmentDialog: !this.state.isShowRenameDepartmentDialog });
|
this.setState({
|
||||||
|
showDeleteRepoDialog: false,
|
||||||
|
deletedRepo: {},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleAddRepoDialog = () => {
|
onRepoChanged = () => {
|
||||||
this.setState({ isShowAddRepoDialog: !this.state.isShowAddRepoDialog });
|
this.getRepos(this.props.checkedDepartmentId);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleAddMemberDialog = () => {
|
freezeItem = () => {
|
||||||
this.setState({ isShowAddMemberDialog: !this.state.isShowAddMemberDialog });
|
this.setState({ isItemFreezed: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleAddDepartmentDialog = () => {
|
unfreezeItem = () => {
|
||||||
this.setState({ isShowAddDepartmentDialog: !this.state.isShowAddDepartmentDialog });
|
this.setState({ isItemFreezed: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
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() {
|
render() {
|
||||||
const { groupID, currentItem } = this.props;
|
const { activeNav, repos } = this.state;
|
||||||
const { groupName } = this.state;
|
const { membersList, isMembersListLoading, sortBy, sortOrder } = this.props;
|
||||||
|
const sortByName = sortBy === 'name';
|
||||||
const topBtn = 'btn btn-secondary operation-item';
|
const sortByRole = sortBy === 'role';
|
||||||
const topbarChildren = (
|
const sortIcon = <span className={`sort-dirent sf3-font sf3-font-down ${sortOrder === 'asc' ? 'rotate-180' : ''}`}></span>;
|
||||||
<Fragment>
|
const currentDepartment = this.getCurrentDepartment();
|
||||||
{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>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<div className="department-content-main d-flex flex-column">
|
||||||
<MainPanelTopbar {...this.props}>
|
<div className="department-content-main-name">
|
||||||
{topbarChildren}
|
{currentDepartment.name}
|
||||||
</MainPanelTopbar>
|
<Dropdown
|
||||||
<div className="main-panel-center flex-row h-100">
|
isOpen={this.state.dropdownOpen}
|
||||||
<div className="cur-view-container o-auto">
|
toggle={(e) => this.dropdownToggle(e)}
|
||||||
<div className="cur-view-path">
|
direction="down"
|
||||||
<div className="fleft">
|
className="department-dropdown-menu"
|
||||||
<h3 className="sf-heading">
|
>
|
||||||
{groupID ?
|
<DropdownToggle
|
||||||
<Link to={siteRoot + 'sys/departments/'}>{gettext('Departments')}</Link>
|
tag='span'
|
||||||
: <span>{gettext('Departments')}</span>
|
role="button"
|
||||||
}
|
className='sf3-font-down sf3-font ml-1 sf-dropdown-toggle'
|
||||||
{this.state.ancestorGroups.map(ancestor => {
|
title={gettext('More operations')}
|
||||||
let newHref = siteRoot + 'sys/departments/' + ancestor.id + '/';
|
aria-label={gettext('More operations')}
|
||||||
return <span key={ancestor.id}>{' / '}<Link to={newHref}>{ancestor.name}</Link></span>;
|
data-toggle="dropdown"
|
||||||
})}
|
/>
|
||||||
{groupID && <span>{' / '}{groupName}</span>}
|
<DepartmentNodeMenu
|
||||||
</h3>
|
node={currentDepartment}
|
||||||
</div>
|
toggleAddDepartment={this.props.toggleAddDepartment}
|
||||||
</div>
|
toggleAddLibrary={this.props.toggleAddLibrary}
|
||||||
|
toggleAddMembers={this.props.toggleAddMembers}
|
||||||
<ul className="nav border-bottom mx-4">
|
toggleRename={this.props.toggleRename}
|
||||||
{this.navItems.map((item, index) => {
|
toggleDelete={this.props.toggleDelete}
|
||||||
return (
|
/>
|
||||||
<li className="nav-item mr-2" key={index}>
|
</Dropdown>
|
||||||
<Link to={`${siteRoot}sys/departments/${groupID}${item.urlPart}`} className={`nav-link ${currentItem == item.name ? ' active' : ''}`}>{item.text}</Link>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
{this.props.children}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Department.propTypes = DepartmentDetailPropTypes;
|
Department.propTypes = propTypes;
|
||||||
|
|
||||||
export default Department;
|
export default Department;
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import DepartmentsV2TreeNode from './departments-v2-tree-node';
|
import DepartmentTreeNode from './tree-node';
|
||||||
|
|
||||||
const DepartmentV2TreePanelPropTypes = {
|
const DepartmentsTreePanelPropTypes = {
|
||||||
rootNodes: PropTypes.array,
|
rootNodes: PropTypes.array,
|
||||||
checkedDepartmentId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
checkedDepartmentId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||||
onChangeDepartment: PropTypes.func,
|
onChangeDepartment: PropTypes.func,
|
||||||
@@ -14,14 +14,14 @@ const DepartmentV2TreePanelPropTypes = {
|
|||||||
toggleDelete: PropTypes.func
|
toggleDelete: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
class DepartmentV2TreePanel extends Component {
|
class DepartmentsTreePanel extends Component {
|
||||||
render() {
|
render() {
|
||||||
const { rootNodes, checkedDepartmentId } = this.props;
|
const { rootNodes, checkedDepartmentId } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="departments-tree-panel">
|
<div className="departments-tree-panel">
|
||||||
{rootNodes.map(rootNode => {
|
{rootNodes.map(rootNode => {
|
||||||
return (
|
return (
|
||||||
<DepartmentsV2TreeNode
|
<DepartmentTreeNode
|
||||||
key={rootNode.id}
|
key={rootNode.id}
|
||||||
node={rootNode}
|
node={rootNode}
|
||||||
checkedDepartmentId={checkedDepartmentId}
|
checkedDepartmentId={checkedDepartmentId}
|
||||||
@@ -40,6 +40,6 @@ class DepartmentV2TreePanel extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DepartmentV2TreePanel.propTypes = DepartmentV2TreePanelPropTypes;
|
DepartmentsTreePanel.propTypes = DepartmentsTreePanelPropTypes;
|
||||||
|
|
||||||
export default DepartmentV2TreePanel;
|
export default DepartmentsTreePanel;
|
@@ -1,19 +1,428 @@
|
|||||||
import React from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { Button } from 'reactstrap';
|
||||||
import '../../../css/org-department-item.css';
|
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 {
|
class Departments extends React.Component {
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
render() {
|
||||||
|
const { rootNodes, operateNode, checkedDepartmentId, isAddDepartmentDialogShow, isAddMembersDialogShow,
|
||||||
|
membersList, isMembersListLoading, isTopDepartmentLoading, isRenameDepartmentDialogShow,
|
||||||
|
isDeleteDepartmentDialogShow, sortBy, sortOrder } = this.state;
|
||||||
return (
|
return (
|
||||||
<div className="h-100 org-departments">
|
<Fragment>
|
||||||
{this.props.children}
|
<MainPanelTopbar {...this.props}>
|
||||||
</div>
|
<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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Departments.propTypes = {
|
|
||||||
children: PropTypes.any.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Departments;
|
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 React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { systemAdminAPI } from '../../../utils/system-admin-api';
|
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||||
import { Utils } from '../../../utils/utils';
|
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||||
import { gettext } from '../../../utils/constants';
|
|
||||||
import toaster from '../../../components/toast';
|
|
||||||
import RoleSelector from '../../../components/single-selector';
|
import RoleSelector from '../../../components/single-selector';
|
||||||
import UserLink from '../user-link';
|
import { gettext, siteRoot } from '../../../utils/constants';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
|
||||||
const MemberItemPropTypes = {
|
const propTypes = {
|
||||||
groupID: PropTypes.string,
|
isItemFreezed: PropTypes.bool,
|
||||||
member: PropTypes.object.isRequired,
|
member: PropTypes.object,
|
||||||
isItemFreezed: PropTypes.bool.isRequired,
|
setMemberStaff: PropTypes.func,
|
||||||
onMemberChanged: PropTypes.func.isRequired,
|
deleteMember: PropTypes.func,
|
||||||
showDeleteMemberDialog: PropTypes.func.isRequired,
|
unfreezeItem: PropTypes.func,
|
||||||
toggleItemFreezed: PropTypes.func.isRequired,
|
freezeItem: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
class MemberItem extends React.Component {
|
class MemberItem extends React.Component {
|
||||||
@@ -21,7 +20,8 @@ class MemberItem extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
highlight: false
|
highlighted: false,
|
||||||
|
isItemMenuShow: false,
|
||||||
};
|
};
|
||||||
this.roleOptions = [
|
this.roleOptions = [
|
||||||
{ value: 'Admin', text: gettext('Admin'), isSelected: false },
|
{ value: 'Admin', text: gettext('Admin'), isSelected: false },
|
||||||
@@ -30,58 +30,119 @@ class MemberItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMouseEnter = () => {
|
onMouseEnter = () => {
|
||||||
if (this.props.isItemFreezed) return;
|
if (!this.props.isItemFreezed) {
|
||||||
this.setState({ highlight: true });
|
this.setState({ highlighted: true });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMouseLeave = () => {
|
onMouseLeave = () => {
|
||||||
if (this.props.isItemFreezed) return;
|
if (!this.props.isItemFreezed) {
|
||||||
this.setState({ highlight: false });
|
this.setState({ highlighted: false });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeUserRole = (roleOption) => {
|
setMemberStaff = (role) => {
|
||||||
let isAdmin = roleOption.value === 'Admin' ? true : false;
|
this.props.setMemberStaff(this.props.member.email, role.value === 'Admin');
|
||||||
systemAdminAPI.sysAdminUpdateGroupMemberRole(this.props.groupID, this.props.member.email, isAdmin).then((res) => {
|
};
|
||||||
this.props.onMemberChanged();
|
|
||||||
}).catch(error => {
|
deleteMember = () => {
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
const { member } = this.props;
|
||||||
toaster.danger(errMessage);
|
this.props.deleteMember(member.email);
|
||||||
});
|
};
|
||||||
|
|
||||||
|
toggleDropdownMenu = () => {
|
||||||
this.setState({
|
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() {
|
render() {
|
||||||
const member = this.props.member;
|
const { member } = this.props;
|
||||||
const highlight = this.state.highlight;
|
const { highlighted, isItemMenuShow, isDeleteMemberDialogOpen } = this.state;
|
||||||
if (member.role === 'Owner') return null;
|
|
||||||
this.roleOptions = this.roleOptions.map(item => {
|
this.roleOptions = this.roleOptions.map(item => {
|
||||||
item.isSelected = item.value == member.role;
|
item.isSelected = item.value == member.role;
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
const currentSelectedOption = this.roleOptions.filter(item => item.isSelected)[0];
|
const currentSelectedOption = this.roleOptions.filter(item => item.isSelected)[0];
|
||||||
|
|
||||||
return (
|
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>
|
<tr className={`departments-members-item ${highlighted ? 'tr-highlight' : ''}`} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
<td><UserLink email={member.email} name={member.name} /></td>
|
<td><img className="avatar" src={member.avatar_url} alt="" /></td>
|
||||||
<td>
|
<td className='text-truncate'>
|
||||||
<RoleSelector
|
<a href={`${siteRoot}sys/users/${encodeURIComponent(member.email)}/`}>{member.name}</a>
|
||||||
isDropdownToggleShown={highlight}
|
</td>
|
||||||
currentSelectedOption={currentSelectedOption}
|
<td>
|
||||||
options={this.roleOptions}
|
<RoleSelector
|
||||||
selectOption={this.onChangeUserRole}
|
isDropdownToggleShown={highlighted}
|
||||||
toggleItemFreezed={this.props.toggleItemFreezed}
|
currentSelectedOption={currentSelectedOption}
|
||||||
|
options={this.roleOptions}
|
||||||
|
selectOption={this.setMemberStaff}
|
||||||
|
toggleItemFreezed={this.toggleItemFreezed}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{member.contact_email}</td>
|
||||||
|
<td>
|
||||||
|
{highlighted &&
|
||||||
|
<Dropdown
|
||||||
|
isOpen={isItemMenuShow}
|
||||||
|
toggle={this.toggleDropdownMenu}
|
||||||
|
direction="down"
|
||||||
|
>
|
||||||
|
<DropdownToggle
|
||||||
|
tag='a'
|
||||||
|
role="button"
|
||||||
|
className='attr-action-icon sf3-font sf3-font-more-vertical'
|
||||||
|
title={gettext('More operations')}
|
||||||
|
aria-label={gettext('More operations')}
|
||||||
|
data-toggle="dropdown"
|
||||||
|
/>
|
||||||
|
<DropdownMenu right={true}>
|
||||||
|
<DropdownItem 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}
|
||||||
/>
|
/>
|
||||||
</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>
|
|
||||||
</tr>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberItem.propTypes = MemberItemPropTypes;
|
MemberItem.propTypes = propTypes;
|
||||||
|
|
||||||
export default MemberItem;
|
export default MemberItem;
|
||||||
|
@@ -34,15 +34,16 @@ class RepoItem extends React.Component {
|
|||||||
let iconUrl = Utils.getLibIconUrl(repo);
|
let iconUrl = Utils.getLibIconUrl(repo);
|
||||||
return (
|
return (
|
||||||
<tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
<tr className={highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
<td><img src={iconUrl} width="24" alt={gettext('icon')}/></td>
|
<td><img src={iconUrl} width="24" alt="" /></td>
|
||||||
{ enableSysAdminViewRepo ?
|
<td>
|
||||||
<td><a href={`${siteRoot}sys/libraries/${repo.repo_id}/${encodeURIComponent(repoName)}/`}>{repoName}</a></td>
|
{enableSysAdminViewRepo
|
||||||
:
|
? <a href={`${siteRoot}sys/libraries/${repo.repo_id}/${encodeURIComponent(repoName)}/`}>{repoName}</a>
|
||||||
<td>{repoName}</td>
|
: <span>{repoName}</span>
|
||||||
}
|
}
|
||||||
|
</td>
|
||||||
<td>{Utils.bytesToSize(repo.size)}</td>
|
<td>{Utils.bytesToSize(repo.size)}</td>
|
||||||
<td className="cursor-pointer text-center" onClick={this.props.showDeleteRepoDialog.bind(this, repo)}>
|
<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>
|
</td>
|
||||||
</tr>
|
</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 { gettext } from '../../../utils/constants';
|
||||||
import DepartmentNodeMenu from './departments-node-dropdown-menu';
|
import DepartmentNodeMenu from './departments-node-dropdown-menu';
|
||||||
|
|
||||||
const departmentsV2TreeNodePropTypes = {
|
const departmentsTreeNodePropTypes = {
|
||||||
node: PropTypes.object,
|
node: PropTypes.object,
|
||||||
checkedDepartmentId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
checkedDepartmentId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||||
listSubDepartments: PropTypes.func,
|
listSubDepartments: PropTypes.func,
|
||||||
@@ -17,7 +17,7 @@ const departmentsV2TreeNodePropTypes = {
|
|||||||
toggleDelete: PropTypes.func
|
toggleDelete: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
class DepartmentsV2TreeNode extends Component {
|
class DepartmentsTreeNode extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -74,7 +74,7 @@ class DepartmentsV2TreeNode extends Component {
|
|||||||
if (nodes.length > 0) {
|
if (nodes.length > 0) {
|
||||||
return nodes.map((node) => {
|
return nodes.map((node) => {
|
||||||
return (
|
return (
|
||||||
<DepartmentsV2TreeNode
|
<DepartmentsTreeNode
|
||||||
key={node.id}
|
key={node.id}
|
||||||
node={node}
|
node={node}
|
||||||
onChangeDepartment={this.props.onChangeDepartment}
|
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 GroupRepos from './groups/group-repos';
|
||||||
import GroupMembers from './groups/group-members';
|
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 ShareLinks from './links/share-links';
|
||||||
import UploadLinks from './links/upload-links';
|
import UploadLinks from './links/upload-links';
|
||||||
@@ -237,7 +237,7 @@ class SysAdmin extends React.Component {
|
|||||||
<SearchGroups path={siteRoot + 'sys/search-groups'} {...commonProps} />
|
<SearchGroups path={siteRoot + 'sys/search-groups'} {...commonProps} />
|
||||||
<GroupRepos path={siteRoot + 'sys/groups/:groupID/libraries'} {...commonProps} />
|
<GroupRepos path={siteRoot + 'sys/groups/:groupID/libraries'} {...commonProps} />
|
||||||
<GroupMembers path={siteRoot + 'sys/groups/:groupID/members'} {...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} />
|
<ShareLinks path={siteRoot + 'sys/share-links'} {...commonProps} />
|
||||||
<UploadLinks path={siteRoot + 'sys/upload-links'} {...commonProps} />
|
<UploadLinks path={siteRoot + 'sys/upload-links'} {...commonProps} />
|
||||||
<Orgs path={siteRoot + 'sys/organizations'} {...commonProps} />
|
<Orgs path={siteRoot + 'sys/organizations'} {...commonProps} />
|
||||||
|
Reference in New Issue
Block a user