1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-27 15:54:39 +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 React, { Fragment, } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Modal, ModalBody } from 'reactstrap'; import { Modal, ModalBody, Input } from 'reactstrap';
import { gettext, isOrgContext, username } from '../../utils/constants'; import { gettext, isOrgContext, username } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api.js'; import { seafileAPI } from '../../utils/seafile-api.js';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
@@ -38,6 +38,9 @@ class DepartmentDetailDialog extends React.Component {
membersLoading: true, membersLoading: true,
selectedMemberMap: {}, selectedMemberMap: {},
departmentsTree: [], departmentsTree: [],
keyword: '',
searching: false,
usersFound: []
}; };
} }
@@ -127,15 +130,13 @@ class DepartmentDetailDialog extends React.Component {
}; };
onMemberChecked = (member) => { onMemberChecked = (member) => {
if (this.state.departmentMembers.indexOf(member) !== -1) { let { newMembersTempObj } = this.state;
let newMembersTempObj = this.state.newMembersTempObj; if (member.email in newMembersTempObj) {
if (member.email in newMembersTempObj) { delete newMembersTempObj[member.email];
delete newMembersTempObj[member.email]; } else {
} else { newMembersTempObj[member.email] = member;
newMembersTempObj[member.email] = member;
}
this.setState({ newMembersTempObj: newMembersTempObj });
} }
this.setState({ newMembersTempObj: newMembersTempObj });
}; };
addGroupMember = () => { addGroupMember = () => {
@@ -162,7 +163,10 @@ class DepartmentDetailDialog extends React.Component {
this.setState({ currentDepartment: department }); 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; let { newMembersTempObj, selectedMemberMap } = this.state;
for (let member of members) { for (let member of members) {
if (Object.keys(selectedMemberMap).indexOf(member.email) !== -1) { if (Object.keys(selectedMemberMap).indexOf(member.email) !== -1) {
@@ -173,13 +177,58 @@ class DepartmentDetailDialog extends React.Component {
this.setState({ newMembersTempObj: newMembersTempObj }); 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 = () => { renderHeader = () => {
const title = this.props.usedFor === 'add_group_member' ? gettext('Select group members') : gettext('Select shared users'); const title = this.props.usedFor === 'add_group_member' ? gettext('Select group members') : gettext('Select shared users');
return <SeahubModalHeader toggle={this.toggle}>{title}</SeahubModalHeader>; return <SeahubModalHeader toggle={this.toggle}>{title}</SeahubModalHeader>;
}; };
render() { render() {
let { departmentsLoading, departments } = this.state; const { departmentsLoading, departments, keyword } = this.state;
if (departmentsLoading) { if (departmentsLoading) {
return ( return (
<Modal isOpen={true} toggle={this.toggle}> <Modal isOpen={true} toggle={this.toggle}>
@@ -206,20 +255,48 @@ class DepartmentDetailDialog extends React.Component {
<Modal isOpen={true} toggle={this.toggle} className="department-dialog" style={{ maxWidth: '900px' }}> <Modal isOpen={true} toggle={this.toggle} className="department-dialog" style={{ maxWidth: '900px' }}>
{this.renderHeader()} {this.renderHeader()}
<ModalBody className="department-dialog-content"> <ModalBody className="department-dialog-content">
<DepartmentGroup <div className="department-dialog-left-panel">
departments={this.state.departments} <div className="mb-2 position-relative">
getMembers={this.getMembers} <i className="sf3-font sf3-font-search input-icon-addon"></i>
setCurrent={this.setCurrent} <Input
currentDepartment={this.state.currentDepartment} bsSize="sm"
loading={this.state.departmentsLoading} className="px-6"
departmentsTree={this.state.departmentsTree} 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}
setCurrent={this.setCurrent}
currentDepartment={this.state.currentDepartment}
loading={this.state.departmentsLoading}
departmentsTree={this.state.departmentsTree}
/>
}
</div>
<DepartmentGroupMembers <DepartmentGroupMembers
keyword={this.state.keyword}
searching={this.state.searching}
usersFound={this.state.usersFound}
members={this.state.departmentMembers} members={this.state.departmentMembers}
memberSelected={this.state.newMembersTempObj} memberSelected={this.state.newMembersTempObj}
onUserChecked={this.onMemberChecked} onUserChecked={this.onMemberChecked}
currentDepartment={this.state.currentDepartment} currentDepartment={this.state.currentDepartment}
selectAll={this.selectAll} selectAll={this.selectAll}
unselectAll={this.unselectAll}
loading={this.state.membersLoading} loading={this.state.membersLoading}
selectedMemberMap={this.state.selectedMemberMap} selectedMemberMap={this.state.selectedMemberMap}
isLoadingMore={this.state.isLoadingMore} isLoadingMore={this.state.isLoadingMore}

View File

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

View File

@@ -115,24 +115,22 @@ class DepartmentGroup extends Component {
} }
return ( return (
<div className="department-dialog-group"> <div className="department-dialog-group">
<div> {departments.length > 0 && departments.map((department, index) => {
{departments.length > 0 && departments.map((department, index) => { if (department.parent_group_id !== -1) return null;
if (department.parent_group_id !== -1) return null; return (
return ( <Item
<Item key={department.id}
key={department.id} department={department}
department={department} departments={departments}
departments={departments} getMembers={this.getMembers}
getMembers={this.getMembers} setCurrent={this.props.setCurrent}
setCurrent={this.props.setCurrent} toggleExpanded={this.toggleExpanded}
toggleExpanded={this.toggleExpanded} currentDepartment={this.props.currentDepartment}
currentDepartment={this.props.currentDepartment} allMembersClick={this.state.allMembersClick}
allMembersClick={this.state.allMembersClick} padding={10}
padding={10} />
/> );
); })}
})}
</div>
</div> </div>
); );
} }

View File

@@ -14,12 +14,27 @@
overflow-y: auto; overflow-y: auto;
} }
.department-dialog-content .department-dialog-group { .department-dialog-content .department-dialog-left-panel {
flex: 0 0 30%; flex: 0 0 30%;
padding: 1rem; padding: 1rem;
border-right: 1px solid #eee; 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 { .department-dialog-content .department-dialog-member {
display: flex; display: flex;
flex: 0 0 35%; flex: 0 0 35%;