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