Skip to content

Commit 8e4618c

Browse files
Add speaker name filter to proposal review recap screen (#4548)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Marco Acierno <[email protected]>
1 parent f015f9e commit 8e4618c

File tree

1 file changed

+65
-2
lines changed

1 file changed

+65
-2
lines changed

backend/reviews/templates/proposals-recap.html

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,34 @@
162162
margin-left: 10px;
163163
}
164164

165+
.speaker-name-link {
166+
cursor: pointer;
167+
text-decoration: underline;
168+
color: var(--link-fg, #417690);
169+
}
170+
171+
.speaker-name-link:hover {
172+
color: var(--link-hover-color, #036);
173+
}
174+
175+
.clear-filter-btn {
176+
background: none;
177+
border: 1px solid #ccc;
178+
border-radius: 3px;
179+
cursor: pointer;
180+
padding: 2px 8px;
181+
font-size: 12px;
182+
color: #666;
183+
margin-left: 6px;
184+
vertical-align: middle;
185+
}
186+
187+
.clear-filter-btn:hover {
188+
background-color: #eee;
189+
border-color: #999;
190+
color: #333;
191+
}
192+
165193
tr:nth-of-type(odd) {
166194
background-color: var(--body-bg);
167195
}
@@ -261,11 +289,13 @@
261289
const filterWithReviewsSelect = document.querySelector('#filter-with-n-reviews');
262290
const filterByStatusInputs = [...document.querySelectorAll('input[name="filter-by-status"]')];
263291
const filterByTypeInputs = [...document.querySelectorAll('input[name="filter-by-type"]')];
292+
const filterBySpeakerInput = document.querySelector('#filter-by-speaker');
264293

265294
const applyFilters = () => {
266295
const reviewFilterValue = filterWithReviewsSelect.value;
267296
const visibleStatuses = filterByStatusInputs.filter(input => input.checked).map(input => input.value);
268297
const visibleTypes = filterByTypeInputs.filter(input => input.checked).map(input => input.value);
298+
const speakerFilter = filterBySpeakerInput.value.toLowerCase().trim();
269299

270300
document.querySelectorAll('.proposal-item').forEach(proposalRow => {
271301
const proposalId = parseInt(proposalRow.id.split('-')[1], 10);
@@ -274,8 +304,9 @@
274304
const matchesReviews = reviewFilterValue === 'all' || proposalData.numOfVotes === parseInt(reviewFilterValue, 10);
275305
const matchesStatus = visibleStatuses.includes(proposalData.originalStatus);
276306
const matchesType = visibleTypes.includes(proposalData.submissionType);
307+
const matchesSpeaker = !speakerFilter || proposalData.speakerName.toLowerCase().includes(speakerFilter);
277308

278-
if (matchesReviews && matchesStatus && matchesType) {
309+
if (matchesReviews && matchesStatus && matchesType && matchesSpeaker) {
279310
proposalRow.classList.remove('hidden');
280311
} else {
281312
proposalRow.classList.add('hidden');
@@ -286,6 +317,14 @@
286317
filterWithReviewsSelect.addEventListener('change', applyFilters);
287318
filterByStatusInputs.forEach(input => input.addEventListener('change', applyFilters));
288319
filterByTypeInputs.forEach(input => input.addEventListener('change', applyFilters));
320+
filterBySpeakerInput.addEventListener('input', () => {
321+
applyFilters();
322+
const hasFilter = filterBySpeakerInput.value.trim() !== '';
323+
document.getElementById('clear-speaker-filter').style.display = hasFilter ? 'inline' : 'none';
324+
document.querySelectorAll('.speaker-row-clear-btn').forEach(btn => {
325+
btn.style.display = hasFilter ? 'inline' : 'none';
326+
});
327+
});
289328
});
290329

291330
const updateBottomBarUI = () => {
@@ -353,6 +392,21 @@
353392
);
354393
};
355394

395+
// Filter by speaker name (called when clicking a speaker name link)
396+
const filterBySpeakerName = (name) => {
397+
const input = document.querySelector('#filter-by-speaker');
398+
input.value = name;
399+
input.dispatchEvent(new Event('input'));
400+
input.scrollIntoView({ behavior: 'smooth', block: 'center' });
401+
};
402+
403+
// Clear speaker name filter
404+
const clearSpeakerFilter = () => {
405+
const input = document.querySelector('#filter-by-speaker');
406+
input.value = '';
407+
input.dispatchEvent(new Event('input'));
408+
};
409+
356410
// Toggle bottom bar visibility
357411
const toggleBottomBar = () => {
358412
const bottomBar = document.querySelector('.reviews-bottom-bar');
@@ -405,6 +459,13 @@ <h3>Show proposals with pending status:</h3>
405459
{% endfor %}
406460
</div>
407461
</div>
462+
<div class="opt-filter">
463+
<h3>Filter by speaker name:</h3>
464+
<div style="display: flex;">
465+
<input type="text" id="filter-by-speaker" placeholder="Type speaker name..." style="flex: 1;" />
466+
<button type="button" class="clear-filter-btn" id="clear-speaker-filter" onclick="clearSpeakerFilter()" style="display: none;">Clear</button>
467+
</div>
468+
</div>
408469
<div class="opt-filter">
409470
<h3>Show proposals of type:</h3>
410471
<div>
@@ -464,6 +525,7 @@ <h3>Show proposals of type:</h3>
464525
languages: [{% for language in item.languages.all %}"{{language.code}}",{% endfor %}],
465526
numOfVotes: {{item.userreview_set.count}},
466527
submissionType: "{{ item.type.name }}",
528+
speakerName: "{{ item.speaker.fullname|escapejs }}",
467529
};
468530
</script>
469531
<tr class="proposal-item" id="submission-{{item.id}}" data-original-status="{{ item.current_or_pending_status }}">
@@ -494,7 +556,8 @@ <h3>Show proposals of type:</h3>
494556
{% with speaker_id=item.speaker_id|stringformat:"i" %}
495557
<li>
496558
<strong>Speaker Name</strong>
497-
<span>{{ item.speaker.fullname }}</span>
559+
<span class="speaker-name-link" onclick="filterBySpeakerName('{{ item.speaker.fullname|escapejs }}')">{{ item.speaker.fullname }}</span>
560+
<button type="button" class="clear-filter-btn speaker-row-clear-btn" onclick="event.stopPropagation(); clearSpeakerFilter();" style="display: none;">Clear filter</button>
498561
</li>
499562
<li>
500563
<strong>Speaker Country</strong>

0 commit comments

Comments
 (0)