Fix End challenge search to load participant based on search query (#15520)

* Load all participants when end challenge modal is opened.

* Fetch members in batches until members are loaded

* Fix challenge winner search to load all participants

Separated loading flags to prevent conflicts between modals

* Rename end challenge members flag to be more clear

* await load members

* Implement challenge member search only when searching w/debounce
This commit is contained in:
Fiz
2025-09-30 16:33:36 -05:00
committed by GitHub
parent 2029739a1b
commit e8eeb76cab
2 changed files with 32 additions and 10 deletions

View File

@@ -7,7 +7,6 @@
@update-challenge="updateChallenge" @update-challenge="updateChallenge"
/> />
<close-challenge-modal <close-challenge-modal
:members="members"
:challenge-id="challenge._id" :challenge-id="challenge._id"
:prize="challenge.prize" :prize="challenge.prize"
:flag-count="challenge.flagCount" :flag-count="challenge.flagCount"
@@ -697,7 +696,6 @@ export default {
this.members = []; this.members = [];
}, },
closeChallenge () { closeChallenge () {
this.initialMembersLoad();
this.$root.$emit('bv::show::modal', 'close-challenge-modal'); this.$root.$emit('bv::show::modal', 'close-challenge-modal');
}, },
edit () { edit () {

View File

@@ -350,6 +350,7 @@
</style> </style>
<script> <script>
import debounce from 'lodash/debounce';
import searchIcon from '@/assets/svg/for-css/search.svg?raw'; import searchIcon from '@/assets/svg/for-css/search.svg?raw';
import deleteIcon from '@/assets/svg/delete.svg?raw'; import deleteIcon from '@/assets/svg/delete.svg?raw';
import gemIcon from '@/assets/svg/gem.svg?raw'; import gemIcon from '@/assets/svg/gem.svg?raw';
@@ -362,13 +363,14 @@ export default {
components: { components: {
closeX, closeX,
}, },
props: ['challengeId', 'members', 'prize', 'flagCount'], props: ['challengeId', 'prize', 'flagCount'],
data () { data () {
return { return {
winner: {}, winner: {},
searchTerm: '', searchTerm: '',
showResults: false, showResults: false,
filteredMembers: [], filteredMembers: [],
isSearching: false,
icons: Object.freeze({ icons: Object.freeze({
search: searchIcon, search: searchIcon,
deleteIcon, deleteIcon,
@@ -388,20 +390,42 @@ export default {
return this.flagCount > 0; return this.flagCount > 0;
}, },
}, },
created () {
this.searchMembersDebounced = debounce(this.performSearch, 500);
},
methods: { methods: {
searchMembers () { searchMembers () {
if (!this.searchTerm) { if (!this.searchTerm) {
this.filteredMembers = []; this.filteredMembers = [];
this.isSearching = false;
return; return;
} }
const searchLower = this.searchTerm.toLowerCase().replace('@', ''); this.isSearching = true;
this.filteredMembers = this.members.filter(member => { this.searchMembersDebounced();
const username = member.auth?.local?.username || ''; },
const displayName = member.profile?.name || ''; async performSearch () {
return username.toLowerCase().includes(searchLower) if (!this.searchTerm) {
|| displayName.toLowerCase().includes(searchLower); this.filteredMembers = [];
}).slice(0, 10); this.isSearching = false;
return;
}
const searchTerm = this.searchTerm.replace('@', '');
try {
const members = await this.$store.dispatch('members:getChallengeMembers', {
challengeId: this.challengeId,
searchTerm,
includeAllPublicFields: true,
});
this.filteredMembers = members.slice(0, 10);
} catch (err) {
this.filteredMembers = [];
} finally {
this.isSearching = false;
}
}, },
getMemberDisplayName (member) { getMemberDisplayName (member) {
if (member.auth?.local?.username) { if (member.auth?.local?.username) {