UserTable.tsx 6.5 KB

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