UserTable.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import React, { useCallback } from 'react';
  2. import type { IUserHasId } from '@growi/core';
  3. import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
  4. import dateFnsFormat from 'date-fns/format';
  5. import { useTranslation } from 'next-i18next';
  6. import AdminUsersContainer from '~/client/services/AdminUsersContainer';
  7. import { withUnstatedContainers } from '../../UnstatedUtils';
  8. import { SortIcons } from './SortIcons';
  9. import UserMenu from './UserMenu';
  10. type UserTableProps = {
  11. adminUsersContainer: AdminUsersContainer,
  12. }
  13. const UserTable = (props: UserTableProps) => {
  14. const { t } = useTranslation('admin');
  15. const { adminUsersContainer } = props;
  16. const getUserStatusLabel = (userStatus: number) => {
  17. let additionalClassName = 'badge-info';
  18. let text = 'Approval Pending';
  19. switch (userStatus) {
  20. case 1:
  21. additionalClassName = 'badge-info';
  22. text = 'Approval Pending';
  23. break;
  24. case 2:
  25. additionalClassName = 'badge-success';
  26. text = 'Active';
  27. break;
  28. case 3:
  29. additionalClassName = 'badge-warning';
  30. text = 'Suspended';
  31. break;
  32. case 4:
  33. additionalClassName = 'badge-danger';
  34. text = 'Deleted';
  35. break;
  36. case 5:
  37. additionalClassName = 'badge-pink';
  38. text = 'Invited';
  39. break;
  40. }
  41. return (
  42. <span className={`badge badge-pill ${additionalClassName}`}>
  43. {text}
  44. </span>
  45. );
  46. };
  47. const sortIconsClickedHandler = useCallback(async(sort: string, sortOrder: string) => {
  48. const isAsc = sortOrder === 'asc';
  49. await adminUsersContainer.sort(sort, isAsc);
  50. }, [adminUsersContainer]);
  51. const isCurrentSortOrderAsc = adminUsersContainer.state.sortOrder === 'asc';
  52. return (
  53. <div className="table-responsive text-nowrap h-100">
  54. <table className="table table-default table-bordered table-user-list">
  55. <thead>
  56. <tr>
  57. <th style={{ width: '100px' }}>#</th>
  58. <th>
  59. <div className="d-flex align-items-center">
  60. <div className="mr-3">
  61. {t('user_management.status')}
  62. </div>
  63. <SortIcons
  64. isSelected={adminUsersContainer.state.sort === 'status'}
  65. isAsc={isCurrentSortOrderAsc}
  66. onClick={sortOrder => sortIconsClickedHandler('status', sortOrder)}
  67. />
  68. </div>
  69. </th>
  70. <th>
  71. <div className="d-flex align-items-center">
  72. <div className="mr-3">
  73. <code>username</code>
  74. </div>
  75. <SortIcons
  76. isSelected={adminUsersContainer.state.sort === 'username'}
  77. isAsc={isCurrentSortOrderAsc}
  78. onClick={sortOrder => sortIconsClickedHandler('username', sortOrder)}
  79. />
  80. </div>
  81. </th>
  82. <th>
  83. <div className="d-flex align-items-center">
  84. <div className="mr-3">
  85. {t('Name')}
  86. </div>
  87. <SortIcons
  88. isSelected={adminUsersContainer.state.sort === 'name'}
  89. isAsc={isCurrentSortOrderAsc}
  90. onClick={sortOrder => sortIconsClickedHandler('name', sortOrder)}
  91. />
  92. </div>
  93. </th>
  94. <th>
  95. <div className="d-flex align-items-center">
  96. <div className="mr-3">
  97. {t('Email')}
  98. </div>
  99. <SortIcons
  100. isSelected={adminUsersContainer.state.sort === 'email'}
  101. isAsc={isCurrentSortOrderAsc}
  102. onClick={sortOrder => sortIconsClickedHandler('email', sortOrder)}
  103. />
  104. </div>
  105. </th>
  106. <th style={{ width: '100px' }}>
  107. <div className="d-flex align-items-center">
  108. <div className="mr-3">
  109. {t('Created')}
  110. </div>
  111. <SortIcons
  112. isSelected={adminUsersContainer.state.sort === 'createdAt'}
  113. isAsc={isCurrentSortOrderAsc}
  114. onClick={sortOrder => sortIconsClickedHandler('createdAt', sortOrder)}
  115. />
  116. </div>
  117. </th>
  118. <th style={{ width: '150px' }}>
  119. <div className="d-flex align-items-center">
  120. <div className="mr-3">
  121. {t('last_login')}
  122. </div>
  123. <SortIcons
  124. isSelected={adminUsersContainer.state.sort === 'lastLoginAt'}
  125. isAsc={isCurrentSortOrderAsc}
  126. onClick={sortOrder => sortIconsClickedHandler('lastLoginAt', sortOrder)}
  127. />
  128. </div>
  129. </th>
  130. <th style={{ width: '70px' }}></th>
  131. </tr>
  132. </thead>
  133. <tbody>
  134. { adminUsersContainer.state.users.map((user: IUserHasId) => {
  135. return (
  136. <tr data-testid="user-table-tr" key={user._id}>
  137. <td>
  138. <UserPicture user={user} />
  139. </td>
  140. <td>
  141. {getUserStatusLabel(user.status)}
  142. {(user.admin) && (
  143. <span className="badge badge-indigo badge-pill ml-2">
  144. {t('admin:user_management.user_table.administrator')}
  145. </span>
  146. )}
  147. {(user.readOnly) && (
  148. <span className="badge badge-light badge-pill ml-2">
  149. {t('admin:user_management.user_table.read_only_user')}
  150. </span>
  151. )}
  152. </td>
  153. <td>
  154. <strong>{user.username}</strong>
  155. </td>
  156. <td>{user.name}</td>
  157. <td>{user.email}</td>
  158. <td>{dateFnsFormat(new Date(user.createdAt), 'yyyy-MM-dd')}</td>
  159. <td>
  160. {user.lastLoginAt && <span>{dateFnsFormat(new Date(user.lastLoginAt), 'yyyy-MM-dd HH:mm')}</span>}
  161. </td>
  162. <td>
  163. <UserMenu user={user} />
  164. </td>
  165. </tr>
  166. );
  167. }) }
  168. </tbody>
  169. </table>
  170. </div>
  171. );
  172. };
  173. const UserTableWrapper = withUnstatedContainers(UserTable, [AdminUsersContainer]);
  174. export default UserTableWrapper;