Просмотр исходного кода

typescriptize UserGroupPageList and UserGroupUserTable

kaori 3 лет назад
Родитель
Сommit
ba5cfb50ae

+ 1 - 1
packages/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx

@@ -340,7 +340,7 @@ const UserGroupDetailPage = (props: Props) => {
         />
       </div>
       <h2 className="admin-setting-header mt-4">{t('admin:user_group_management.user_list')}</h2>
-      <UserGroupUserTable />
+      <UserGroupUserTable userGroup={currentUserGroup} userGroupRelations={childUserGroupRelations} />
       <UserGroupUserModal />
 
       <h2 className="admin-setting-header mt-4">{t('admin:user_group_management.child_group_list')}</h2>

+ 0 - 105
packages/app/src/components/Admin/UserGroupDetail/UserGroupPageList.jsx

@@ -1,105 +0,0 @@
-import React, { Fragment } from 'react';
-
-import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
-
-import AdminUserGroupDetailContainer from '~/client/services/AdminUserGroupDetailContainer';
-import { toastError } from '~/client/util/apiNotification';
-import { apiv3Get } from '~/client/util/apiv3-client';
-import { IPageHasId } from '~/interfaces/page';
-
-import PageListItemS from '../../PageList/PageListItemS';
-import PaginationWrapper from '../../PaginationWrapper';
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
-class UserGroupPageList extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      currentPages: [],
-      activePage: 1,
-      total: 0,
-      pagingLimit: 10,
-    };
-
-    this.handlePageChange = this.handlePageChange.bind(this);
-  }
-
-  async componentDidMount() {
-    await this.handlePageChange(this.state.activePage);
-  }
-
-  async handlePageChange(pageNum) {
-    const limit = this.state.pagingLimit;
-    const offset = (pageNum - 1) * limit;
-
-    try {
-      const res = await apiv3Get(`/user-groups/${this.props.userGroupId}/pages`, {
-        limit,
-        offset,
-      });
-      const { total, pages } = res.data;
-
-      this.setState({
-        total,
-        activePage: pageNum,
-        currentPages: pages,
-      });
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }
-
-  render() {
-    const { t, adminUserGroupDetailContainer } = this.props;
-    const { relatedPages } = this.props;
-
-    return (
-      <Fragment>
-        <ul className="page-list-ul page-list-ul-flat mb-3">
-          {this.state.currentPages.map(page => <li key={page._id}><PageListItemS page={page} /></li>)}
-        </ul>
-        {relatedPages.length === 0 ? <p>{t('admin:user_group_management.no_pages')}</p> : (
-          <PaginationWrapper
-            activePage={this.state.activePage}
-            changePage={this.handlePageChange}
-            totalItemsCount={this.state.total}
-            pagingLimit={this.state.pagingLimit}
-            align="center"
-            size="sm"
-          />
-        )}
-      </Fragment>
-    );
-  }
-
-}
-
-UserGroupPageList.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  adminUserGroupDetailContainer: PropTypes.instanceOf(AdminUserGroupDetailContainer).isRequired,
-  userGroupId: PropTypes.string.isRequired,
-  relatedPages: PropTypes.arrayOf(IPageHasId),
-};
-
-const UserGroupPageListWrapperFC = (props) => {
-  const { t } = useTranslation();
-  const { userGroupId, relatedPages } = props;
-
-
-  if (userGroupId == null || relatedPages == null) {
-    return <></>;
-  }
-
-  return <UserGroupPageList t={t} userGroupId={userGroupId} relatedPages={relatedPages} {...props} />;
-};
-
-/**
- * Wrapper component for using unstated
- */
-const UserGroupPageListWrapper = withUnstatedContainers(UserGroupPageListWrapperFC, [AdminUserGroupDetailContainer]);
-
-export default UserGroupPageListWrapper;

+ 69 - 0
packages/app/src/components/Admin/UserGroupDetail/UserGroupPageList.tsx

@@ -0,0 +1,69 @@
+import React, { useEffect, useState, useCallback } from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+import { toastError } from '~/client/util/apiNotification';
+import { apiv3Get } from '~/client/util/apiv3-client';
+import { IPageHasId } from '~/interfaces/page';
+
+import PageListItemS from '../../PageList/PageListItemS';
+import PaginationWrapper from '../../PaginationWrapper';
+
+const pagingLimit = 10;
+
+type Props = {
+  userGroupId: string,
+  relatedPages?: IPageHasId[],
+}
+
+const UserGroupPageList = (props: Props): JSX.Element => {
+  const { t } = useTranslation();
+  const { userGroupId, relatedPages } = props;
+
+  const [currentPages, setCurrentPages] = useState<IPageHasId[]>([]);
+  const [activePage, setActivePage] = useState(1);
+  const [total, setTotal] = useState(0);
+
+  const handlePageChange = useCallback(async(pageNum) => {
+    const offset = (pageNum - 1) * pagingLimit;
+
+    try {
+      const res = await apiv3Get(`/user-groups/${userGroupId}/pages`, {
+        limit: pagingLimit,
+        offset,
+      });
+      const { total, pages } = res.data;
+
+      setTotal(total);
+      setActivePage(pageNum);
+      setCurrentPages(pages);
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [userGroupId]);
+
+  useEffect(() => {
+    handlePageChange(activePage);
+  }, [activePage, handlePageChange]);
+
+  return (
+    <>
+      <ul className="page-list-ul page-list-ul-flat mb-3">
+        {currentPages.map(page => <li key={page._id}><PageListItemS page={page} /></li>)}
+      </ul>
+      {relatedPages != null && relatedPages.length === 0 ? <p>{t('admin:user_group_management.no_pages')}</p> : (
+        <PaginationWrapper
+          activePage={activePage}
+          changePage={handlePageChange}
+          totalItemsCount={total}
+          pagingLimit={pagingLimit}
+          align="center"
+          size="sm"
+        />
+      )}
+    </>
+  );
+};
+
+export default UserGroupPageList;

+ 0 - 133
packages/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.jsx

@@ -1,133 +0,0 @@
-import React from 'react';
-
-import { UserPicture } from '@growi/ui';
-import dateFnsFormat from 'date-fns/format';
-import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
-
-import AdminUserGroupDetailContainer from '~/client/services/AdminUserGroupDetailContainer';
-import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import Xss from '~/services/xss';
-import { useSWRxMyUserGroupRelations } from '~/stores/user-group';
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
-class UserGroupUserTable extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.xss = new Xss();
-
-    this.removeUser = this.removeUser.bind(this);
-  }
-
-  async removeUser(username) {
-    try {
-      await this.props.adminUserGroupDetailContainer.removeUserByUsername(username);
-      toastSuccess(`Removed "${this.xss.process(username)}" from "${this.xss.process(this.props.adminUserGroupDetailContainer.state.userGroup.name)}"`);
-    }
-    catch (err) {
-      // eslint-disable-next-line max-len
-      toastError(new Error(`Unable to remove "${this.xss.process(username)}" from "${this.xss.process(this.props.adminUserGroupDetailContainer.state.userGroup.name)}"`));
-    }
-  }
-
-  render() {
-    const { t, adminUserGroupDetailContainer, userGroupRelations } = this.props;
-
-    return (
-      <table className="table table-bordered table-user-list">
-        <thead>
-          <tr>
-            <th width="100px">#</th>
-            <th>
-              {t('username')}
-            </th>
-            <th>{t('Name')}</th>
-            <th width="100px">{t('Created')}</th>
-            <th width="160px">{t('Last_Login')}</th>
-            <th width="70px"></th>
-          </tr>
-        </thead>
-        <tbody>
-          {userGroupRelations != null && userGroupRelations.map((relation) => {
-            const { relatedUser } = relation;
-
-            return (
-              <tr key={relation._id}>
-                <td>
-                  <UserPicture user={relatedUser} className="picture rounded-circle" />
-                </td>
-                <td>
-                  <strong>{relatedUser.username}</strong>
-                </td>
-                <td>{relatedUser.name}</td>
-                <td>{relatedUser.createdAt ? dateFnsFormat(new Date(relatedUser.createdAt), 'yyyy-MM-dd') : ''}</td>
-                <td>{relatedUser.lastLoginAt ? dateFnsFormat(new Date(relatedUser.lastLoginAt), 'yyyy-MM-dd HH:mm:ss') : ''}</td>
-                <td>
-                  <div className="btn-group admin-user-menu">
-                    <button
-                      type="button"
-                      id={`admin-group-menu-button-${relatedUser._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-${relatedUser._id}`}>
-                      <button
-                        className="dropdown-item"
-                        type="button"
-                        onClick={() => {
-                          return this.removeUser(relatedUser.username);
-                        }}
-                      >
-                        <i className="icon-fw icon-user-unfollow"></i> {t('admin:user_group_management.remove_from_group')}
-                      </button>
-                    </div>
-                  </div>
-                </td>
-              </tr>
-            );
-          })}
-
-          <tr>
-            <td></td>
-            <td className="text-center">
-              <button className="btn btn-outline-secondary" type="button" onClick={adminUserGroupDetailContainer.openUserGroupUserModal}>
-                <i className="ti ti-plus"></i>
-              </button>
-            </td>
-            <td></td>
-            <td></td>
-            <td></td>
-            <td></td>
-          </tr>
-
-        </tbody>
-      </table>
-    );
-  }
-
-}
-
-UserGroupUserTable.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  adminUserGroupDetailContainer: PropTypes.instanceOf(AdminUserGroupDetailContainer).isRequired,
-  userGroupRelations: PropTypes.object,
-};
-
-const UserGroupUserTableWrapperFC = (props) => {
-  const { t } = useTranslation();
-  const { data: myUserGroupRelations } = useSWRxMyUserGroupRelations();
-
-  return <UserGroupUserTable t={t} userGroupRelations={myUserGroupRelations} {...props} />;
-};
-
-/**
- * Wrapper component for using unstated
- */
-const UserGroupUserTableWrapper = withUnstatedContainers(UserGroupUserTableWrapperFC, [AdminUserGroupDetailContainer]);
-
-export default UserGroupUserTableWrapper;

+ 126 - 0
packages/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.tsx

@@ -0,0 +1,126 @@
+import React, { useEffect } from 'react';
+
+import { UserPicture } from '@growi/ui';
+import dateFnsFormat from 'date-fns/format';
+import { useTranslation } from 'next-i18next';
+
+
+import AdminUserGroupDetailContainer from '~/client/services/AdminUserGroupDetailContainer';
+import { toastSuccess, toastError } from '~/client/util/apiNotification';
+import { IUserGroupHasId, IUserGroupRelation } from '~/interfaces/user';
+import Xss from '~/services/xss';
+import { useSWRxUserGroupRelations } from '~/stores/user-group';
+
+import { withUnstatedContainers } from '../../UnstatedUtils';
+
+type Props = {
+  adminUserGroupDetailContainer: AdminUserGroupDetailContainer
+  userGroupRelations: IUserGroupRelation[],
+  userGroup: IUserGroupHasId,
+}
+
+const UserGroupUserTable = (props: Props) => {
+  const { t } = useTranslation();
+
+  const { adminUserGroupDetailContainer, userGroup } = props;
+  const { data: userGroupRelations } = useSWRxUserGroupRelations(userGroup._id);
+
+  let xss;
+
+  useEffect(() => {
+    xss = new Xss();
+  }, []);
+
+
+  const removeUser = async(username: string) => {
+    try {
+      await props.adminUserGroupDetailContainer.removeUserByUsername(username);
+      toastSuccess(`Removed "${xss.process(username)}" from "${xss.process(props.adminUserGroupDetailContainer.state.userGroup.name)}"`);
+    }
+    catch (err) {
+      // eslint-disable-next-line max-len
+      toastError(new Error(`Unable to remove "${xss.process(username)}" from "${xss.process(props.adminUserGroupDetailContainer.state.userGroup.name)}"`));
+    }
+  };
+
+
+  return (
+    <table className="table table-bordered table-user-list">
+      <thead>
+        <tr>
+          <th style={{ width: '100px' }}>#</th>
+          <th>
+            {t('username')}
+          </th>
+          <th>{t('Name')}</th>
+          <th style={{ width: '100px' }}>{t('Created')}</th>
+          <th style={{ width: '160px' }}>{t('Last_Login')}</th>
+          <th style={{ width: '70px' }}></th>
+        </tr>
+      </thead>
+      <tbody>
+        {userGroupRelations != null && userGroupRelations.map((relation) => {
+          const { relatedUser } = relation;
+
+          return (
+            <tr key={relation._id}>
+              <td>
+                <UserPicture user={relatedUser} className="picture rounded-circle" />
+              </td>
+              <td>
+                <strong>{relatedUser.username}</strong>
+              </td>
+              <td>{relatedUser.name}</td>
+              <td>{relatedUser.createdAt ? dateFnsFormat(new Date(relatedUser.createdAt), 'yyyy-MM-dd') : ''}</td>
+              <td>{relatedUser.lastLoginAt ? dateFnsFormat(new Date(relatedUser.lastLoginAt), 'yyyy-MM-dd HH:mm:ss') : ''}</td>
+              <td>
+                <div className="btn-group admin-user-menu">
+                  <button
+                    type="button"
+                    id={`admin-group-menu-button-${relatedUser._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-${relatedUser._id}`}>
+                    <button
+                      className="dropdown-item"
+                      type="button"
+                      onClick={() => {
+                        return removeUser(relatedUser.username);
+                      }}
+                    >
+                      <i className="icon-fw icon-user-unfollow"></i> {t('admin:user_group_management.remove_from_group')}
+                    </button>
+                  </div>
+                </div>
+              </td>
+            </tr>
+          );
+        })}
+
+        <tr>
+          <td></td>
+          <td className="text-center">
+            <button className="btn btn-outline-secondary" type="button" onClick={adminUserGroupDetailContainer.openUserGroupUserModal}>
+              <i className="ti ti-plus"></i>
+            </button>
+          </td>
+          <td></td>
+          <td></td>
+          <td></td>
+          <td></td>
+        </tr>
+
+      </tbody>
+    </table>
+  );
+};
+
+/**
+ * Wrapper component for using unstated
+ */
+const UserGroupUserTableWrapper = withUnstatedContainers(UserGroupUserTable, [AdminUserGroupDetailContainer]);
+
+export default UserGroupUserTableWrapper;

+ 5 - 1
packages/app/src/interfaces/user-group-response.ts

@@ -1,5 +1,5 @@
-import { IUserGroupHasId, IUserGroupRelationHasId } from './user';
 import { IPageHasId } from './page';
+import { IUserGroupHasId, IUserGroupRelationHasId, IUserGroupRelationHasIdPopulatedUser } from './user';
 
 export type UserGroupResult = {
   userGroup: IUserGroupHasId,
@@ -18,6 +18,10 @@ export type UserGroupRelationListResult = {
   userGroupRelations: IUserGroupRelationHasId[],
 };
 
+export type UserGroupRelationsResult = {
+  userGroupRelations: IUserGroupRelationHasIdPopulatedUser[],
+};
+
 export type UserGroupPagesResult = {
   pages: IPageHasId[],
 }

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

@@ -1,3 +1,3 @@
 export type {
-  IUser, IUserGroupRelation, IUserGroup, IUserHasId, IUserGroupHasId, IUserGroupRelationHasId,
+  IUser, IUserGroupRelation, IUserGroup, IUserHasId, IUserGroupHasId, IUserGroupRelationHasId, IUserGroupRelationHasIdPopulatedUser,
 } from '@growi/core';

+ 0 - 1
packages/app/src/server/routes/apiv3/user-group.js

@@ -765,7 +765,6 @@ module.exports = (crowi) => {
     try {
       const userGroup = await UserGroup.findById(id);
       const userGroupRelations = await UserGroupRelation.findAllRelationForUserGroup(userGroup);
-
       return res.apiv3({ userGroupRelations });
     }
     catch (err) {

+ 4 - 4
packages/app/src/stores/user-group.tsx

@@ -4,9 +4,9 @@ import useSWRImmutable from 'swr/immutable';
 import { apiGet } from '~/client/util/apiv1-client';
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { IPageHasId } from '~/interfaces/page';
-import { IUserGroupHasId, IUserGroupRelationHasId } from '~/interfaces/user';
+import { IUserGroupHasId, IUserGroupRelationHasId, IUserGroupRelationHasIdPopulatedUser } from '~/interfaces/user';
 import {
-  UserGroupResult, UserGroupListResult, ChildUserGroupListResult, UserGroupRelationListResult,
+  UserGroupResult, UserGroupListResult, ChildUserGroupListResult, UserGroupRelationListResult, UserGroupRelationsResult,
   UserGroupPagesResult, SelectableParentUserGroupsResult, SelectableUserChildGroupsResult, AncestorUserGroupsResult,
 } from '~/interfaces/user-group-response';
 
@@ -51,10 +51,10 @@ export const useSWRxChildUserGroupList = (
   );
 };
 
-export const useSWRxUserGroupRelations = (groupId: string): SWRResponse<IUserGroupRelationHasId[], Error> => {
+export const useSWRxUserGroupRelations = (groupId: string): SWRResponse<IUserGroupRelationHasIdPopulatedUser[], Error> => {
   return useSWRImmutable(
     groupId != null ? [`/user-groups/${groupId}/user-group-relations`] : null,
-    endpoint => apiv3Get<UserGroupRelationListResult>(endpoint).then(result => result.data.userGroupRelations),
+    endpoint => apiv3Get<UserGroupRelationsResult>(endpoint).then(result => result.data.userGroupRelations),
   );
 };
 

+ 9 - 0
packages/core/src/interfaces/user.ts

@@ -17,6 +17,8 @@ export type IUser = {
   isEmailPublished: boolean,
   lang: Lang,
   slackMemberId?: string,
+  createdAt: Date,
+  lastLoginAt: Date,
 }
 
 export type IUserGroupRelation = {
@@ -35,3 +37,10 @@ export type IUserGroup = {
 export type IUserHasId = IUser & HasObjectId;
 export type IUserGroupHasId = IUserGroup & HasObjectId;
 export type IUserGroupRelationHasId = IUserGroupRelation & HasObjectId;
+
+
+export type IUserGroupRelationHasIdPopulatedUser = {
+  relatedGroup: Ref<IUserGroup>,
+  relatedUser: IUser & HasObjectId,
+  createdAt: Date,
+} & HasObjectId;