UserManagement.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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/apiNotification';
  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} mr-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 badge-pill badge-${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 href="/admin/users/external-accounts" prefetch={false}>
  109. <a className="btn btn-outline-secondary ml-2" role="button">
  110. <i className="icon-user-follow mr-1" aria-hidden="true"></i>
  111. {t('admin:user_management.external_account')}
  112. </a>
  113. </Link>
  114. </p>
  115. <h2>{t('user_management.user_management')}</h2>
  116. <div className="border-top border-bottom">
  117. <div className="row d-flex justify-content-start align-items-center my-2">
  118. <div className="col-md-3 d-flex align-items-center my-2">
  119. <i className="icon-magnifier mr-1"></i>
  120. <span className={`search-typeahead ${styles['search-typeahead']}`}>
  121. <input
  122. className="w-100"
  123. type="text"
  124. ref={inputRef}
  125. onChange={changeSearchTextHandler}
  126. />
  127. {
  128. adminUsersContainer.state.searchText.length > 0
  129. ? (
  130. <i
  131. className="icon-close search-clear"
  132. onClick={async() => {
  133. await adminUsersContainer.clearSearchText();
  134. if (inputRef.current != null) {
  135. inputRef.current.value = '';
  136. }
  137. }}
  138. />
  139. )
  140. : ''
  141. }
  142. </span>
  143. </div>
  144. <div className="offset-md-1 col-md-6 my-2">
  145. <div className="form-inline">
  146. {renderCheckbox('all', 'All', 'secondary')}
  147. {renderCheckbox('registered', 'Approval Pending', 'info')}
  148. {renderCheckbox('active', 'Active', 'success')}
  149. {renderCheckbox('suspended', 'Suspended', 'warning')}
  150. {renderCheckbox('invited', 'Invited', 'pink')}
  151. </div>
  152. <div>
  153. { isNotifyCommentShow && <span className="text-warning">{t('admin:user_management.click_twice_same_checkbox')}</span> }
  154. </div>
  155. </div>
  156. <div className="col-md-2 my-2">
  157. <button
  158. type="button"
  159. className="btn btn-outline-secondary btn-sm"
  160. onClick={resetButtonClickHandler}
  161. >
  162. <span className="icon-refresh mr-1"></span>
  163. {t('commons:Reset')}
  164. </button>
  165. </div>
  166. </div>
  167. </div>
  168. {pager}
  169. <UserTable />
  170. {pager}
  171. </div>
  172. );
  173. };
  174. const UserManagementWrapper = withUnstatedContainers(UserManagement, [AdminUsersContainer]);
  175. export default UserManagementWrapper;