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')}
:
+
+
+
+ |
+ {gettext('Name')} |
+ {gettext('Role')} |
+ |
+
+
+
+ {members.map((member, index) => {
+ return (
+
+
+
+ );
+ })}
+
+
+ }
+
+
+
+
+
+
{gettext('Libraries')}
+
+
+
+
+ { repos.length > 0 ?
+
+
+
+
+ |
+ {gettext('Name')} |
+ {gettext('Size')} |
+ |
+
+
+
+ {repos.map((repo, index) => {
+ return(
+
+
+
+ );
+ })}
+
+
+
+ :
{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.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 (
+
+  |
+ {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 ?
+
+
+
+ {gettext('Name')} |
+ {gettext('Created At')} |
+ {gettext('Quota')} |
+ |
+
+
+
+ {groups.map((group, index) => {
+ return(
+
+
+
+ );
+ })}
+
+
+ :
+
{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')} >