Procházet zdrojové kódy

Merge pull request #1165 from weseek/imprv/reactify-admin-user-groups-detail-1

Imprv/reactify admin user groups detail 1
Yuki Takei před 6 roky
rodič
revize
9c2453ddf9

+ 12 - 0
src/client/js/app.jsx

@@ -32,6 +32,7 @@ import StaffCredit from './components/StaffCredit/StaffCredit';
 import MyDraftList from './components/MyDraftList/MyDraftList';
 import MyDraftList from './components/MyDraftList/MyDraftList';
 import UserPictureList from './components/User/UserPictureList';
 import UserPictureList from './components/User/UserPictureList';
 
 
+import UserGroupDetailPage from './components/Admin/UserGroupDetail/UserGroupDetailPage';
 import CustomCssEditor from './components/Admin/CustomCssEditor';
 import CustomCssEditor from './components/Admin/CustomCssEditor';
 import CustomScriptEditor from './components/Admin/CustomScriptEditor';
 import CustomScriptEditor from './components/Admin/CustomScriptEditor';
 import CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
 import CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
@@ -148,6 +149,17 @@ Object.keys(componentMappings).forEach((key) => {
 });
 });
 
 
 // render for admin
 // render for admin
+const adminUserGroupDetailElem = document.getElementById('admin-user-group-detail');
+if (adminUserGroupDetailElem != null) {
+  ReactDOM.render(
+    <Provider inject={[]}>
+      <I18nextProvider i18n={i18n}>
+        <UserGroupDetailPage />
+      </I18nextProvider>
+    </Provider>,
+    adminUserGroupDetailElem,
+  );
+}
 const customCssEditorElem = document.getElementById('custom-css-editor');
 const customCssEditorElem = document.getElementById('custom-css-editor');
 if (customCssEditorElem != null) {
 if (customCssEditorElem != null) {
   // get input[type=hidden] element
   // get input[type=hidden] element

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

@@ -73,7 +73,7 @@ class UserGroupTable extends React.Component {
                       })}
                       })}
                     </ul>
                     </ul>
                   </td>
                   </td>
-                  <td>{dateFnsFormat(new Date(group.createdAt), 'YYYY-MM-DD')}</td>
+                  <td>{dateFnsFormat(new Date(group.createdAt), 'yyyy-MM-dd')}</td>
                   {this.props.isAclEnabled
                   {this.props.isAclEnabled
                     ? (
                     ? (
                       <td>
                       <td>

+ 41 - 0
src/client/js/components/Admin/UserGroupDetail/UserGroupDetailPage.jsx

@@ -0,0 +1,41 @@
+import React from 'react';
+
+import UserGroupEditForm from './UserGroupEditForm';
+import UserGroupUserTable from './UserGroupUserTable';
+import UserGroupUserModal from './UserGroupUserModal';
+import UserGroupPageList from './UserGroupPageList';
+
+class UserGroupDetailPage extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    const elem = document.getElementById('admin-user-group-detail');
+    const userGroup = JSON.parse(elem.getAttribute('data-user-group'));
+
+    this.state = {
+      userGroup,
+    };
+  }
+
+  render() {
+
+    return (
+      <div>
+        <a href="/admin/user-groups" className="btn btn-default">
+          <i className="icon-fw ti-arrow-left" aria-hidden="true"></i>
+        グループ一覧に戻る
+        </a>
+        <UserGroupEditForm
+          userGroup={this.state.userGroup}
+        />
+        <UserGroupUserTable />
+        <UserGroupUserModal />
+        <UserGroupPageList />
+      </div>
+    );
+  }
+
+}
+
+export default UserGroupDetailPage;

+ 97 - 0
src/client/js/components/Admin/UserGroupDetail/UserGroupEditForm.jsx

@@ -0,0 +1,97 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+import dateFnsFormat from 'date-fns/format';
+
+import { createSubscribedElement } from '../../UnstatedUtils';
+import AppContainer from '../../../services/AppContainer';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
+
+class UserGroupEditForm extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      name: props.userGroup.name,
+    };
+
+    this.xss = window.xss;
+
+    this.handleChange = this.handleChange.bind(this);
+    this.handleSubmit = this.handleSubmit.bind(this);
+    this.validateForm = this.validateForm.bind(this);
+  }
+
+  handleChange(event) {
+    this.setState({
+      name: event.target.value,
+    });
+  }
+
+  async handleSubmit(e) {
+    e.preventDefault();
+
+    try {
+      const res = await this.props.appContainer.apiv3.put(`/user-groups/${this.props.userGroup._id}`, {
+        name: this.state.name,
+      });
+
+      toastSuccess(`Updated the group name to "${this.xss.process(res.data.userGroup.name)}"`);
+    }
+    catch (err) {
+      toastError(new Error('Unable to update the group name'));
+    }
+  }
+
+  validateForm() {
+    return this.state.name !== '';
+  }
+
+  render() {
+    const { t } = this.props;
+
+    return (
+      <div className="m-t-20 form-box">
+        <form className="form-horizontal" onSubmit={this.handleSubmit}>
+          <fieldset>
+            <legend>基本情報</legend>
+            <div className="form-group">
+              <label htmlFor="name" className="col-sm-2 control-label">{ t('Name') }</label>
+              <div className="col-sm-4">
+                <input className="form-control" type="text" name="name" value={this.state.name} onChange={this.handleChange} disabled={!this.validateForm()} />
+              </div>
+            </div>
+            <div className="form-group">
+              <label className="col-sm-2 control-label">{ t('Created') }</label>
+              <div className="col-sm-4">
+                <input className="form-control" type="text" disabled value={dateFnsFormat(new Date(this.props.userGroup.createdAt), 'yyyy-MM-dd')} />
+              </div>
+            </div>
+            <div className="form-group">
+              <div className="col-sm-offset-2 col-sm-10">
+                <button type="submit" className="btn btn-primary">{ t('Update') }</button>
+              </div>
+            </div>
+          </fieldset>
+        </form>
+      </div>
+    );
+  }
+
+}
+
+UserGroupEditForm.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  userGroup: PropTypes.object.isRequired,
+};
+
+/**
+ * Wrapper component for using unstated
+ */
+const UserGroupEditFormWrapper = (props) => {
+  return createSubscribedElement(UserGroupEditForm, props, [AppContainer]);
+};
+
+export default withTranslation()(UserGroupEditFormWrapper);

+ 20 - 0
src/client/js/components/Admin/UserGroupDetail/UserGroupPageList.jsx

@@ -0,0 +1,20 @@
+import React from 'react';
+// import PropTypes from 'prop-types';
+
+class UserGroupPageList extends React.Component {
+
+  render() {
+
+    return (
+      <div>
+        UserGroupPageList
+      </div>
+    );
+  }
+
+}
+
+UserGroupPageList.propTypes = {
+};
+
+export default UserGroupPageList;

+ 20 - 0
src/client/js/components/Admin/UserGroupDetail/UserGroupUserModal.jsx

@@ -0,0 +1,20 @@
+import React from 'react';
+// import PropTypes from 'prop-types';
+
+class UserGroupUserModal extends React.Component {
+
+  render() {
+
+    return (
+      <div>
+        UserGroupUserModal
+      </div>
+    );
+  }
+
+}
+
+UserGroupUserModal.propTypes = {
+};
+
+export default UserGroupUserModal;

+ 20 - 0
src/client/js/components/Admin/UserGroupDetail/UserGroupUserTable.jsx

@@ -0,0 +1,20 @@
+import React from 'react';
+// import PropTypes from 'prop-types';
+
+class UserGroupUserTable extends React.Component {
+
+  render() {
+
+    return (
+      <div>
+        UserGroupUserTable
+      </div>
+    );
+  }
+
+}
+
+UserGroupUserTable.propTypes = {
+};
+
+export default UserGroupUserTable;

+ 1 - 1
src/client/js/components/Admin/Users/PasswordResetModal.jsx

@@ -4,7 +4,7 @@ import { withTranslation } from 'react-i18next';
 
 
 import Modal from 'react-bootstrap/es/Modal';
 import Modal from 'react-bootstrap/es/Modal';
 
 
-import toastError from '../../../util/apiNotification';
+import { toastError } from '../../../util/apiNotification';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import AppContainer from '../../../services/AppContainer';
 import AppContainer from '../../../services/AppContainer';
 
 

+ 2 - 2
src/server/models/user-group.js

@@ -118,10 +118,10 @@ class UserGroup {
   }
   }
 
 
   // グループ名の更新
   // グループ名の更新
-  updateName(name) {
+  async updateName(name) {
     // 名前を設定して更新
     // 名前を設定して更新
     this.name = name;
     this.name = name;
-    return this.save();
+    await this.save();
   }
   }
 
 
 }
 }

+ 0 - 35
src/server/routes/admin.js

@@ -716,41 +716,6 @@ module.exports = function(crowi, app) {
     return res.render('admin/user-group-detail', renderVar);
     return res.render('admin/user-group-detail', renderVar);
   };
   };
 
 
-  //
-  actions.userGroup.update = function(req, res) {
-    const userGroupId = req.params.userGroupId;
-    const name = crowi.xss.process(req.body.name);
-
-    UserGroup.findById(userGroupId)
-      .then((userGroupData) => {
-        if (userGroupData == null) {
-          req.flash('errorMessage', 'グループの検索に失敗しました。');
-          return new Promise();
-        }
-
-        // 名前存在チェック
-        return UserGroup.isRegisterableName(name)
-          .then((isRegisterableName) => {
-          // 既に存在するグループ名に更新しようとした場合はエラー
-            if (!isRegisterableName) {
-              req.flash('errorMessage', 'グループ名が既に存在します。');
-            }
-            else {
-              return userGroupData.updateName(name)
-                .then(() => {
-                  req.flash('successMessage', 'グループ名を更新しました。');
-                })
-                .catch((err) => {
-                  req.flash('errorMessage', 'グループ名の更新に失敗しました。');
-                });
-            }
-          });
-      })
-      .then(() => {
-        return res.redirect(`/admin/user-group-detail/${userGroupId}`);
-      });
-  };
-
   actions.userGroupRelation = {};
   actions.userGroupRelation = {};
   actions.userGroupRelation.index = function(req, res) {
   actions.userGroupRelation.index = function(req, res) {
 
 

+ 58 - 3
src/server/routes/apiv3/user-group.js

@@ -175,9 +175,64 @@ module.exports = (crowi) => {
   // router.get('/:id', async(req, res) => {
   // router.get('/:id', async(req, res) => {
   // });
   // });
 
 
-  // update one group with the id
-  // router.put('/:id/update', async(req, res) => {
-  // });
+  validator.update = [
+    body('name', 'Group name is required').trim().exists(),
+  ];
+
+  /**
+   * @swagger
+   *
+   *  paths:
+   *    /_api/v3/user-groups/{:id}:
+   *      put:
+   *        tags: [UserGroup]
+   *        description: Updates userGroup
+   *        produces:
+   *          - application/json
+   *        parameters:
+   *          - name: id
+   *            in: path
+   *            required: true
+   *            description: id of userGroup
+   *            schema:
+   *              type: ObjectId
+   *        responses:
+   *          200:
+   *            description: userGroup is updated
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    userGroup:
+   *                      type: object
+   *                      description: A result of `UserGroup.updateName`
+   */
+  router.put('/:id', loginRequired(), adminRequired, csrf, validator.update, ApiV3FormValidator, async(req, res) => {
+    const { id } = req.params;
+    const { name } = req.body;
+
+    try {
+      const userGroup = await UserGroup.findById(id);
+      if (userGroup == null) {
+        throw new Error('The group does not exist');
+      }
+
+      // check if the new group name is available
+      const isRegisterableName = await UserGroup.isRegisterableName(name);
+      if (!isRegisterableName) {
+        throw new Error('The group name is already taken');
+      }
+
+      await userGroup.updateName(name);
+
+      res.apiv3({ userGroup });
+    }
+    catch (err) {
+      const msg = 'Error occurred in updating a user group name';
+      logger.error(msg, err);
+      return res.apiv3Err(new ErrorV3(msg, 'user-group-update-failed'));
+    }
+  });
 
 
   /**
   /**
    * @swagger
    * @swagger

+ 0 - 1
src/server/routes/index.js

@@ -134,7 +134,6 @@ module.exports = function(crowi, app) {
   // user-groups admin
   // user-groups admin
   app.get('/admin/user-groups'                    , loginRequired(), adminRequired, admin.userGroup.index);
   app.get('/admin/user-groups'                    , loginRequired(), adminRequired, admin.userGroup.index);
   app.get('/admin/user-group-detail/:id'          , loginRequired(), adminRequired, admin.userGroup.detail);
   app.get('/admin/user-group-detail/:id'          , loginRequired(), adminRequired, admin.userGroup.detail);
-  app.post('/admin/user-group/:userGroupId/update', loginRequired(), adminRequired, csrf, admin.userGroup.update);
 
 
   // user-group-relations admin
   // user-group-relations admin
   app.post('/admin/user-group-relation/create', loginRequired(), adminRequired, csrf, admin.userGroupRelation.create);
   app.post('/admin/user-group-relation/create', loginRequired(), adminRequired, csrf, admin.userGroupRelation.create);

+ 15 - 1
src/server/views/admin/user-group-detail.html

@@ -31,12 +31,19 @@
       {% include './widget/menu.html' with {current: 'user-group'} %}
       {% include './widget/menu.html' with {current: 'user-group'} %}
     </div>
     </div>
 
 
-    <div class="col-md-9">
+    <div
+      id="admin-user-group-detail"
+      class="col-md-9"
+      data-user-group="{{ userGroup|json }}"
+    >
+      <!-- そのまま start -->
       <a href="/admin/user-groups" class="btn btn-default">
       <a href="/admin/user-groups" class="btn btn-default">
         <i class="icon-fw ti-arrow-left" aria-hidden="true"></i>
         <i class="icon-fw ti-arrow-left" aria-hidden="true"></i>
         {{ t('user_group_management.back_to_list') }}
         {{ t('user_group_management.back_to_list') }}
       </a>
       </a>
+      <!-- そのまま end -->
 
 
+      <!-- UserGroupUserModal start -->
       <div class="modal fade" id="admin-add-user-group-relation-modal">
       <div class="modal fade" id="admin-add-user-group-relation-modal">
         <div class="modal-dialog">
         <div class="modal-dialog">
           <div class="modal-content">
           <div class="modal-content">
@@ -87,7 +94,9 @@
         </div>
         </div>
         <!-- /.modal-dialog -->
         <!-- /.modal-dialog -->
       </div>
       </div>
+      <!-- UserGroupUserModal end -->
 
 
+      <!-- UserGroupEditForm start -->
       <div class="m-t-20 form-box">
       <div class="m-t-20 form-box">
         <form action="/admin/user-group/{{userGroup.id}}/update" method="post" class="form-horizontal" role="form">
         <form action="/admin/user-group/{{userGroup.id}}/update" method="post" class="form-horizontal" role="form">
           <fieldset>
           <fieldset>
@@ -113,7 +122,9 @@
           </fieldset>
           </fieldset>
         </form>
         </form>
       </div>
       </div>
+      <!-- UserGroupEditForm end -->
 
 
+      <!-- UserGroupUserTable start -->
       <legend class="m-t-20">{{ t('User List') }}</legend>
       <legend class="m-t-20">{{ t('User List') }}</legend>
 
 
       <table class="table table-bordered table-user-list">
       <table class="table table-bordered table-user-list">
@@ -180,15 +191,18 @@
           {% endif %}
           {% endif %}
         </tbody>
         </tbody>
       </table>
       </table>
+      <!-- UserGroupUserTable end -->
 
 
       <!-- {% include '../widget/pager.html' with {path: "/admin/user-group-detail", pager: pager} %} -->
       <!-- {% include '../widget/pager.html' with {path: "/admin/user-group-detail", pager: pager} %} -->
 
 
       <legend class="m-t-20">{{ t('Page') }}</legend>
       <legend class="m-t-20">{{ t('Page') }}</legend>
 
 
+      <!-- UserGroupPageList start -->
       <div class="page-list">
       <div class="page-list">
         {% if relatedPages.length == 0 %}<p>{{ t('user_group_management.no_pages') }}</p>{% endif %}
         {% if relatedPages.length == 0 %}<p>{{ t('user_group_management.no_pages') }}</p>{% endif %}
         {% include '../widget/page_list.html' with { pages: relatedPages } %}
         {% include '../widget/page_list.html' with { pages: relatedPages } %}
       </div>
       </div>
+      <!-- UserGroupPageList end -->
 
 
     </div>
     </div>
   </div>
   </div>