Added notification for when leader is updated (#9674)

* Added notification for when leader is updated

* Abstracted challenge member search component

* Added challenge member search modal to challenge detail

* Added group search
This commit is contained in:
Keith Holliday
2017-12-14 12:12:43 -06:00
committed by GitHub
parent 54db84fddc
commit c28ec24c33
11 changed files with 162 additions and 33 deletions

View File

@@ -161,4 +161,19 @@ describe('GET /groups/:groupId/members', () => {
let resIds = res.concat(res2).map(member => member._id);
expect(resIds).to.eql(expectedIds.sort());
});
it('searches members', async () => {
let group = await generateGroup(user, {type: 'party', name: generateUUID()});
let usersToGenerate = [];
for (let i = 0; i < 2; i++) {
usersToGenerate.push(generateUser({party: {_id: group._id}}));
}
const usersCreated = await Promise.all(usersToGenerate);
const userToSearch = usersCreated[0].profile.name;
let res = await user.get(`/groups/party/members?search=${userToSearch}`);
expect(res.length).to.equal(1);
expect(res[0].profile.name).to.equal(userToSearch);
});
});

View File

@@ -33,10 +33,7 @@
.col-7.offset-5
span.view-progress
strong {{ $t('viewProgressOf') }}
b-dropdown.create-dropdown(text="Select a Participant")
input.form-control(type='text', v-model='searchTerm')
b-dropdown-item(v-for="member in memberResults", :key="member._id", @click="openMemberProgressModal(member._id)")
| {{ member.profile.name }}
member-search-dropdown(:text="$t('selectParticipant')", :members='members', :challengeId='challengeId', @member-selected='openMemberProgressModal')
span(v-if='isLeader || isAdmin')
b-dropdown.create-dropdown(:text="$t('addTaskToChallenge')", :variant="'success'")
b-dropdown-item(v-for="type in columns", :key="type", @click="createTask(type)")
@@ -51,7 +48,6 @@
v-on:taskEdited='taskEdited',
@taskDestroyed='taskDestroyed'
)
.row
task-column.col-12.col-sm-6(
v-for="column in columns",
@@ -185,6 +181,7 @@ import omit from 'lodash/omit';
import uuid from 'uuid';
import { mapState } from 'client/libs/store';
import memberSearchDropdown from 'client/components/members/memberSearchDropdown';
import closeChallengeModal from './closeChallengeModal';
import Column from '../tasks/column';
import TaskModal from '../tasks/taskModal';
@@ -211,6 +208,7 @@ export default {
leaveChallengeModal,
challengeModal,
challengeMemberProgressModal,
memberSearchDropdown,
TaskColumn: Column,
TaskModal,
},
@@ -388,8 +386,8 @@ export default {
updatedChallenge (eventData) {
Object.assign(this.challenge, eventData.challenge);
},
openMemberProgressModal (memberId) {
this.progressMemberId = memberId;
openMemberProgressModal (member) {
this.progressMemberId = member._id;
this.$root.$emit('bv::show::modal', 'challenge-member-modal');
},
async exportChallengeCsv () {

View File

@@ -10,10 +10,7 @@ div
.col-12
strong(v-once) {{$t('selectChallengeWinnersDescription')}}
.col-12
b-dropdown.create-dropdown(:text="winnerText")
input.form-control(type='text', v-model='searchTerm')
b-dropdown-item(v-for="member in memberResults", :key="member._id", @click="selectMember(member)")
| {{ member.profile.name }}
member-search-dropdown(:text='winnerText', :members='members', :challengeId='challengeId', @member-selected='selectMember')
.col-12
button.btn.btn-primary(v-once, @click='closeChallenge') {{$t('awardWinners')}}
.col-12
@@ -74,16 +71,16 @@ div
</style>
<script>
import challengeMemberSearchMixin from 'client/mixins/challengeMemberSearch';
import memberSearchDropdown from 'client/components/members/memberSearchDropdown';
export default {
props: ['challengeId', 'members'],
mixins: [challengeMemberSearchMixin],
components: {
memberSearchDropdown,
},
data () {
return {
winner: {},
searchTerm: '',
memberResults: [],
};
},
computed: {

View File

@@ -8,9 +8,7 @@
.form-group(v-if='workingGroup.id && members.length > 0')
label
strong(v-once) {{$t('guildOrPartyLeader')}} *
select.form-control(v-model="workingGroup.newLeader")
option(v-for='potentialLeader in potentialLeaders', :value="potentialLeader._id") {{ potentialLeader.name }}
group-member-search-dropdown(:text="currentLeader", :members='members', :groupId='workingGroup.id', @member-selected='selectNewLeader')
.form-group
label
strong(v-once) {{$t('privacySettings')}} *
@@ -170,6 +168,7 @@
<script>
import { mapState } from 'client/libs/store';
import toggleSwitch from 'client/components/ui/toggleSwitch';
import groupMemberSearchDropdown from 'client/components/members/groupMemberSearchDropdown';
import markdownDirective from 'client/directives/markdown';
import gemIcon from 'assets/svg/gem.svg';
import informationIcon from 'assets/svg/information.svg';
@@ -185,6 +184,7 @@ import { MAX_SUMMARY_SIZE_FOR_GUILDS } from '../../../common/script/constants';
export default {
components: {
toggleSwitch,
groupMemberSearchDropdown,
},
directives: {
markdown: markdownDirective,
@@ -307,16 +307,12 @@ export default {
isParty () {
return this.workingGroup.type === 'party';
},
potentialLeaders () {
let leaders = [{ _id: this.user._id, name: this.user.profile.name }];
// @TODO consider pushing all recent posters to the top of the list if they are guild members - more likely to be the ones the leader wants to see (and then ignore them in the while below)
let i = 0;
while (this.members[i]) {
let memb = this.members[i];
i++;
if (memb._id !== this.user._id) leaders.push({_id: memb._id, name: memb.profile.name});
}
return leaders;
currentLeader () {
const currentLeader = this.members.find(member => {
return member._id === this.workingGroup.newLeader;
});
const currentLeaderName = currentLeader.profile ? currentLeader.profile.name : '';
return currentLeaderName;
},
},
watch: {
@@ -356,6 +352,9 @@ export default {
},
},
methods: {
selectNewLeader (member) {
this.workingGroup.newLeader = member._id;
},
async getMembers () {
if (!this.workingGroup.id) return;
let members = await this.$store.dispatch('members:getGroupMembers', {

View File

@@ -36,7 +36,7 @@ div
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.messageIcon")
span.text {{$t('sendMessage')}}
b-dropdown-item(@click='promoteToLeader(member._id)', v-if='isLeader')
b-dropdown-item(@click='promoteToLeader(member)', v-if='isLeader || isAdmin')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.starIcon")
span.text {{$t('promoteToLeader')}}
@@ -290,6 +290,9 @@ export default {
if (!this.group || !this.group.leader) return false;
return this.user._id === this.group.leader || this.user._id === this.group.leader._id;
},
isAdmin () {
return Boolean(this.user.contributor.admin);
},
groupIsSubscribed () {
return this.group.purchased.active;
},
@@ -440,10 +443,15 @@ export default {
});
this.viewMembers();
},
async promoteToLeader (memberId) {
async promoteToLeader (member) {
let groupData = Object.assign({}, this.group);
groupData.leader = memberId;
groupData.leader = member._id;
await this.$store.dispatch('guilds:update', {group: groupData});
alert(this.$t('leaderChanged'));
groupData.leader = member;
this.$root.$emit('updatedGroup', groupData);
},
},

View File

@@ -0,0 +1,58 @@
<template lang="pug">
b-dropdown.select-member(:text="text")
input.form-control(type='text', v-model='searchTerm')
b-dropdown-item(v-for="member in memberResults", :key="member._id", @click="selectMember(member)")
| {{ member.profile.name }}
</template>
<style lang="scss" scoped>
.select-member {
width: 100%;
}
</style>
<script>
// @TODO: how do we subclass the other member search or compose?
import debounce from 'lodash/debounce';
export default {
props: {
text: {
type: String,
required: true,
},
members: {
type: Array,
required: true,
},
groupId: {
type: String,
},
},
data () {
return {
searchTerm: '',
memberResults: [],
};
},
mounted () {
this.memberResults = this.members;
},
watch: {
searchTerm: debounce(function searchTerm (newSearch) {
this.searchMember(newSearch);
}, 500),
},
methods: {
selectMember (member) {
this.$emit('member-selected', member);
},
async searchMember (search) {
this.memberResults = await this.$store.dispatch('members:getGroupMembers', {
groupId: this.groupId,
searchTerm: search,
});
},
},
};
</script>

View File

@@ -0,0 +1,42 @@
<template lang="pug">
b-dropdown.create-dropdown(:text="text")
input.form-control(type='text', v-model='searchTerm')
b-dropdown-item(v-for="member in memberResults", :key="member._id", @click="selectMember(member)")
| {{ member.profile.name }}
</template>
<style lang="scss" scoped>
</style>
<script>
// @TODO: how do we subclass this rather than type checking?
import challengeMemberSearchMixin from 'client/mixins/challengeMemberSearch';
export default {
mixins: [challengeMemberSearchMixin],
props: {
text: {
type: String,
required: true,
},
members: {
type: Array,
required: true,
},
challengeId: {
type: String,
},
},
data () {
return {
searchTerm: '',
memberResults: [],
};
},
methods: {
selectMember (member) {
this.$emit('member-selected', member);
},
},
};
</script>

View File

@@ -6,9 +6,15 @@ let apiV3Prefix = '/api/v3';
export async function getGroupMembers (store, payload) {
let url = `${apiV3Prefix}/groups/${payload.groupId}/members`;
if (payload.includeAllPublicFields) {
url += '?includeAllPublicFields=true';
}
if (payload.searchTerm) {
url += `?search=${payload.searchTerm}`;
}
let response = await axios.get(url);
return response.data.data;
}

View File

@@ -130,5 +130,6 @@
"categoiresRequired": "One or more categories must be selected",
"viewProgressOf": "View Progress Of",
"selectMember": "Select Member",
"confirmKeepChallengeTasks": "Do you want to keep challenge tasks?"
"confirmKeepChallengeTasks": "Do you want to keep challenge tasks?",
"selectParticipant": "Select a Participant"
}

View File

@@ -413,5 +413,6 @@
"groupBilling": "Group Billing",
"wouldYouParticipate": "Would you like to participate?",
"managerAdded": "Manager added successfully",
"managerRemoved": "Manager removed successfully"
"managerRemoved": "Manager removed successfully",
"leaderChanged": "Leader has been changed"
}

View File

@@ -246,6 +246,10 @@ function _getMembersForItem (type) {
addComputedStats = true;
}
}
if (req.query.search) {
query['profile.name'] = {$regex: req.query.search};
}
} else if (type === 'group-invites') {
if (group.type === 'guild') { // eslint-disable-line no-lonely-if
query['invitations.guilds.id'] = group._id;