TeamMembers.tsx 4.9 KB

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