UserGroupUserFormByInput.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import type { FC, KeyboardEvent } from 'react';
  2. import React, { useState } from 'react';
  3. import type { IUserGroupHasId, IUserHasId } from '@growi/core';
  4. import { UserPicture } from '@growi/ui/dist/components';
  5. import { useTranslation } from 'next-i18next';
  6. import { AsyncTypeahead } from 'react-bootstrap-typeahead';
  7. import { toastError, toastSuccess } from '~/client/util/toastr';
  8. import type { SearchType } from '~/interfaces/user-group';
  9. type Props = {
  10. userGroup: IUserGroupHasId;
  11. onClickAddUserBtn: (username: string) => Promise<void>;
  12. onSearchApplicableUsers: (searchWord: string) => Promise<IUserHasId[]>;
  13. isAlsoNameSearched: boolean;
  14. isAlsoMailSearched: boolean;
  15. searchType: SearchType;
  16. };
  17. export const UserGroupUserFormByInput: FC<Props> = (props) => {
  18. const {
  19. userGroup,
  20. onClickAddUserBtn,
  21. onSearchApplicableUsers,
  22. isAlsoNameSearched,
  23. isAlsoMailSearched,
  24. searchType,
  25. } = props;
  26. const { t } = useTranslation();
  27. const [inputUser, setInputUser] = useState<IUserHasId[]>([]);
  28. const [applicableUsers, setApplicableUsers] = useState<IUserHasId[]>([]);
  29. const [isLoading, setIsLoading] = useState(false);
  30. const [isSearchError, setIsSearchError] = useState(false);
  31. const addUserBySubmit = async () => {
  32. if (inputUser.length === 0) {
  33. return;
  34. }
  35. const userName = inputUser[0].username;
  36. try {
  37. await onClickAddUserBtn(userName);
  38. toastSuccess(`Added "${userName}" to "${userGroup.name}"`);
  39. setInputUser([]);
  40. } catch (err) {
  41. toastError(
  42. new Error(`Unable to add "${userName}" to "${userGroup.name}"`),
  43. );
  44. }
  45. };
  46. const searchApplicableUsers = async (keyword: string) => {
  47. try {
  48. const users = await onSearchApplicableUsers(keyword);
  49. setApplicableUsers(users);
  50. setIsLoading(false);
  51. } catch (err) {
  52. setIsSearchError(true);
  53. toastError(err);
  54. }
  55. };
  56. const handleChange = (inputUser: IUserHasId[]) => {
  57. setInputUser(inputUser);
  58. };
  59. const handleSearch = async (keyword: string) => {
  60. setIsLoading(true);
  61. await searchApplicableUsers(keyword);
  62. };
  63. const onKeyDown = (event: KeyboardEvent) => {
  64. if (event.key === 'Enter') {
  65. addUserBySubmit();
  66. }
  67. };
  68. const renderMenuItemChildren = (option: IUserHasId) => {
  69. const user = option;
  70. return (
  71. <>
  72. <UserPicture user={user} size="sm" noLink noTooltip />
  73. <strong className="ms-2">{user.username}</strong>
  74. {isAlsoNameSearched && <span className="ms-2">{user.name}</span>}
  75. {isAlsoMailSearched && <span className="ms-2">{user.email}</span>}
  76. </>
  77. );
  78. };
  79. return (
  80. <div className="row">
  81. <div className="col-8 pe-0">
  82. <AsyncTypeahead
  83. key={`${searchType}-${isAlsoNameSearched}-${isAlsoMailSearched}`} // The searched keywords are not re-searched, so re-rendered by key.
  84. id="name-typeahead-asynctypeahead"
  85. inputProps={{ autoComplete: 'off' }}
  86. isLoading={isLoading}
  87. labelKey={(user: IUserHasId) =>
  88. `${user.username} ${user.name} ${user.email}`
  89. }
  90. options={applicableUsers} // Search result
  91. onSearch={handleSearch}
  92. onChange={handleChange}
  93. onKeyDown={onKeyDown}
  94. minLength={1}
  95. searchText={
  96. isLoading ? 'Searching...' : isSearchError && 'Error on searching.'
  97. }
  98. renderMenuItemChildren={renderMenuItemChildren}
  99. align="left"
  100. clearButton
  101. />
  102. </div>
  103. <div className="col-2 ps-0">
  104. <button
  105. type="button"
  106. className="btn btn-success"
  107. disabled={inputUser.length === 0}
  108. onClick={addUserBySubmit}
  109. >
  110. {t('add')}
  111. </button>
  112. </div>
  113. </div>
  114. );
  115. };