UserManagement.tsx 6.3 KB

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