From 597d22c4030071ed4895d15f736df59897b1f7d1 Mon Sep 17 00:00:00 2001 From: Michael An <2331806369@qq.com> Date: Mon, 1 Apr 2019 13:38:25 +0800 Subject: [PATCH] Org department (#3194) * add org department * delete hash * update * update * render repo icon * update dialogs --- .../components/dialog/add-reviewer-dialog.js | 2 +- .../dialog/org-add-department-dialog.js | 92 +++++ .../dialog/org-add-member-dialog.js | 73 ++++ .../components/dialog/org-add-repo-dialog.js | 87 ++++ .../components/dialog/org-add-user-dialog.js | 4 +- .../dialog/org-delete-department-dialog.js | 55 +++ .../dialog/org-delete-member-dialog.js | 50 +++ .../dialog/org-delete-repo-dialog.js | 49 +++ .../dialog/org-set-group-quota-dialog.js | 80 ++++ frontend/src/css/org-department-item.css | 28 ++ frontend/src/pages/org-admin/index.js | 15 + .../pages/org-admin/org-department-item.js | 372 ++++++++++++++++++ .../pages/org-admin/org-departments-list.js | 199 ++++++++++ .../src/pages/org-admin/org-departments.js | 20 + frontend/src/pages/org-admin/org-groups.js | 2 +- frontend/src/pages/org-admin/org-links.js | 6 +- frontend/src/pages/org-admin/side-panel.js | 4 +- 17 files changed, 1129 insertions(+), 9 deletions(-) create mode 100644 frontend/src/components/dialog/org-add-department-dialog.js create mode 100644 frontend/src/components/dialog/org-add-member-dialog.js create mode 100644 frontend/src/components/dialog/org-add-repo-dialog.js create mode 100644 frontend/src/components/dialog/org-delete-department-dialog.js create mode 100644 frontend/src/components/dialog/org-delete-member-dialog.js create mode 100644 frontend/src/components/dialog/org-delete-repo-dialog.js create mode 100644 frontend/src/components/dialog/org-set-group-quota-dialog.js create mode 100644 frontend/src/css/org-department-item.css create mode 100644 frontend/src/pages/org-admin/org-department-item.js create mode 100644 frontend/src/pages/org-admin/org-departments-list.js create mode 100644 frontend/src/pages/org-admin/org-departments.js diff --git a/frontend/src/components/dialog/add-reviewer-dialog.js b/frontend/src/components/dialog/add-reviewer-dialog.js index 027e358102..afcfeeac5f 100644 --- a/frontend/src/components/dialog/add-reviewer-dialog.js +++ b/frontend/src/components/dialog/add-reviewer-dialog.js @@ -98,7 +98,7 @@ class AddReviewerDialog extends React.Component {

{gettext('Add new reviewer')}

{ + let isValid = this.validateName(); + if (isValid) { + let parentGroup = -1; + if (this.props.parentGroupID) { + parentGroup = this.props.parentGroupID; + } + seafileAPI.orgAdminAddDepartGroup(orgID, parentGroup, this.state.departName.trim()).then((res) => { + this.props.toggle(); + this.props.onDepartChanged(); + }).catch(error => { + let errorMsg = gettext(error.response.data.error_msg); + this.setState({ errMessage: errorMsg }); + }); + } + } + + validateName = () => { + let errMessage = ''; + const name = this.state.departName.trim(); + if (!name.length) { + errMessage = gettext('Name is required'); + this.setState({ errMessage: errMessage }); + return false; + } + return true; + } + + handleChange = (e) => { + this.setState({ + departName: e.target.value, + }); + } + + handleKeyPress = (e) => { + if (e.key === 'Enter') { + this.handleSubmit(); + e.preventDefault(); + } + } + + render() { + let header = this.props.parentGroupID ? gettext('New Sub-department') : gettext('New Department'); + return ( + + {header} + +
+ + + + +
+ { this.state.errMessage &&

{this.state.errMessage}

} +
+ + + +
+ ); + } +} + +AddDepartDialog.propTypes = propTypes; + +export default AddDepartDialog; diff --git a/frontend/src/components/dialog/org-add-member-dialog.js b/frontend/src/components/dialog/org-add-member-dialog.js new file mode 100644 index 0000000000..136d6faa6d --- /dev/null +++ b/frontend/src/components/dialog/org-add-member-dialog.js @@ -0,0 +1,73 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; +import { gettext, orgID } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; +import UserSelect from '../user-select.js'; + +const propTypes = { + toggle: PropTypes.func.isRequired, + onMemberChanged: PropTypes.func.isRequired +}; + +class AddMemberDialog extends React.Component { + + constructor(props) { + super(props); + this.state = { + selectedOption: null, + errMessage: '', + }; + this.Options = []; + } + + handleSelectChange = (option) => { + this.setState({ selectedOption: option }); + this.Options = []; + } + + handleSubmit = () => { + if (!this.state.selectedOption) return; + const email = this.state.selectedOption.email; + this.refs.orgSelect.clearSelect(); + this.setState({ errorMsg: [] }); + seafileAPI.orgAdminAddDepartGroupUser(orgID, this.props.groupID, email).then((res) => { + if (res.data.failed) { + this.setState({ errorMsg: res.data.failed[0] }); + } + this.setState({ + selectedOption: null, + }); + if (res.data.success) { + this.props.onMemberChanged(); + this.props.toggle(); + } + }); + } + + render() { + return ( + + {gettext('Add Member')} + + + { this.state.errMessage &&

{this.state.errMessage}

} +
+ + + + +
+ ); + } +} + +AddMemberDialog.propTypes = propTypes; + +export default AddMemberDialog; diff --git a/frontend/src/components/dialog/org-add-repo-dialog.js b/frontend/src/components/dialog/org-add-repo-dialog.js new file mode 100644 index 0000000000..124748d720 --- /dev/null +++ b/frontend/src/components/dialog/org-add-repo-dialog.js @@ -0,0 +1,87 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Input, Form, FormGroup, Label } from 'reactstrap'; +import { gettext, orgID } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; + +const propTypes = { + toggle: PropTypes.func.isRequired, + onRepoChanged: PropTypes.func.isRequired, +}; + +class AddRepoDialog extends React.Component { + + constructor(props) { + super(props); + this.state = { + repoName: '', + errMessage: '', + }; + } + + handleSubmit = () => { + let isValid = this.validateName(); + if (isValid) { + seafileAPI.orgAdminAddDepartGroupRepo(orgID, this.props.groupID, this.state.repoName.trim()).then((res) => { + this.props.toggle(); + this.props.onRepoChanged(); + }).catch(error => { + let errorMsg = gettext(error.response.data.error_msg); + this.setState({ errMessage: errorMsg }); + }); + } + } + + validateName = () => { + let errMessage = ''; + const name = this.state.repoName.trim(); + if (!name.length) { + errMessage = gettext('Name is required'); + this.setState({ errMessage: errMessage }); + return false; + } + return true; + } + + handleChange = (e) => { + this.setState({ + repoName: e.target.value, + }); + } + + handleKeyPress = (e) => { + if (e.key === 'Enter') { + this.handleSubmit(); + e.preventDefault(); + } + } + + render() { + return ( + + {gettext('New Library')} + +
+ + + + +
+ { this.state.errMessage &&

{this.state.errMessage}

} +
+ + + +
+ ); + } +} + +AddRepoDialog.propTypes = propTypes; + +export default AddRepoDialog; diff --git a/frontend/src/components/dialog/org-add-user-dialog.js b/frontend/src/components/dialog/org-add-user-dialog.js index 64ff5fcc95..f7b5f9e2f5 100644 --- a/frontend/src/components/dialog/org-add-user-dialog.js +++ b/frontend/src/components/dialog/org-add-user-dialog.js @@ -145,7 +145,7 @@ class AddOrgUserDialog extends React.Component { - {this.passwdInput = input}} value={this.state.password || ''} onChange={this.inputPassword} /> + {this.passwdInput = input;}} value={this.state.password || ''} onChange={this.inputPassword} /> @@ -154,7 +154,7 @@ class AddOrgUserDialog extends React.Component { - {this.passwdNewInput = input}} className="passwd" value={this.state.passwdnew || ''} onChange={this.inputPasswordNew} /> + {this.passwdNewInput = input;}} className="passwd" value={this.state.passwdnew || ''} onChange={this.inputPasswordNew} /> diff --git a/frontend/src/components/dialog/org-delete-department-dialog.js b/frontend/src/components/dialog/org-delete-department-dialog.js new file mode 100644 index 0000000000..d2e19cfbf7 --- /dev/null +++ b/frontend/src/components/dialog/org-delete-department-dialog.js @@ -0,0 +1,55 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; +import { gettext, orgID } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; +import { Utils } from '../../utils/utils'; + +const propTypes = { + groupName: PropTypes.string, + toggle: PropTypes.func.isRequired, + onDepartChanged: PropTypes.func.isRequired +}; + +class DeleteDepartDialog extends React.Component { + + constructor(props) { + super(props); + this.state = { + errMessage: null + }; + } + + deleteDepart = () => { + seafileAPI.orgAdminDeleteDepartGroup(orgID, this.props.groupID).then((res) => { + if (res.data.success) { + this.props.onDepartChanged(); + this.props.toggle(); + } + }).catch(err => { + this.setState({ errMessage: 'There are sub-departments in this department.' }); + }); + } + + render() { + let subtitle = gettext('Are you sure you want to delete {placeholder} ?'); + subtitle = subtitle.replace('{placeholder}', '' + Utils.HTMLescape(this.props.groupName) + ''); + return ( + + {gettext('Delete Department')} + +
+ { this.state.errMessage &&

{this.state.errMessage}

} +
+ + {!this.state.errMessage && } + + +
+ ); + } +} + +DeleteDepartDialog.propTypes = propTypes; + +export default DeleteDepartDialog; diff --git a/frontend/src/components/dialog/org-delete-member-dialog.js b/frontend/src/components/dialog/org-delete-member-dialog.js new file mode 100644 index 0000000000..b5e02dd7b3 --- /dev/null +++ b/frontend/src/components/dialog/org-delete-member-dialog.js @@ -0,0 +1,50 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; +import { gettext, orgID } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; +import { Utils } from '../../utils/utils'; + +const propTypes = { + member: PropTypes.object.isRequired, + toggle: PropTypes.func.isRequired, + onMemberChanged: PropTypes.func.isRequired +}; + +class DeleteMemberDialog extends React.Component { + + constructor(props) { + super(props); + } + + deleteMember = () => { + const userEmail = this.props.member.email; + seafileAPI.orgAdminDeleteDepartGroupUser(orgID, this.props.groupID, userEmail).then((res) => { + if (res.data.success) { + this.props.onMemberChanged(); + this.props.toggle(); + } + }); + } + + render() { + let subtitle = gettext('Are you sure you want to delete {placeholder} ?'); + subtitle = subtitle.replace('{placeholder}', '' + Utils.HTMLescape(this.props.member.name) + ''); + return ( + + {gettext('Delete Member')} + +
+
+ + + + +
+ ); + } +} + +DeleteMemberDialog.propTypes = propTypes; + +export default DeleteMemberDialog; diff --git a/frontend/src/components/dialog/org-delete-repo-dialog.js b/frontend/src/components/dialog/org-delete-repo-dialog.js new file mode 100644 index 0000000000..7dc0d45364 --- /dev/null +++ b/frontend/src/components/dialog/org-delete-repo-dialog.js @@ -0,0 +1,49 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; +import { gettext, orgID } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; +import { Utils } from '../../utils/utils'; + +class DeleteRepoDialog extends React.Component { + + constructor(props) { + super(props); + } + + deleteRepo = () => { + seafileAPI.orgAdminDeleteDepartGroupRepo(orgID, this.props.groupID, this.props.repo.repo_id).then((res) => { + if (res.data.success) { + this.props.onRepoChanged(); + this.props.toggle(); + } + }); + } + + render() { + let subtitle = gettext('Are you sure you want to delete {placeholder} ?'); + subtitle = subtitle.replace('{placeholder}', '' + Utils.HTMLescape(this.props.repo.name) + ''); + return ( + + {gettext('Delete Library')} + +
+
+ + + + +
+ ); + } +} + +const propTypes = { + repo: PropTypes.object.isRequired, + toggle: PropTypes.func.isRequired, + onRepoChanged: PropTypes.func.isRequired +}; + +DeleteRepoDialog.propTypes = propTypes; + +export default DeleteRepoDialog; diff --git a/frontend/src/components/dialog/org-set-group-quota-dialog.js b/frontend/src/components/dialog/org-set-group-quota-dialog.js new file mode 100644 index 0000000000..19f0260e4b --- /dev/null +++ b/frontend/src/components/dialog/org-set-group-quota-dialog.js @@ -0,0 +1,80 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Input, InputGroupAddon, InputGroup } from 'reactstrap'; +import { gettext, orgID } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; + +const propTypes = { + toggle: PropTypes.func.isRequired, + groupID: PropTypes.number.isRequired, + onDepartChanged: PropTypes.func.isRequired, +}; + +class SetGroupQuotaDialog extends React.Component { + + constructor(props) { + super(props); + this.state = { + quota: '', + errMessage: '', + }; + } + + setGroupQuota = () => { + const myReg = /^[1-9]\d*$/im; + let quota = this.state.quota; + if ((quota.length && myReg.test(quota)) || quota == -2) { + this.setState({ errMessage: '' }); + let newQuota = this.state.quota == -2 ? this.state.quota : this.state.quota * 1000000; + seafileAPI.orgAdminSetGroupQuota(orgID, this.props.groupID, newQuota).then((res) => { + this.props.toggle(); + this.props.onDepartChanged(); + }); + } else { + const err = gettext('Quota is invalid.'); + this.setState({ errMessage: err }); + } + } + + handleChange = (e) => { + const quota = e.target.value.trim(); + this.setState({ quota: quota }); + } + + handleKeyPress = (e) => { + if (e.key === 'Enter') { + this.setGroupQuota(); + e.preventDefault(); + } + } + + render() { + return ( + + {gettext('Set Quota')} + + + + {'MB'} + +

+
{gettext('An integer that is greater than 0 or equal to -2.')}
+ {gettext('Tip: -2 means no limit.')} +

+ { this.state.errMessage &&

{this.state.errMessage}

} +
+ + + +
+ ); + } +} + +SetGroupQuotaDialog.propTypes = propTypes; + +export default SetGroupQuotaDialog; diff --git a/frontend/src/css/org-department-item.css b/frontend/src/css/org-department-item.css new file mode 100644 index 0000000000..ea9a347c30 --- /dev/null +++ b/frontend/src/css/org-department-item.css @@ -0,0 +1,28 @@ +.cur-view-path .operation-item { + font-size: 12px; + height: 24px; + line-height: 24px; +} +.cur-view-container .no-libraty, .cur-view-container .no-member, .cur-view-container .no-group { + color: #a4a4a4; + text-align: center; + margin: 30px 0; +} +.cur-view-path .sf-heading a { + color: #eb8205; +} +.cur-view-subcontainer { + margin: 10px; +} +.cur-view-subcontainer table { + margin: 8px 0 40px; +} +.org-departments>div{ + height: 100%; +} +.org-members .cur-view-content { + padding-bottom: 40px; +} +.cur-view-path button:hover { + cursor: pointer; +} \ No newline at end of file diff --git a/frontend/src/pages/org-admin/index.js b/frontend/src/pages/org-admin/index.js index e60e45ee69..101d880f52 100644 --- a/frontend/src/pages/org-admin/index.js +++ b/frontend/src/pages/org-admin/index.js @@ -12,6 +12,9 @@ import OrgGroups from './org-groups'; import OrgLibraries from './org-libraries'; import OrgInfo from './org-info'; import OrgLinks from './org-links'; +import OrgDepartments from './org-departments'; +import OrgDepartmentsList from './org-departments-list'; +import OrgDepartmentItem from './org-department-item'; import '../../assets/css/fa-solid.css'; import '../../assets/css/fa-regular.css'; @@ -37,6 +40,9 @@ class Org extends React.Component { if (currentTab == 'useradmin') { currentTab = 'users'; } + if (currentTab > 0) { + currentTab = 'departmentadmin'; + } this.setState({currentTab: currentTab}); } @@ -63,6 +69,11 @@ class Org extends React.Component { render() { let { isSidePanelClosed, currentTab, isShowAddOrgUserDialog, isShowAddOrgAdminDialog, isInviteUserDialogOpen } = this.state; + let href = window.location.href; + let newPath = 'groups/'; + if (href.indexOf('org/departmentadmin/groups/') > 0) { + newPath = href.slice(href.indexOf('groups/')); + } return (
@@ -82,6 +93,10 @@ class Org extends React.Component { + + + +
diff --git a/frontend/src/pages/org-admin/org-department-item.js b/frontend/src/pages/org-admin/org-department-item.js new file mode 100644 index 0000000000..7090360427 --- /dev/null +++ b/frontend/src/pages/org-admin/org-department-item.js @@ -0,0 +1,372 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { seafileAPI } from '../../utils/seafile-api'; +import { Utils } from '../../utils/utils.js'; +import { serviceURL, mediaUrl, gettext, orgID } from '../../utils/constants'; +import OrgDepartmentsList from './org-departments-list'; +import ModalPortal from '../../components/modal-portal'; +import AddMemberDialog from '../../components/dialog/org-add-member-dialog'; +import DeleteMemberDialog from '../../components/dialog/org-delete-member-dialog'; +import AddRepoDialog from '../../components/dialog/org-add-repo-dialog'; +import DeleteRepoDialog from '../../components/dialog/org-delete-repo-dialog'; +import RoleEditor from '../../components/select-editor/role-editor'; +import '../../css/org-department-item.css'; + +class OrgDepartmentItem extends React.Component { + + constructor(props) { + super(props); + this.state = { + members: [], + repos: [], + groups: [], + ancestorGroups: [], + deletedMember: {}, + deletedRepo: {}, + showAddMemberDialog: false, + showDeleteMemberDialog: false, + showAddRepoDialog: false, + showDeleteRepoDialog: false, + isItemFreezed: false, + groupID: null, + groupName: '', + } + } + + listOrgGroupRepo = (groupID) => { + seafileAPI.orgAdminListDepartGroupRepos(orgID, groupID).then(res => { + this.setState({ + repos: res.data.libraries + }); + }); + } + + listOrgMembers = (groupID) => { + seafileAPI.orgAdminListGroupInfo(orgID, groupID, true).then(res => { + this.setState({ + members: res.data.members, + groups: res.data.groups, + ancestorGroups: res.data.ancestor_groups, + groupName: res.data.name, + }); + }); + } + + showAddMemberDialog = () => { + this.setState({ showAddMemberDialog: true }); + } + + showDeleteMemberDialog = (member) => { + this.setState({ showDeleteMemberDialog: true, deletedMember: member }); + } + + showAddRepoDialog = () => { + this.setState({ showAddRepoDialog: true }); + } + + showDeleteRepoDialog = (repo) => { + this.setState({ showDeleteRepoDialog: true, deletedRepo: repo }); + } + + toggleCancel = () => { + this.setState({ + showAddMemberDialog: false, + showDeleteMemberDialog: false, + showAddRepoDialog: false, + showDeleteRepoDialog: false, + }); + } + + onRepoChanged = () => { + this.listOrgGroupRepo(this.state.groupID); + } + + onMemberChanged = () => { + this.listOrgMembers(this.state.groupID); + } + + toggleItemFreezed = (isFreezed) => { + this.setState({ + isItemFreezed: isFreezed + }); + } + + componentWillMount() { + const href = window.location.href; + let path = href.slice(href.indexOf('groups/')); + let groupID = path.slice(7, path.length - 1); + this.setState({ + groupID: groupID + }); + this.listOrgGroupRepo(groupID); + this.listOrgMembers(groupID); + } + + render() { + const members = this.state.members; + const repos = this.state.repos; + return ( +
+
+
+

+ { this.state.groupID ? + {gettext('Departments')} + : {gettext('Departments')} + } + { + this.state.ancestorGroups.map(ancestor => { + let newHref = serviceURL + '/org/departmentadmin/groups/' + ancestor.id + '/'; + return ( + {' / '}{ancestor.name} + ); + }) + } + { this.state.groupID && {' / '}{this.state.groupName} } +

+
+ +
+ +
+ +
+
+
+

{gettext('Members')}

+
+
+ +
+
+
+ {(members && members.length === 1 && members[0].role === "Owner") ? +

{gettext('No Members')}

: + + + + + + + + + + + {members.map((member, index) => { + return ( + + + + ); + })} + +
{gettext('Name')}{gettext('Role')}
+ } +
+
+ +
+
+

{gettext('Libraries')}

+
+ +
+
+ { repos.length > 0 ? +
+ + + + + + + + + + + {repos.map((repo, index) => { + return( + + + + ); + })} + +
{gettext('Name')}{gettext('Size')}
+
+ :

{gettext('No libraries')}

+ } +
+ +
+ + {this.state.showDeleteMemberDialog && ( + + + + )} + {this.state.showDeleteRepoDialog && ( + + + + )} + {this.state.showAddMemberDialog && ( + + + + )} + {this.state.showAddRepoDialog && ( + + + + )} + +
+ ); + } +} + + +class MemberItem extends React.Component { + + constructor(props) { + super(props); + this.state = { + highlight: false, + showRoleMenu: false, + }; + this.roles = ['Admin', 'Member']; + } + + onMouseEnter = () => { + if (this.props.isItemFreezed) return; + this.setState({ highlight: true }); + } + + onMouseLeave = () => { + if (this.props.isItemFreezed) return; + this.setState({ highlight: false }); + } + + toggleMemberRoleMenu = () => { + this.setState({ showRoleMenu: !this.state.showRoleMenu }); + } + + onChangeUserRole = (role) => { + let isAdmin = role === 'Admin' ? true : false; + seafileAPI.orgAdminSetDepartGroupUserRole(orgID, this.props.groupID, this.props.member.email, isAdmin).then((res) => { + this.props.onMemberChanged(); + }); + this.setState({ + highlight: false, + }); + } + + render() { + const member = this.props.member; + const highlight = this.state.highlight; + let memberLink = serviceURL + '/org/useradmin/info/' + member.email + '/'; + if (member.role === 'Owner') return null; + return ( + + member-header + {member.name} + + + + { + !this.props.isItemFreezed ? + + + : + } + + ); + } +} + +const MemberItemPropTypes = { + isItemFreezed: PropTypes.bool.isRequired, + onMemberChanged: PropTypes.func.isRequired, + showDeleteMemberDialog: PropTypes.func.isRequired, + toggleItemFreezed: PropTypes.func.isRequired, +}; + +MemberItem.propTypes = MemberItemPropTypes; + + +class RepoItem extends React.Component { + + constructor(props) { + super(props); + this.state = { + highlight: false, + }; + } + + onMouseEnter = () => { + this.setState({ highlight: true }); + } + + onMouseLeave = () => { + this.setState({ highlight: false }); + } + + render() { + const repo = this.props.repo; + const highlight = this.state.highlight; + let iconUrl = Utils.getLibIconUrl(repo); + return ( + + {gettext('icon')}/ + {repo.name} + {Utils.bytesToSize(repo.size)}{' '} + + + + + ); + } +} + +const RepoItemPropTypes = { + repo: PropTypes.object.isRequired, + showDeleteRepoDialog: PropTypes.func.isRequired, +}; + +RepoItem.propTypes = RepoItemPropTypes; + +export default OrgDepartmentItem; \ No newline at end of file diff --git a/frontend/src/pages/org-admin/org-departments-list.js b/frontend/src/pages/org-admin/org-departments-list.js new file mode 100644 index 0000000000..125cf308a5 --- /dev/null +++ b/frontend/src/pages/org-admin/org-departments-list.js @@ -0,0 +1,199 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import moment from 'moment'; +import { seafileAPI } from '../../utils/seafile-api'; +import { siteRoot, serviceURL, gettext, orgID, lang } from '../../utils/constants'; +import { Utils } from '../../utils/utils.js'; +import ModalPortal from '../../components/modal-portal'; +import AddDepartDialog from '../../components/dialog/org-add-department-dialog'; +import DeleteDepartDialog from '../../components/dialog/org-delete-department-dialog'; +import SetGroupQuotaDialog from '../../components/dialog/org-set-group-quota-dialog'; +import '../../css/org-department-item.css'; + +moment.locale(lang); + +class OrgDepartmentsList extends React.Component { + + constructor(props) { + super(props); + this.state = { + groups: null, + groupID: -1, + groupName: '', + showAddDepartDialog: false, + showDeleteDepartDialog: false, + showSetGroupQuotaDialog: false, + }; + } + + listDepartGroups = () => { + if (this.props.groupID) { + seafileAPI.orgAdminListGroupInfo(orgID, this.props.groupID, true).then(res => { + this.setState({ + groups: res.data.groups + }); + }); + } else { + seafileAPI.orgAdminListDepartGroups(orgID).then(res => { + this.setState({ + groups: res.data.data + }); + }); + } + } + + showAddDepartDialog = () => { + this.setState({ showAddDepartDialog: true }); + } + + showDeleteDepartDialog = (group) => { + this.setState({ showDeleteDepartDialog: true, groupID: group.id, groupName: group.name }); + } + + showSetGroupQuotaDialog = (groupID) => { + this.setState({ showSetGroupQuotaDialog: true, groupID: groupID }); + } + + toggleCancel = () => { + this.setState({ + showAddDepartDialog: false, + showDeleteDepartDialog: false, + showSetGroupQuotaDialog: false, + }); + } + + onDepartChanged = () => { + this.listDepartGroups(); + } + + componentWillMount() { + this.listDepartGroups(); + } + + render() { + const groups = this.state.groups; + let isSub = this.props.groupID ? true : false; + let header = isSub ? gettext('Sub-departments') : gettext('Departments'); + let headerButton = isSub ? gettext('New Sub-departments') : gettext('New Departments'); + let noGroup = isSub ? gettext('No sub-departments') : gettext('No departments'); + return ( +
+
+
+

{header}

+
+ +
+
+
+ {groups && groups.length > 0 ? + + + + + + + + + + + {groups.map((group, index) => { + return( + + + + ); + })} + +
{gettext('Name')}{gettext('Created At')}{gettext('Quota')}
+ : +

{noGroup}

+ } +
+ + {this.state.showAddDepartDialog && ( + + + + )} + {this.state.showDeleteDepartDialog && ( + + + + )} + {this.state.showSetGroupQuotaDialog && ( + + + + )} + +
+
+ ); + } +} + +class GroupItem extends React.Component { + + constructor(props) { + super(props); + this.state = { + highlight: false, + }; + } + + onMouseEnter = () => { + this.setState({ highlight: true }); + } + + onMouseLeave = () => { + this.setState({ highlight: false }); + } + + render() { + const group = this.props.group; + const highlight = this.state.highlight; + const newHref = serviceURL + '/org/departmentadmin/groups/' + group.id + '/'; + return ( + + {group.name} + {moment(group.created_at).fromNow()} + + {Utils.bytesToSize(group.quota)}{' '} + + + + + + + ); + } +} + +const GroupItemPropTypes = { + group: PropTypes.object.isRequired, + showSetGroupQuotaDialog: PropTypes.func.isRequired, + showDeleteDepartDialog: PropTypes.func.isRequired, +}; + +GroupItem.propTypes = GroupItemPropTypes; + +export default OrgDepartmentsList; \ No newline at end of file diff --git a/frontend/src/pages/org-admin/org-departments.js b/frontend/src/pages/org-admin/org-departments.js new file mode 100644 index 0000000000..9a7a17b193 --- /dev/null +++ b/frontend/src/pages/org-admin/org-departments.js @@ -0,0 +1,20 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import '../../css/org-department-item.css'; + +class OrgDepartments extends React.Component { + + constructor(props) { + super(props); + } + + render() { + return ( +
+ {this.props.children} +
+ ); + } +} + +export default OrgDepartments; diff --git a/frontend/src/pages/org-admin/org-groups.js b/frontend/src/pages/org-admin/org-groups.js index 91f498ede8..5debbf3335 100644 --- a/frontend/src/pages/org-admin/org-groups.js +++ b/frontend/src/pages/org-admin/org-groups.js @@ -172,7 +172,7 @@ class GroupItem extends React.Component { renderGroupHref = (group) => { let groupInfoHref; if (group.creatorName == 'system admin') { - groupInfoHref = siteRoot + 'org/admin/#address-book/groups/' + group.id + '/'; + groupInfoHref = siteRoot + 'org/departmentadmin/groups/' + group.id + '/'; } else { groupInfoHref = siteRoot + 'org/groupadmin/' + group.id + '/'; } diff --git a/frontend/src/pages/org-admin/org-links.js b/frontend/src/pages/org-admin/org-links.js index e9c5f4e863..177986b305 100644 --- a/frontend/src/pages/org-admin/org-links.js +++ b/frontend/src/pages/org-admin/org-links.js @@ -73,7 +73,7 @@ class OrgLinks extends React.Component { {gettext('Name')} {gettext('Owner')} - {gettext('Create At')} + {gettext('Created At')} {gettext('Count')} @@ -160,8 +160,8 @@ class RepoItem extends React.Component { } render() { - const { index, link, deleteOrgLink } = this.props; - const href = siteRoot + 'org/useradmin/info/' + link.owner_email + '/'; + const { link, deleteOrgLink } = this.props; + const href = siteRoot + 'org/useradmin/info/' + encodeURIComponent(link.owner_email) + '/'; return ( {link.name} diff --git a/frontend/src/pages/org-admin/side-panel.js b/frontend/src/pages/org-admin/side-panel.js index 2e5bc54761..2559d8e6ed 100644 --- a/frontend/src/pages/org-admin/side-panel.js +++ b/frontend/src/pages/org-admin/side-panel.js @@ -57,10 +57,10 @@ class SidePanel extends React.Component {
  • - + this.tabItemClick('departmentadmin')} > {gettext('Departments')} - +
  • this.tabItemClick('publinkadmin')} >