1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-27 07:44:50 +00:00

Depts members select users (#7829)

* ['departments, members' dialog] added 'search users'(search users and list, select/unselct, select all users found)

* ['departments, members' dialog] added 'unselect all'

* ['departments, members' dialog] updated 'select/unselect all'
This commit is contained in:
llj
2025-05-19 10:48:12 +08:00
committed by GitHub
parent 5283335822
commit 28d4495347
4 changed files with 153 additions and 55 deletions

View File

@@ -1,6 +1,6 @@
import React, { Fragment, } from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalBody } from 'reactstrap';
import { Modal, ModalBody, Input } from 'reactstrap';
import { gettext, isOrgContext, username } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api.js';
import { Utils } from '../../utils/utils';
@@ -38,6 +38,9 @@ class DepartmentDetailDialog extends React.Component {
membersLoading: true,
selectedMemberMap: {},
departmentsTree: [],
keyword: '',
searching: false,
usersFound: []
};
}
@@ -127,15 +130,13 @@ class DepartmentDetailDialog extends React.Component {
};
onMemberChecked = (member) => {
if (this.state.departmentMembers.indexOf(member) !== -1) {
let newMembersTempObj = this.state.newMembersTempObj;
let { newMembersTempObj } = this.state;
if (member.email in newMembersTempObj) {
delete newMembersTempObj[member.email];
} else {
newMembersTempObj[member.email] = member;
}
this.setState({ newMembersTempObj: newMembersTempObj });
}
};
addGroupMember = () => {
@@ -162,7 +163,10 @@ class DepartmentDetailDialog extends React.Component {
this.setState({ currentDepartment: department });
};
selectAll = (members) => {
selectAll = () => {
const { keyword, departmentMembers, usersFound } = this.state;
const members = keyword ? usersFound : departmentMembers; // 'members': to be compatible with the old code
let { newMembersTempObj, selectedMemberMap } = this.state;
for (let member of members) {
if (Object.keys(selectedMemberMap).indexOf(member.email) !== -1) {
@@ -173,13 +177,58 @@ class DepartmentDetailDialog extends React.Component {
this.setState({ newMembersTempObj: newMembersTempObj });
};
unselectAll = () => {
const { keyword, departmentMembers, usersFound } = this.state;
const members = keyword ? usersFound : departmentMembers; // 'members': to be compatible with the old code
let { newMembersTempObj, selectedMemberMap } = this.state;
for (let member of members) {
if (Object.keys(selectedMemberMap).indexOf(member.email) !== -1) {
continue;
}
if (member.email in newMembersTempObj) {
delete newMembersTempObj[member.email];
}
}
this.setState({ newMembersTempObj: newMembersTempObj });
};
onKeywordChanged = (e) => {
this.setState({ keyword: e.target.value }, () => {
const { keyword } = this.state;
const q = keyword.trim();
if (!q) {
return false;
}
this.setState({ searching: true });
seafileAPI.searchUsers(q).then((res) => {
this.setState({
searching: false,
usersFound: res.data.users
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
this.setState({
searching: false,
});
});
});
};
clearKeyword = () => {
this.setState({ keyword: '' });
};
renderHeader = () => {
const title = this.props.usedFor === 'add_group_member' ? gettext('Select group members') : gettext('Select shared users');
return <SeahubModalHeader toggle={this.toggle}>{title}</SeahubModalHeader>;
};
render() {
let { departmentsLoading, departments } = this.state;
const { departmentsLoading, departments, keyword } = this.state;
if (departmentsLoading) {
return (
<Modal isOpen={true} toggle={this.toggle}>
@@ -206,6 +255,28 @@ class DepartmentDetailDialog extends React.Component {
<Modal isOpen={true} toggle={this.toggle} className="department-dialog" style={{ maxWidth: '900px' }}>
{this.renderHeader()}
<ModalBody className="department-dialog-content">
<div className="department-dialog-left-panel">
<div className="mb-2 position-relative">
<i className="sf3-font sf3-font-search input-icon-addon"></i>
<Input
bsSize="sm"
className="px-6"
type="text"
value={keyword}
onChange={this.onKeywordChanged}
placeholder={gettext('Search users')}
/>
{keyword &&
<span className="input-icon-addon pe-auto">
<i
className="sf3-font sf3-font-x-01 clear-keyword-icon"
onClick={this.clearKeyword}
>
</i>
</span>
}
</div>
{!keyword &&
<DepartmentGroup
departments={this.state.departments}
getMembers={this.getMembers}
@@ -214,12 +285,18 @@ class DepartmentDetailDialog extends React.Component {
loading={this.state.departmentsLoading}
departmentsTree={this.state.departmentsTree}
/>
}
</div>
<DepartmentGroupMembers
keyword={this.state.keyword}
searching={this.state.searching}
usersFound={this.state.usersFound}
members={this.state.departmentMembers}
memberSelected={this.state.newMembersTempObj}
onUserChecked={this.onMemberChecked}
currentDepartment={this.state.currentDepartment}
selectAll={this.selectAll}
unselectAll={this.unselectAll}
loading={this.state.membersLoading}
selectedMemberMap={this.state.selectedMemberMap}
isLoadingMore={this.state.isLoadingMore}

View File

@@ -94,21 +94,20 @@ const DepartmentGroupMembersPropTypes = {
currentDepartment: PropTypes.object.isRequired,
selectedMemberMap: PropTypes.object,
selectAll: PropTypes.func.isRequired,
unselectAll: PropTypes.func.isRequired,
loading: PropTypes.bool,
usedFor: PropTypes.oneOf(['add_group_member', 'add_user_share']),
keyword: PropTypes.string,
searching: PropTypes.bool,
usersFound: PropTypes.array
};
class DepartmentGroupMembers extends Component {
selectAll = () => {
const { members } = this.props;
this.props.selectAll(members);
};
render() {
const { members, memberSelected, loading, selectedMemberMap, currentDepartment, usedFor } = this.props;
const { members, memberSelected, loading, selectedMemberMap, currentDepartment, usedFor, keyword, searching, usersFound } = this.props;
let headerTitle = (currentDepartment.name || '') + ' ' + gettext('members');
if (loading) {
if (loading || searching) {
return (
<div className="department-dialog-member pt-4">
<div className="w-100">
@@ -119,26 +118,35 @@ class DepartmentGroupMembers extends Component {
</div>
);
}
const enableSelectAll = Object.keys(memberSelected).length < members.length;
const itemsShown = keyword ? usersFound : members;
const unselectedItems = itemsShown.filter(item => !(item.email in memberSelected) && !selectedMemberMap[item.email]);
const addedItems = itemsShown.filter(item => selectedMemberMap[item.email]);
const tip = usedFor === 'add_group_member' ? gettext('User is already in this group') : gettext('It is already shared to user');
return (
<div className="department-dialog-member pt-4">
<div className="w-100">
<div className='department-dialog-member-head px-4'>
<div className='department-name'>
{headerTitle}
{keyword ? gettext('Search results') : headerTitle}
</div>
{enableSelectAll ?
<div className='select-all' onClick={this.selectAll}>{gettext('Select All')}</div>
:
<div className='select-all-disable'>{gettext('Select All')}</div>
{itemsShown.length > 0 && (
<>
{unselectedItems.length > 0
? <div className='select-all' onClick={this.props.selectAll}>{gettext('Select all')}</div>
: itemsShown.length > addedItems.length
? <div className='select-all' onClick={this.props.unselectAll}>{gettext('Unselect all')}</div>
: ''
}
</>
)}
</div>
{members.length > 0 ?
{itemsShown.length > 0 ?
<Fragment>
<table className="department-dialog-member-table">
<tbody>
{members.map((member, index) => {
{itemsShown.map((member, index) => {
return (
<Item
key={index}
@@ -156,7 +164,7 @@ class DepartmentGroupMembers extends Component {
</Fragment>
:
<EmptyTip tipSrc={`${mediaUrl}img/no-users-tip.png`}>
<h2>{gettext('No members')}</h2>
<h2>{keyword ? gettext('No users found') : gettext('No members')}</h2>
</EmptyTip>
}
</div>

View File

@@ -115,7 +115,6 @@ class DepartmentGroup extends Component {
}
return (
<div className="department-dialog-group">
<div>
{departments.length > 0 && departments.map((department, index) => {
if (department.parent_group_id !== -1) return null;
return (
@@ -133,7 +132,6 @@ class DepartmentGroup extends Component {
);
})}
</div>
</div>
);
}
}

View File

@@ -14,12 +14,27 @@
overflow-y: auto;
}
.department-dialog-content .department-dialog-group {
.department-dialog-content .department-dialog-left-panel {
flex: 0 0 30%;
padding: 1rem;
border-right: 1px solid #eee;
}
.department-dialog-content .department-dialog-left-panel .input-icon-addon {
min-width: 2rem;
}
.department-dialog-content .department-dialog-left-panel .clear-keyword-icon {
line-height: 1;
padding: 2px;
border-radius: 3px;
cursor: pointer;
}
.department-dialog-content .department-dialog-left-panel .clear-keyword-icon:hover {
background: #efefef;
}
.department-dialog-content .department-dialog-member {
display: flex;
flex: 0 0 35%;