shinoka7 6 лет назад
Родитель
Сommit
f05a74f555

+ 27 - 8
src/client/js/components/Admin/UserGroup/UserGroupPage.jsx

@@ -1,6 +1,7 @@
 import React, { Fragment } from 'react';
 import PropTypes from 'prop-types';
 
+import PaginationWrapper from '../../PaginationWrapper';
 import UserGroupTable from './UserGroupTable';
 import UserGroupCreateForm from './UserGroupCreateForm';
 import UserGroupDeleteModal from './UserGroupDeleteModal';
@@ -19,10 +20,13 @@ class UserGroupPage extends React.Component {
       userGroupRelations: {},
       selectedUserGroup: undefined, // not null but undefined (to use defaultProps in UserGroupDeleteModal)
       isDeleteModalShow: false,
+      activePage: 1,
+      totalUserGroups: 0,
     };
 
     this.xss = window.xss;
 
+    this.handlePage = this.handlePage.bind(this);
     this.showDeleteModal = this.showDeleteModal.bind(this);
     this.hideDeleteModal = this.hideDeleteModal.bind(this);
     this.addUserGroup = this.addUserGroup.bind(this);
@@ -96,23 +100,32 @@ class UserGroupPage extends React.Component {
     }
   }
 
+  async handlePage(selectedPage) {
+    await this.setState({ activePage: selectedPage });
+    await this.syncUserGroupAndRelations();
+  }
+
   async syncUserGroupAndRelations() {
     let userGroups = [];
     let userGroupRelations = {};
+    let totalUserGroups = 0;
 
     try {
+      const params = { page: this.state.activePage };
       const responses = await Promise.all([
-        this.props.appContainer.apiv3.get('/user-groups'),
-        this.props.appContainer.apiv3.get('/user-group-relations'),
+        this.props.appContainer.apiv3.get('/user-groups', params),
+        this.props.appContainer.apiv3.get('/user-group-relations', params),
       ]);
 
       const [userGroupsRes, userGroupRelationsRes] = responses;
       userGroups = userGroupsRes.data.userGroups;
+      totalUserGroups = userGroupsRes.data.totalUserGroups;
       userGroupRelations = userGroupRelationsRes.data.userGroupRelations;
 
       this.setState({
         userGroups,
         userGroupRelations,
+        totalUserGroups,
       });
     }
     catch (err) {
@@ -127,12 +140,18 @@ class UserGroupPage extends React.Component {
           isAclEnabled={this.props.isAclEnabled}
           onCreate={this.addUserGroup}
         />
-        <UserGroupTable
-          userGroups={this.state.userGroups}
-          userGroupRelations={this.state.userGroupRelations}
-          isAclEnabled={this.props.isAclEnabled}
-          onDelete={this.showDeleteModal}
-        />
+        <PaginationWrapper
+          activePage={this.state.activePage}
+          changePage={this.handlePage}
+          totalItemsCount={this.state.totalUserGroups}
+        >
+          <UserGroupTable
+            userGroups={this.state.userGroups}
+            isAclEnabled={this.props.isAclEnabled}
+            onDelete={this.showDeleteModal}
+            userGroupRelations={this.state.userGroupRelations}
+          />
+        </PaginationWrapper>
         <UserGroupDeleteModal
           userGroups={this.state.userGroups}
           deleteUserGroup={this.state.selectedUserGroup}

+ 15 - 3
src/client/js/components/Admin/UserGroup/UserGroupTable.jsx

@@ -13,13 +13,25 @@ class UserGroupTable extends React.Component {
 
     this.xss = window.xss;
 
+    this.state = {
+      userGroups: this.props.userGroups,
+      userGroupRelations: this.props.userGroupRelations,
+    };
+
     this.onDelete = this.onDelete.bind(this);
   }
 
+  componentWillReceiveProps(nextProps) {
+    this.setState({
+      userGroups: nextProps.userGroups,
+      userGroupRelations: nextProps.userGroupRelations,
+    });
+  }
+
   onDelete(e) {
     const { target } = e;
     const groupId = target.getAttribute('data-user-group-id');
-    const group = this.props.userGroups.find((group) => {
+    const group = this.state.userGroups.find((group) => {
       return group._id === groupId;
     });
 
@@ -43,7 +55,7 @@ class UserGroupTable extends React.Component {
             </tr>
           </thead>
           <tbody>
-            {this.props.userGroups.map((group) => {
+            {this.state.userGroups.map((group) => {
               return (
                 <tr key={group._id}>
                   {this.props.isAclEnabled
@@ -56,7 +68,7 @@ class UserGroupTable extends React.Component {
                   }
                   <td>
                     <ul className="list-inline">
-                      {this.props.userGroupRelations[group._id].map((user) => {
+                      {this.state.userGroupRelations[group._id].map((user) => {
                         return <li key={user._id} className="list-inline-item badge badge-primary">{this.xss.process(user.username)}</li>;
                       })}
                     </ul>

+ 182 - 0
src/client/js/components/PaginationWrapper.jsx

@@ -0,0 +1,182 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { withTranslation } from 'react-i18next';
+
+import Pagination from 'react-bootstrap/lib/Pagination';
+import { createSubscribedElement } from './UnstatedUtils';
+import AppContainer from '../services/AppContainer';
+
+class PaginationWrapper extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      totalItemsCount: 0,
+      activePage: 1,
+      paginationNumbers: {},
+    };
+
+    this.calculatePagination = this.calculatePagination.bind(this);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    const limit = this.props.appContainer.getConfig().recentCreatedLimit;
+
+    this.setState({
+      activePage: nextProps.activePage,
+      totalItemsCount: nextProps.totalItemsCount,
+    }, () => {
+      const activePage = this.state.activePage;
+      const totalCount = this.state.totalItemsCount;
+      const paginationNumbers = this.calculatePagination(limit, totalCount, activePage);
+      this.setState({ paginationNumbers });
+    });
+  }
+
+  calculatePagination(limit, totalCount, activePage) {
+    // calc totalPageNumber
+    const totalPage = Math.floor(totalCount / limit) + (totalCount % limit === 0 ? 0 : 1);
+
+    let paginationStart = activePage - 2;
+    let maxViewPageNum = activePage + 2;
+    // pagiNation Number area size = 5 , pageNuber calculate in here
+    // activePage Position calculate ex. 4 5 [6] 7 8 (Page8 over is Max), 3 4 5 [6] 7 (Page7 is Max)
+    if (paginationStart < 1) {
+      const diff = 1 - paginationStart;
+      paginationStart += diff;
+      maxViewPageNum = Math.min(totalPage, maxViewPageNum + diff);
+    }
+    if (maxViewPageNum > totalPage) {
+      const diff = maxViewPageNum - totalPage;
+      maxViewPageNum -= diff;
+      paginationStart = Math.max(1, paginationStart - diff);
+    }
+
+    return {
+      totalPage,
+      paginationStart,
+      maxViewPageNum,
+    };
+  }
+
+  /**
+    * generate Elements of Pagination First Prev
+    * ex.  <<   <   1  2  3  >  >>
+    * this function set << & <
+    */
+  generateFirstPrev(activePage) {
+    const paginationItems = [];
+    if (activePage !== 1) {
+      paginationItems.push(
+        <Pagination.First key="first" onClick={() => { return this.props.changePage(1) }} />,
+      );
+      paginationItems.push(
+        <Pagination.Prev key="prev" onClick={() => { return this.props.changePage(activePage - 1) }} />,
+      );
+    }
+    else {
+      paginationItems.push(
+        <Pagination.First key="first" disabled />,
+      );
+      paginationItems.push(
+        <Pagination.Prev key="prev" disabled />,
+      );
+
+    }
+    return paginationItems;
+  }
+
+  /**
+   * generate Elements of Pagination First Prev
+   *  ex. << < 4 5 6 7 8 > >>, << < 1 2 3 4 > >>
+   * this function set  numbers
+   */
+  generatePaginations(activePage, paginationStart, maxViewPageNum) {
+    const paginationItems = [];
+    for (let number = paginationStart; number <= maxViewPageNum; number++) {
+      paginationItems.push(
+        <Pagination.Item key={number} active={number === activePage} onClick={() => { return this.props.changePage(number) }}>{number}</Pagination.Item>,
+      );
+    }
+    return paginationItems;
+  }
+
+  /**
+   * generate Elements of Pagination First Prev
+   * ex.  <<   <   1  2  3  >  >>
+   * this function set > & >>
+   */
+  generateNextLast(activePage, totalPage) {
+    const paginationItems = [];
+    if (totalPage !== activePage) {
+      paginationItems.push(
+        <Pagination.Next key="next" onClick={() => { return this.props.changePage(activePage + 1) }} />,
+      );
+      paginationItems.push(
+        <Pagination.Last key="last" onClick={() => { return this.props.changePage(totalPage) }} />,
+      );
+    }
+    else {
+      paginationItems.push(
+        <Pagination.Next key="next" disabled />,
+      );
+      paginationItems.push(
+        <Pagination.Last key="last" disabled />,
+      );
+
+    }
+    return paginationItems;
+
+  }
+
+  render() {
+    const { children } = this.props;
+    const childElement = React.cloneElement(
+      children, {
+        currentItems: this.state.currentItems,
+        currentPage: this.state.activePage,
+      },
+    );
+
+    const paginationItems = [];
+
+    const activePage = this.state.activePage;
+    const totalPage = this.state.paginationNumbers.totalPage;
+    const paginationStart = this.state.paginationNumbers.paginationStart;
+    const maxViewPageNum = this.state.paginationNumbers.maxViewPageNum;
+    const firstPrevItems = this.generateFirstPrev(activePage);
+    paginationItems.push(firstPrevItems);
+    const paginations = this.generatePaginations(activePage, paginationStart, maxViewPageNum);
+    paginationItems.push(paginations);
+    const nextLastItems = this.generateNextLast(activePage, totalPage);
+    paginationItems.push(nextLastItems);
+
+    return (
+      <React.Fragment>
+        <div>
+          { childElement }
+          <Pagination bsSize="small">{paginationItems}</Pagination>
+        </div>
+      </React.Fragment>
+    );
+  }
+
+
+}
+
+const PaginationWrappered = (props) => {
+  return createSubscribedElement(PaginationWrapper, props, [AppContainer]);
+};
+
+PaginationWrapper.propTypes = {
+  children: PropTypes.node.isRequired,
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+
+  activePage: PropTypes.number.isRequired,
+  changePage: PropTypes.func.isRequired,
+  totalItemsCount: PropTypes.number.isRequired,
+};
+
+export default withTranslation()(PaginationWrappered);

+ 4 - 0
src/server/models/user-group.js

@@ -110,6 +110,10 @@ class UserGroup {
     return deletedGroup;
   }
 
+  static countUserGroups() {
+    return this.estimatedDocumentCount();
+  }
+
   // グループ生成(名前が要る)
   static createGroupByName(name) {
     return this.create({ name });

+ 5 - 2
src/server/routes/apiv3/user-group.js

@@ -50,8 +50,11 @@ module.exports = (crowi) => {
   router.get('/', loginRequired(crowi), adminRequired(), async(req, res) => {
     // TODO: filter with querystring
     try {
-      const userGroups = await UserGroup.find();
-      return res.apiv3({ userGroups });
+      const page = parseInt(req.query.page) || 1;
+      const totalUserGroups = await UserGroup.countUserGroups();
+      const result = await UserGroup.findUserGroupsWithPagination({ page });
+      const userGroups = result.docs;
+      return res.apiv3({ userGroups, totalUserGroups });
     }
     catch (err) {
       const msg = 'Error occurred in fetching user group list';