1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-17 15:53:28 +00:00

Change department UI (#7063)

* 01 change department member more icon

* 02 when no contact email return None

* 03 delete menu

* 04 move into member high change

* 05 current department support more operations

* 06 change comment

* add username_as_email is true

* update-get-group-members-info

* Revert "06 change comment"

This reverts commit 14800df3fd.

---------

Co-authored-by: r350178982 <32759763+r350178982@users.noreply.github.com>
This commit is contained in:
Michael An
2024-11-20 10:21:32 +08:00
committed by GitHub
parent a004e65b10
commit 5f6de21b19
8 changed files with 149 additions and 85 deletions

View File

@@ -128,4 +128,6 @@
color: #21bc2e; color: #21bc2e;
} }
/* 顶部的样式:背景色和边界去掉,和 seatable 保持一致 */ .departments-members-item {
height: 58px;
}

View File

@@ -0,0 +1,44 @@
import React from 'react';
import PropTypes from 'prop-types';
import { DropdownItem, DropdownMenu } from 'reactstrap';
import { gettext } from '../../../utils/constants';
function DepartmentNodeMenu({ node, toggleDelete, toggleRename, toggleAddMembers, toggleAddDepartment, toggleAddLibrary }) {
return (
<DropdownMenu
right={true}
modifiers={{ preventOverflow: { boundariesElement: document.body } }}
positionFixed={true}
>
<DropdownItem key={`${node.id}-add-department`} onClick={() => toggleAddDepartment(node)}>
{gettext('Add sub-department')}
</DropdownItem>
<DropdownItem key={`${node.id}-add-repo`} onClick={() => toggleAddLibrary(node)}>
{gettext('Add Library')}
</DropdownItem>
<DropdownItem key={`${node.id}-add-members`} onClick={() => toggleAddMembers(node)}>
{gettext('Add members')}
</DropdownItem>
<DropdownItem key={`${node.id}-rename`} onClick={() => toggleRename(node)}>
{gettext('Rename')}
</DropdownItem>
<DropdownItem key={`${node.id}-delete`} onClick={() => toggleDelete(node)}>
{gettext('Delete')}
</DropdownItem>
<DropdownItem key={`${node.id}-id`} disabled={true}>
{`${gettext('Department ID')} : ${node.id}`}
</DropdownItem>
</DropdownMenu>
);
}
DepartmentNodeMenu.propTypes = {
node: PropTypes.object.isRequired,
toggleDelete: PropTypes.func.isRequired,
toggleRename: PropTypes.func.isRequired,
toggleAddMembers: PropTypes.func.isRequired,
toggleAddDepartment: PropTypes.func.isRequired,
toggleAddLibrary: PropTypes.func.isRequired,
};
export default DepartmentNodeMenu;

View File

@@ -78,7 +78,7 @@ class DepartmentsV2MembersItem extends React.Component {
const option = options.find(item => item.value === currentRole) || {}; const option = options.find(item => item.value === currentRole) || {};
return ( return (
<tr key={member.email} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> <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><img className="avatar" src={member.avatar_url} alt=""></img></td>
<td className='text-truncate'> <td className='text-truncate'>
<Link to={`${siteRoot}sys/users/${encodeURIComponent(member.email)}/`}>{member.name}</Link> <Link to={`${siteRoot}sys/users/${encodeURIComponent(member.email)}/`}>{member.name}</Link>
@@ -103,12 +103,12 @@ class DepartmentsV2MembersItem extends React.Component {
<DropdownToggle <DropdownToggle
tag='a' tag='a'
role="button" role="button"
className='attr-action-icon sf3-font sf3-font-more' className='attr-action-icon sf3-font sf3-font-more-vertical'
title={gettext('More operations')} title={gettext('More operations')}
aria-label={gettext('More operations')} aria-label={gettext('More operations')}
data-toggle="dropdown" data-toggle="dropdown"
/> />
<DropdownMenu className="dtable-dropdown-menu dropdown-menu mt-2 mr-2" right={true}> <DropdownMenu right={true}>
<DropdownItem key='delete' onClick={this.deleteMember}>{gettext('Delete')}</DropdownItem> <DropdownItem key='delete' onClick={this.deleteMember}>{gettext('Delete')}</DropdownItem>
</DropdownMenu> </DropdownMenu>
</Dropdown> </Dropdown>

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Table } from 'reactstrap'; import { Table, Dropdown, DropdownToggle } from 'reactstrap';
import Loading from '../../../components/loading'; import Loading from '../../../components/loading';
import EmptyTip from '../../../components/empty-tip'; import EmptyTip from '../../../components/empty-tip';
import { gettext } from '../../../utils/constants'; import { gettext } from '../../../utils/constants';
@@ -8,6 +8,7 @@ import DepartmentsV2MembersItem from './departments-v2-members-item';
import RepoItem from '../departments/repo-item'; import RepoItem from '../departments/repo-item';
import ModalPortal from '../../../components/modal-portal'; import ModalPortal from '../../../components/modal-portal';
import DeleteRepoDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-repo-dialog'; import DeleteRepoDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-delete-repo-dialog';
import DepartmentNodeMenu from './departments-node-dropdown-menu';
const propTypes = { const propTypes = {
rootNodes: PropTypes.array, rootNodes: PropTypes.array,
@@ -32,6 +33,7 @@ class DepartmentsV2MembersList extends React.Component {
repos: [], repos: [],
deletedRepo: {}, deletedRepo: {},
showDeleteRepoDialog: false, showDeleteRepoDialog: false,
dropdownOpen: false,
}; };
} }
@@ -75,20 +77,20 @@ class DepartmentsV2MembersList extends React.Component {
this.setState({ isItemFreezed: !this.state.isItemFreezed }); this.setState({ isItemFreezed: !this.state.isItemFreezed });
}; };
getDepartmentName = () => { getCurrentDepartment = () => {
const { rootNodes, checkedDepartmentId } = this.props; const { rootNodes, checkedDepartmentId } = this.props;
if (!rootNodes) return ''; if (!rootNodes) return {};
let name = ''; let currentDepartment = {};
let arr = [...rootNodes]; let arr = [...rootNodes];
while (!name && arr.length > 0) { while (!currentDepartment.id && arr.length > 0) {
let curr = arr.shift(); let curr = arr.shift();
if (curr.id === checkedDepartmentId) { if (curr.id === checkedDepartmentId) {
name = curr.name; currentDepartment = curr;
} else if (curr.children && curr.children.length > 0) { } else if (curr.children && curr.children.length > 0) {
arr.push(...curr.children); arr.push(...curr.children);
} }
} }
return name; return currentDepartment;
}; };
sortByName = (e) => { sortByName = (e) => {
@@ -117,16 +119,47 @@ class DepartmentsV2MembersList extends React.Component {
}); });
}; };
dropdownToggle = (e) => {
e.stopPropagation();
this.setState({ dropdownOpen: !this.state.dropdownOpen });
};
render() { render() {
const { activeNav, repos } = this.state; const { activeNav, repos } = this.state;
const { membersList, isMembersListLoading, sortBy, sortOrder } = this.props; const { membersList, isMembersListLoading, sortBy, sortOrder } = this.props;
const sortByName = sortBy === 'name'; const sortByName = sortBy === 'name';
const sortByRole = sortBy === 'role'; const sortByRole = sortBy === 'role';
const sortIcon = <span className={`sort-dirent sf3-font sf3-font-down ${sortOrder === 'asc' ? 'rotate-180' : ''}`}></span>; const sortIcon = <span className={`sort-dirent sf3-font sf3-font-down ${sortOrder === 'asc' ? 'rotate-180' : ''}`}></span>;
const currentDepartment = this.getCurrentDepartment();
return ( return (
<div className="department-content-main"> <div className="department-content-main">
<div className="department-content-main-name">{this.getDepartmentName()}</div> <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"> <div className="cur-view-path tab-nav-container">
<ul className="nav"> <ul className="nav">

View File

@@ -1,8 +1,9 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap'; import { Dropdown, DropdownToggle } from 'reactstrap';
import { gettext } from '../../../utils/constants'; import { gettext } from '../../../utils/constants';
import DepartmentNodeMenu from './departments-node-dropdown-menu';
const departmentsV2TreeNodePropTypes = { const departmentsV2TreeNodePropTypes = {
node: PropTypes.object, node: PropTypes.object,
@@ -105,22 +106,6 @@ class DepartmentsV2TreeNode extends Component {
} }
}; };
toggleAddDepartment = (node) => {
this.props.toggleAddDepartment(node);
};
toggleAddMembers = (node) => {
this.props.toggleAddMembers(node);
};
toggleRename = (node) => {
this.props.toggleRename(node);
};
toggleDelete = (node) => {
this.props.toggleDelete(node);
};
render() { render() {
const { node, checkedDepartmentId } = this.props; const { node, checkedDepartmentId } = this.props;
const { isChildrenShow, dropdownOpen, active } = this.state; const { isChildrenShow, dropdownOpen, active } = this.state;
@@ -144,7 +129,7 @@ class DepartmentsV2TreeNode extends Component {
<span style={{ width: 24 }}></span> <span style={{ width: 24 }}></span>
} }
<span className="departments-v2-tree-node-text text-truncate">{node.name}</span> <span className="departments-v2-tree-node-text text-truncate">{node.name}</span>
{active && node.id !== 'other_users' && {active &&
<Dropdown <Dropdown
isOpen={dropdownOpen} isOpen={dropdownOpen}
toggle={(e) => this.dropdownToggle(e)} toggle={(e) => this.dropdownToggle(e)}
@@ -159,55 +144,16 @@ class DepartmentsV2TreeNode extends Component {
aria-label={gettext('More operations')} aria-label={gettext('More operations')}
data-toggle="dropdown" data-toggle="dropdown"
> >
<i className="sf3-font sf3-font-more"></i> <i className="sf3-font sf3-font-more mr-1"></i>
</DropdownToggle> </DropdownToggle>
<DropdownMenu <DepartmentNodeMenu
className="dtable-dropdown-menu dropdown-menu drop-list" node={node}
right={true} toggleAddDepartment={this.props.toggleAddDepartment}
modifiers={{ preventOverflow: { boundariesElement: document.body } }} toggleAddLibrary={this.props.toggleAddLibrary}
positionFixed={true} toggleAddMembers={this.props.toggleAddMembers}
> toggleRename={this.props.toggleRename}
<DropdownItem toggleDelete={this.props.toggleDelete}
key={`${node.id}-add-department`} />
onClick={this.toggleAddDepartment.bind(this, node)}
>
{gettext('Add sub-department')}
</DropdownItem>
<DropdownItem
key={`${node.id}-add-repo`}
onClick={this.props.toggleAddLibrary.bind(this, node)}
>
{gettext('Add Library')}
</DropdownItem>
{node.id !== -1 && (
<Fragment>
<DropdownItem
key={`${node.id}-add-members`}
onClick={this.toggleAddMembers.bind(this, node)}
>
{gettext('Add members')}
</DropdownItem>
<DropdownItem
key={`${node.id}-rename`}
onClick={this.toggleRename.bind(this, node)}
>
{gettext('Rename')}
</DropdownItem>
<DropdownItem
key={`${node.id}-delete`}
onClick={this.toggleDelete.bind(this, node)}
>
{gettext('Delete')}
</DropdownItem>
<DropdownItem
key={`${node.id}-id`}
disabled={true}
>
{`${gettext('Department ID')} : ${node.id}`}
</DropdownItem>
</Fragment>
)}
</DropdownMenu>
</Dropdown> </Dropdown>
} }
</div> </div>

View File

@@ -336,6 +336,11 @@ class DepartmentsV2 extends React.Component {
getRepos={this.getRepos} getRepos={this.getRepos}
deleteGroup={this.deleteGroup} deleteGroup={this.deleteGroup}
createGroup={this.createGroup} createGroup={this.createGroup}
toggleAddDepartment={this.toggleAddDepartment}
toggleAddLibrary={this.toggleAddLibrary}
toggleAddMembers={this.toggleAddMembers}
toggleRename={this.toggleRename}
toggleDelete={this.toggleDelete}
/> />
</> </>
} }

View File

@@ -9,7 +9,7 @@ from rest_framework import status
from seaserv import seafile_api, ccnet_api from seaserv import seafile_api, ccnet_api
from seahub.group.utils import get_group_member_info, is_group_member from seahub.group.utils import get_group_member_info, is_group_member, get_group_members_info
from seahub.group.signals import add_user_to_group from seahub.group.signals import add_user_to_group
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
from seahub.base.accounts import User from seahub.base.accounts import User
@@ -67,15 +67,13 @@ class AdminGroupMembers(APIView):
else: else:
has_next_page = False has_next_page = False
group_members_info = []
for m in members:
member_info = get_group_member_info(request, group_id, m.user_name)
group_members_info.append(member_info)
member_usernames = [m.user_name for m in members]
members_info = get_group_members_info(group_id, member_usernames)
group_members = { group_members = {
'group_id': group_id, 'group_id': group_id,
'group_name': group.group_name, 'group_name': group.group_name,
'members': group_members_info, 'members': members_info,
'page_info': { 'page_info': {
'has_next_page': has_next_page, 'has_next_page': has_next_page,
'current_page': page 'current_page': page

View File

@@ -118,6 +118,42 @@ def get_group_member_info(request, group_id, email):
return member_info return member_info
def get_group_members_info(group_id, emails):
member_profiles = Profile.objects.filter(user__in=emails)
username_profile_map = {p.user : p for p in member_profiles}
members_info_list = []
for email in emails:
p = username_profile_map.get(email) or None
if p:
login_id = p.login_id if p.login_id else ''
contact_email = p.contact_email
else:
login_id = ''
contact_email = ''
avatar_url, _, _ = api_avatar_url(email)
role = 'Member'
group = ccnet_api.get_group(int(group_id))
is_admin = bool(ccnet_api.check_group_staff(int(group_id), email))
if email == group.creator_name:
role = 'Owner'
elif is_admin:
role = 'Admin'
member_info = {
'group_id': group_id,
"name": email2nickname(email),
'email': email,
"contact_email": contact_email,
"login_id": login_id,
"avatar_url": avatar_url,
"is_admin": is_admin,
"role": role,
}
members_info_list.append(member_info)
return members_info_list
GROUP_ID_CACHE_PREFIX = "GROUP_ID_" GROUP_ID_CACHE_PREFIX = "GROUP_ID_"
GROUP_ID_CACHE_TIMEOUT = 24 * 60 * 60 GROUP_ID_CACHE_TIMEOUT = 24 * 60 * 60