UserManagement.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import React, {
  2. useEffect, useState, useRef, useCallback,
  3. } from 'react';
  4. import { useTranslation } from 'next-i18next';
  5. import Link from 'next/link';
  6. import AdminUsersContainer from '~/client/services/AdminUsersContainer';
  7. import { toastError } from '~/client/util/toastr';
  8. import PaginationWrapper from '../PaginationWrapper';
  9. import { withUnstatedContainers } from '../UnstatedUtils';
  10. import InviteUserControl from './Users/InviteUserControl';
  11. import PasswordResetModal from './Users/PasswordResetModal';
  12. import UserStatisticsTable from './Users/UserStatisticsTable';
  13. import UserTable from './Users/UserTable';
  14. import styles from './UserManagement.module.scss';
  15. type UserManagementProps = {
  16. adminUsersContainer: AdminUsersContainer
  17. }
  18. const UserManagement = (props: UserManagementProps) => {
  19. const { t } = useTranslation('admin');
  20. const { adminUsersContainer } = props;
  21. const [isNotifyCommentShow, setIsNotifyCommentShow] = useState(false);
  22. const inputRef = useRef<HTMLInputElement>(null);
  23. const pagingHandler = useCallback(async(selectedPage: number) => {
  24. try {
  25. await adminUsersContainer.retrieveUsersByPagingNum(selectedPage);
  26. }
  27. catch (err) {
  28. toastError(err);
  29. }
  30. }, [adminUsersContainer]);
  31. // for Next routing
  32. useEffect(() => {
  33. pagingHandler(1);
  34. adminUsersContainer.retrieveUserStatistics();
  35. }, [pagingHandler, adminUsersContainer]);
  36. const validateToggleStatus = (statusType: string) => {
  37. return (adminUsersContainer.isSelected(statusType)) ? (
  38. adminUsersContainer.state.selectedStatusList.size > 1
  39. )
  40. : (
  41. true
  42. );
  43. };
  44. const clickHandler = (statusType: string) => {
  45. if (!validateToggleStatus(statusType)) {
  46. return setIsNotifyCommentShow(true);
  47. }
  48. if (isNotifyCommentShow) {
  49. setIsNotifyCommentShow(false);
  50. }
  51. adminUsersContainer.handleClick(statusType);
  52. };
  53. const resetButtonClickHandler = useCallback(async() => {
  54. try {
  55. await adminUsersContainer.resetAllChanges();
  56. setIsNotifyCommentShow(false);
  57. if (inputRef.current != null) {
  58. inputRef.current.value = '';
  59. }
  60. }
  61. catch (err) {
  62. toastError(err);
  63. }
  64. }, [adminUsersContainer]);
  65. const changeSearchTextHandler = useCallback(async(e: React.FormEvent<HTMLInputElement>) => {
  66. await adminUsersContainer.handleChangeSearchText(e?.currentTarget.value);
  67. }, [adminUsersContainer]);
  68. const renderCheckbox = (status: string, statusLabel: string, statusColor: string) => {
  69. return (
  70. <div className={`form-check form-check-${statusColor} me-2`}>
  71. <input
  72. className="form-check-input"
  73. type="checkbox"
  74. id={`c_${status}`}
  75. checked={adminUsersContainer.isSelected(status)}
  76. onChange={() => clickHandler(status)}
  77. />
  78. <label className="form-label form-check-label" htmlFor={`c_${status}`}>
  79. <span className={`badge text-bg-${statusColor} d-inline-block vt mt-1`}>
  80. {statusLabel}
  81. </span>
  82. </label>
  83. </div>
  84. );
  85. };
  86. const pager = (
  87. <div className="my-3">
  88. <PaginationWrapper
  89. activePage={adminUsersContainer.state.activePage}
  90. changePage={pagingHandler}
  91. totalItemsCount={adminUsersContainer.state.totalUsers}
  92. pagingLimit={adminUsersContainer.state.pagingLimit}
  93. align="center"
  94. size="sm"
  95. />
  96. </div>
  97. );
  98. return (
  99. <div data-testid="admin-users">
  100. { adminUsersContainer.state.userForPasswordResetModal != null
  101. && (
  102. <PasswordResetModal
  103. isOpen={adminUsersContainer.state.isPasswordResetModalShown}
  104. onClose={adminUsersContainer.hidePasswordResetModal}
  105. userForPasswordResetModal={adminUsersContainer.state.userForPasswordResetModal}
  106. />
  107. ) }
  108. <p>
  109. <InviteUserControl />
  110. <Link
  111. href="/admin/users/external-accounts"
  112. className="btn btn-outline-secondary ms-2"
  113. role="button"
  114. >
  115. <span className="material-symbols-outlined" aria-hidden="true">person_add</span>
  116. {t('admin:user_management.external_account')}
  117. </Link>
  118. </p>
  119. <h2>{t('user_management.user_management')}</h2>
  120. <UserStatisticsTable
  121. userStatistics={adminUsersContainer.state.userStatistics}
  122. />
  123. <div className="border-top border-bottom">
  124. <div className="row d-flex justify-content-start align-items-center my-2">
  125. <div className="col-md-3 d-flex align-items-center my-2">
  126. <span className="material-symbols-outlined">search</span>
  127. <span className={`search-typeahead ${styles['search-typeahead']}`}>
  128. <input
  129. className="w-100"
  130. type="text"
  131. ref={inputRef}
  132. onChange={changeSearchTextHandler}
  133. />
  134. {
  135. adminUsersContainer.state.searchText.length > 0
  136. ? (
  137. <span
  138. className="material-symbols-outlined me-1 search-clear"
  139. onClick={async() => {
  140. await adminUsersContainer.clearSearchText();
  141. if (inputRef.current != null) {
  142. inputRef.current.value = '';
  143. }
  144. }}
  145. >cancel
  146. </span>
  147. )
  148. : ''
  149. }
  150. </span>
  151. </div>
  152. <div className="offset-md-1 col-md-6 my-2">
  153. <div>
  154. {renderCheckbox('all', 'All', 'primary')}
  155. {renderCheckbox('registered', 'Approval Pending', 'info')}
  156. {renderCheckbox('active', 'Active', 'success')}
  157. {renderCheckbox('suspended', 'Suspended', 'warning')}
  158. {renderCheckbox('invited', 'Invited', 'secondary')}
  159. </div>
  160. <div>
  161. { isNotifyCommentShow && <span className="text-warning">{t('admin:user_management.click_twice_same_checkbox')}</span> }
  162. </div>
  163. </div>
  164. <div className="col-md-2 my-2">
  165. <button
  166. type="button"
  167. className="btn btn-outline-secondary btn-sm"
  168. onClick={resetButtonClickHandler}
  169. >
  170. <span className="material-symbols-outlined">refresh</span>
  171. {t('commons:Reset')}
  172. </button>
  173. </div>
  174. </div>
  175. </div>
  176. {pager}
  177. <UserTable />
  178. {pager}
  179. </div>
  180. );
  181. };
  182. const UserManagementWrapper = withUnstatedContainers(UserManagement, [AdminUsersContainer]);
  183. export default UserManagementWrapper;