UserTable.tsx 6.9 KB

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