Taichi Masuyama 4 лет назад
Родитель
Сommit
7ed3af694c

+ 1 - 0
packages/app/src/components/Admin/UserGroup/UserGroupPage.tsx

@@ -129,6 +129,7 @@ const UserGroupPage: FC<Props> = (props: Props) => {
         )
       }
       <UserGroupTable
+        appContainer={props.appContainer}
         userGroups={userGroups}
         isAclEnabled={isAclEnabled}
         onDelete={showDeleteModal}

+ 0 - 157
packages/app/src/components/Admin/UserGroup/UserGroupTable.jsx

@@ -1,157 +0,0 @@
-import React, { Fragment } from 'react';
-import PropTypes from 'prop-types';
-import { withTranslation } from 'react-i18next';
-import dateFnsFormat from 'date-fns/format';
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
-import AppContainer from '~/client/services/AppContainer';
-
-class UserGroupTable extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.xss = window.xss;
-
-    this.state = {
-      userGroups: this.props.userGroups,
-      userGroupMap: {},
-    };
-
-    this.generateUserGroupMap = this.generateUserGroupMap.bind(this);
-    this.onDelete = this.onDelete.bind(this);
-  }
-
-  componentWillMount() {
-    const userGroupMap = this.generateUserGroupMap(this.props.userGroups, this.props.userGroupRelations);
-    this.setState({ userGroupMap });
-  }
-
-  componentWillReceiveProps(nextProps) {
-    const { userGroups, userGroupRelations } = nextProps;
-    const userGroupMap = this.generateUserGroupMap(userGroups, userGroupRelations);
-
-    this.setState({
-      userGroups,
-      userGroupMap,
-    });
-  }
-
-  generateUserGroupMap(userGroups, userGroupRelations) {
-    const userGroupMap = {};
-    userGroupRelations.forEach((relation) => {
-      const group = relation.relatedGroup;
-
-      const users = userGroupMap[group] || [];
-      users.push(relation.relatedUser);
-
-      // register
-      userGroupMap[group] = users;
-    });
-
-    return userGroupMap;
-  }
-
-  onDelete(e) {
-    const { target } = e;
-    const groupId = target.getAttribute('data-user-group-id');
-    const group = this.state.userGroups.find((group) => {
-      return group._id === groupId;
-    });
-
-    this.props.onDelete(group);
-  }
-
-  render() {
-    const { t } = this.props;
-
-    return (
-      <Fragment>
-        <h2>{t('admin:user_group_management.group_list')}</h2>
-
-        <table className="table table-bordered table-user-list">
-          <thead>
-            <tr>
-              <th>{t('Name')}</th>
-              <th>{t('User')}</th>
-              <th width="100px">{t('Created')}</th>
-              <th width="70px"></th>
-            </tr>
-          </thead>
-          <tbody>
-            {this.state.userGroups.map((group) => {
-              const users = this.state.userGroupMap[group._id];
-
-              return (
-                <tr key={group._id}>
-                  {this.props.isAclEnabled
-                    ? (
-                      <td><a href={`/admin/user-group-detail/${group._id}`}>{this.xss.process(group.name)}</a></td>
-                    )
-                    : (
-                      <td>{this.xss.process(group.name)}</td>
-                    )
-                  }
-                  <td>
-                    <ul className="list-inline">
-                      {users != null && users.map((user) => {
-                        return <li key={user._id} className="list-inline-item badge badge-pill badge-warning">{this.xss.process(user.username)}</li>;
-                      })}
-                    </ul>
-                  </td>
-                  <td>{dateFnsFormat(new Date(group.createdAt), 'yyyy-MM-dd')}</td>
-                  {this.props.isAclEnabled
-                    ? (
-                      <td>
-                        <div className="btn-group admin-group-menu">
-                          <button
-                            type="button"
-                            id={`admin-group-menu-button-${group._id}`}
-                            className="btn btn-outline-secondary btn-sm dropdown-toggle"
-                            data-toggle="dropdown"
-                          >
-                            <i className="icon-settings"></i>
-                          </button>
-                          <div className="dropdown-menu" role="menu" aria-labelledby={`admin-group-menu-button-${group._id}`}>
-                            <a className="dropdown-item" href={`/admin/user-group-detail/${group._id}`}>
-                              <i className="icon-fw icon-note"></i> {t('Edit')}
-                            </a>
-                            <button className="dropdown-item" type="button" role="button" onClick={this.onDelete} data-user-group-id={group._id}>
-                              <i className="icon-fw icon-fire text-danger"></i> {t('Delete')}
-                            </button>
-                          </div>
-                        </div>
-                      </td>
-                    )
-                    : (
-                      <td></td>
-                    )
-                  }
-                </tr>
-              );
-            })}
-          </tbody>
-        </table>
-      </Fragment>
-    );
-  }
-
-}
-
-/**
- * Wrapper component for using unstated
- */
-const UserGroupTableWrapper = withUnstatedContainers(withTranslation()(UserGroupTable), [AppContainer]);
-
-
-UserGroupTable.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-
-  userGroups: PropTypes.arrayOf(PropTypes.object).isRequired,
-  userGroupRelations: PropTypes.arrayOf(PropTypes.object).isRequired,
-  isAclEnabled: PropTypes.bool.isRequired,
-  onDelete: PropTypes.func.isRequired,
-};
-
-export default UserGroupTableWrapper;

+ 148 - 0
packages/app/src/components/Admin/UserGroup/UserGroupTable.tsx

@@ -0,0 +1,148 @@
+import React, {
+  FC, useState, useCallback, useEffect,
+} from 'react';
+import { useTranslation } from 'react-i18next';
+import dateFnsFormat from 'date-fns/format';
+
+import Xss from '~/services/xss';
+import AppContainer from '~/client/services/AppContainer';
+import { IUserGroupHasId, IUserGroupRelation, IUserHasId } from '~/interfaces/user';
+import { CustomWindow } from '~/interfaces/global';
+
+
+type Props = {
+  appContainer: AppContainer,
+
+  userGroups: IUserGroupHasId[],
+  userGroupRelations: IUserGroupRelation[],
+  isAclEnabled: boolean,
+  onDelete?: (userGroup: IUserGroupHasId) => void | Promise<void>,
+};
+
+/*
+ * Utility
+ */
+const generateUserGroupMap = (userGroupRelations: IUserGroupRelation[]): Record<string, Partial<IUserHasId>[]> => {
+  const userGroupMap = {};
+  userGroupRelations.forEach((relation) => {
+    const group = relation.relatedGroup as string; // must be an id of related group
+
+    const users: Partial<IUserHasId>[] = userGroupMap[group] || [];
+    users.push(relation.relatedUser as IUserHasId);
+
+    // register
+    userGroupMap[group] = users;
+  });
+
+  return userGroupMap;
+};
+
+
+const UserGroupTable: FC<Props> = (props: Props) => {
+  const xss: Xss = (window as CustomWindow).xss;
+  const { t } = useTranslation();
+
+  /*
+   * State
+   */
+  const [groupIdToUsersMap, setGroupIdToUsersMap] = useState(generateUserGroupMap(props.userGroupRelations));
+
+  /*
+   * Function
+   */
+  const onClickDelete = useCallback((e) => { // no preventDefault
+    if (props.onDelete == null) {
+      return;
+    }
+
+    const groupId = e.target.getAttribute('data-user-group-id');
+    const group = props.userGroups.find((group) => {
+      return group._id === groupId;
+    });
+
+    if (group == null) {
+      return;
+    }
+
+    props.onDelete(group);
+  }, [props.userGroups, props.onDelete]);
+
+  /*
+   * useEffect
+   */
+  useEffect(() => {
+    setGroupIdToUsersMap(generateUserGroupMap(props.userGroupRelations));
+  }, [props.userGroupRelations]);
+
+  return (
+    <>
+      <h2>{t('admin:user_group_management.group_list')}</h2>
+
+      <table className="table table-bordered table-user-list">
+        <thead>
+          <tr>
+            <th>{t('Name')}</th>
+            <th>{t('User')}</th>
+            <th width="100px">{t('Created')}</th>
+            <th width="70px"></th>
+          </tr>
+        </thead>
+        <tbody>
+          {props.userGroups.map((group) => {
+            const users = groupIdToUsersMap[group._id];
+
+            return (
+              <tr key={group._id}>
+                {props.isAclEnabled
+                  ? (
+                    <td><a href={`/admin/user-group-detail/${group._id}`}>{xss.process(group.name)}</a></td>
+                  )
+                  : (
+                    <td>{xss.process(group.name)}</td>
+                  )
+                }
+                <td>
+                  <ul className="list-inline">
+                    {users != null && users.map((user) => {
+                      return <li key={user._id} className="list-inline-item badge badge-pill badge-warning">{xss.process(user.username)}</li>;
+                    })}
+                  </ul>
+                </td>
+                <td>{dateFnsFormat(new Date(group.createdAt), 'yyyy-MM-dd')}</td>
+                {props.isAclEnabled
+                  ? (
+                    <td>
+                      <div className="btn-group admin-group-menu">
+                        <button
+                          type="button"
+                          id={`admin-group-menu-button-${group._id}`}
+                          className="btn btn-outline-secondary btn-sm dropdown-toggle"
+                          data-toggle="dropdown"
+                        >
+                          <i className="icon-settings"></i>
+                        </button>
+                        <div className="dropdown-menu" role="menu" aria-labelledby={`admin-group-menu-button-${group._id}`}>
+                          <a className="dropdown-item" href={`/admin/user-group-detail/${group._id}`}>
+                            <i className="icon-fw icon-note"></i> {t('Edit')}
+                          </a>
+                          <button className="dropdown-item" type="button" role="button" onClick={onClickDelete} data-user-group-id={group._id}>
+                            <i className="icon-fw icon-fire text-danger"></i> {t('Delete')}
+                          </button>
+                        </div>
+                      </div>
+                    </td>
+                  )
+                  : (
+                    <td></td>
+                  )
+                }
+              </tr>
+            );
+          })}
+        </tbody>
+      </table>
+    </>
+  );
+};
+
+export default UserGroupTable;

+ 1 - 0
packages/app/src/interfaces/user.ts

@@ -21,5 +21,6 @@ export type IUserGroup = {
   parent: Ref<IUserGroup>;
 }
 
+export type IUserHasId = IUser & HasObjectId;
 export type IUserGroupHasId = IUserGroup & HasObjectId;
 export type IUserGroupRelationHasId = IUserGroupRelation & HasObjectId;