TeamMembers.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import React, { PureComponent } from 'react';
  2. import { connect } from 'react-redux';
  3. import { SlideDown } from 'app/core/components/Animations/SlideDown';
  4. import { UserPicker } from 'app/core/components/Select/UserPicker';
  5. import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
  6. import { TeamMember, User } from 'app/types';
  7. import { addTeamMember, setSearchMemberQuery } from './state/actions';
  8. import { getSearchMemberQuery, isSignedInUserTeamAdmin } from './state/selectors';
  9. import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
  10. import { WithFeatureToggle } from 'app/core/components/WithFeatureToggle';
  11. import { config } from 'app/core/config';
  12. import { contextSrv, User as SignedInUser } from 'app/core/services/context_srv';
  13. import TeamMemberRow from './TeamMemberRow';
  14. export interface Props {
  15. members: TeamMember[];
  16. searchMemberQuery: string;
  17. addTeamMember: typeof addTeamMember;
  18. setSearchMemberQuery: typeof setSearchMemberQuery;
  19. syncEnabled: boolean;
  20. editorsCanAdmin?: boolean;
  21. signedInUser?: SignedInUser;
  22. }
  23. export interface State {
  24. isAdding: boolean;
  25. newTeamMember?: User;
  26. }
  27. export class TeamMembers extends PureComponent<Props, State> {
  28. constructor(props) {
  29. super(props);
  30. this.state = { isAdding: false, newTeamMember: null };
  31. }
  32. onSearchQueryChange = (value: string) => {
  33. this.props.setSearchMemberQuery(value);
  34. };
  35. onToggleAdding = () => {
  36. this.setState({ isAdding: !this.state.isAdding });
  37. };
  38. onUserSelected = (user: User) => {
  39. this.setState({ newTeamMember: user });
  40. };
  41. onAddUserToTeam = async () => {
  42. this.props.addTeamMember(this.state.newTeamMember.id);
  43. this.setState({ newTeamMember: null });
  44. };
  45. renderLabels(labels: string[]) {
  46. if (!labels) {
  47. return <td />;
  48. }
  49. return (
  50. <td>
  51. {labels.map(label => (
  52. <TagBadge key={label} label={label} removeIcon={false} count={0} onClick={() => {}} />
  53. ))}
  54. </td>
  55. );
  56. }
  57. render() {
  58. const { isAdding } = this.state;
  59. const { searchMemberQuery, members, syncEnabled, editorsCanAdmin, signedInUser } = this.props;
  60. const isTeamAdmin = isSignedInUserTeamAdmin({ members, editorsCanAdmin, signedInUser });
  61. return (
  62. <div>
  63. <div className="page-action-bar">
  64. <div className="gf-form gf-form--grow">
  65. <FilterInput
  66. labelClassName="gf-form--has-input-icon gf-form--grow"
  67. inputClassName="gf-form-input"
  68. placeholder="Search members"
  69. value={searchMemberQuery}
  70. onChange={this.onSearchQueryChange}
  71. />
  72. </div>
  73. <div className="page-action-bar__spacer" />
  74. <button
  75. className="btn btn-primary pull-right"
  76. onClick={this.onToggleAdding}
  77. disabled={isAdding || !isTeamAdmin}
  78. >
  79. Add member
  80. </button>
  81. </div>
  82. <SlideDown in={isAdding}>
  83. <div className="cta-form">
  84. <button className="cta-form__close btn btn-transparent" onClick={this.onToggleAdding}>
  85. <i className="fa fa-close" />
  86. </button>
  87. <h5>Add team member</h5>
  88. <div className="gf-form-inline">
  89. <UserPicker onSelected={this.onUserSelected} className="min-width-30" />
  90. {this.state.newTeamMember && (
  91. <button className="btn btn-primary gf-form-btn" type="submit" onClick={this.onAddUserToTeam}>
  92. Add to team
  93. </button>
  94. )}
  95. </div>
  96. </div>
  97. </SlideDown>
  98. <div className="admin-list-table">
  99. <table className="filter-table filter-table--hover form-inline">
  100. <thead>
  101. <tr>
  102. <th />
  103. <th>Name</th>
  104. <th>Email</th>
  105. <WithFeatureToggle featureToggle={editorsCanAdmin}>
  106. <th>Permission</th>
  107. </WithFeatureToggle>
  108. {syncEnabled && <th />}
  109. <th style={{ width: '1%' }} />
  110. </tr>
  111. </thead>
  112. <tbody>
  113. {members &&
  114. members.map(member => (
  115. <TeamMemberRow
  116. key={member.userId}
  117. member={member}
  118. syncEnabled={syncEnabled}
  119. editorsCanAdmin={editorsCanAdmin}
  120. signedInUserIsTeamAdmin={isTeamAdmin}
  121. />
  122. ))}
  123. </tbody>
  124. </table>
  125. </div>
  126. </div>
  127. );
  128. }
  129. }
  130. function mapStateToProps(state) {
  131. return {
  132. searchMemberQuery: getSearchMemberQuery(state.team),
  133. editorsCanAdmin: config.editorsCanAdmin, // this makes the feature toggle mockable/controllable from tests,
  134. signedInUser: contextSrv.user, // this makes the feature toggle mockable/controllable from tests,
  135. };
  136. }
  137. const mapDispatchToProps = {
  138. addTeamMember,
  139. setSearchMemberQuery,
  140. };
  141. export default connect(
  142. mapStateToProps,
  143. mapDispatchToProps
  144. )(TeamMembers);