Skip to content

Commit c74e1f4

Browse files
authored
feat: add date and username filter (#938)
* feat: add date and username filter * chore: fix filters * chore: fix css * chore: fix ui for filters * chore: update constant file * chore: fix mobile css * chore: fix query builder for date * chore: fix clearusername icon * chore: fix styling * chore: remove console statement
1 parent 1dc3328 commit c74e1f4

File tree

5 files changed

+465
-38
lines changed

5 files changed

+465
-38
lines changed

feed/assets/user.svg

Lines changed: 3 additions & 0 deletions
Loading

feed/index.html

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,46 @@
2020
<body>
2121
<nav id="tasksNav"></nav>
2222
<h1 id="pageTitle">Activity Feed</h1>
23-
<nav class="tabs-container">
23+
<div class="filters">
24+
<div class="filter-row">
25+
<div class="input-wrapper">
26+
<label for="assignee-search">Username</label>
27+
<input
28+
type="text"
29+
id="assignee-search"
30+
placeholder="Enter Username"
31+
oninput="fetchSuggestions()"
32+
/>
33+
<span
34+
id="clear-username"
35+
class="clear-icon"
36+
onclick="clearUsernameFilter()"
37+
>×</span
38+
>
39+
<div id="suggestion-box" class="suggestion-box"></div>
40+
</div>
41+
42+
<div class="date-filters">
43+
<div class="date-inputs">
44+
<label for="start-date">Start Date</label>
45+
<input type="date" id="start-date" placeholder="Start Date" />
46+
</div>
47+
<div class="date-inputs">
48+
<label for="end-date">End Date</label>
49+
<input type="date" id="end-date" placeholder="End Date" />
50+
</div>
51+
</div>
52+
</div>
53+
</div>
54+
55+
<section class="tabs-container">
2456
<ul class="tabs"></ul>
2557
<div class="container">
2658
<div id="activity_feed_container">
2759
<ul class="activity-list"></ul>
2860
</div>
2961
<div class="virtual"></div>
3062
</div>
31-
</nav>
63+
</section>
3264
</body>
3365
</html>

feed/script.js

Lines changed: 178 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ const activityFeedContainer = document.getElementById(ACITIVITY_FEED_CONTAINER);
33
const activityList = document.querySelector('.activity-list');
44
const tabsList = document.querySelector('.tabs');
55
const lastElementContainer = document.querySelector(LAST_ELEMENT_CONTAINER);
6+
const usernameInput = document.getElementById('assignee-search');
7+
const clearUsernameBtn = document.getElementById('clear-username');
68

79
let query = {};
810
let newLink = '';
911
let activityFeedPage = 0;
1012
let nextLink = '';
1113
let isDataLoading = false;
1214
let category = CATEGORY.ALL;
15+
let activeIndex = -1;
1316

1417
const tabsData = [
1518
{ name: 'All', 'data-type': CATEGORY.ALL, class: 'active' },
@@ -21,7 +24,7 @@ const tabsData = [
2124

2225
async function renderFeed() {
2326
changeFilter();
24-
await populateActivityFeed({ category });
27+
await populateActivityFeed({ category: currentCategory, ...activeFilters });
2528
addIntersectionObserver();
2629
}
2730

@@ -38,9 +41,9 @@ function createTabListItem(tab) {
3841
function handleTabClick(tab) {
3942
tabs.forEach((t) => t.classList.remove('active'));
4043
tab.classList.add('active');
41-
const category = tab.dataset.type;
42-
changeFilter();
43-
populateActivityFeed({ category });
44+
currentCategory = tab.dataset.type;
45+
46+
refreshFeed();
4447
}
4548

4649
tabsData.forEach((tab) => {
@@ -300,16 +303,30 @@ function formatTaskRequestsLog(data) {
300303
async function populateActivityFeed(query = {}, newLink) {
301304
activityFeedPage++;
302305
const currentVersion = activityFeedPage;
306+
307+
const combinedQuery = { ...query, ...activeFilters };
308+
303309
try {
304310
isDataLoading = true;
305311
addLoader(container);
306-
const activityFeedData = await getActivityFeedData(query, newLink);
312+
313+
const activityFeedData = await getActivityFeedData(combinedQuery, newLink);
314+
315+
activityFeedContainer.innerHTML = '';
316+
307317
if (activityFeedData) {
308318
nextLink = activityFeedData.next;
309319
const allActivityFeedData = activityFeedData.data;
320+
310321
if (currentVersion !== activityFeedPage) {
311322
return;
312323
}
324+
325+
if (allActivityFeedData.length === 0) {
326+
addEmptyPageMessage(activityFeedContainer);
327+
return;
328+
}
329+
313330
for (const data of allActivityFeedData) {
314331
const renderedItem = renderActivityItem(data);
315332
activityFeedContainer.appendChild(renderedItem);
@@ -319,6 +336,7 @@ async function populateActivityFeed(query = {}, newLink) {
319336
showMessage(activityFeedContainer, error);
320337
} finally {
321338
if (currentVersion !== activityFeedPage) return;
339+
322340
removeLoader('loader');
323341
isDataLoading = false;
324342
}
@@ -335,7 +353,6 @@ async function getActivityFeedData(query = {}, nextLink) {
335353
'Content-type': 'application/json',
336354
},
337355
});
338-
339356
try {
340357
const res = await fetch(finalUrl, {
341358
credentials: 'include',
@@ -370,5 +387,160 @@ async function getActivityFeedData(query = {}, nextLink) {
370387
}
371388
}
372389

390+
let currentCategory = CATEGORY.ALL;
391+
392+
function handleTabClick(tab) {
393+
tabs.forEach((t) => t.classList.remove('active'));
394+
tab.classList.add('active');
395+
currentCategory = tab.dataset.type;
396+
changeFilter();
397+
populateActivityFeed({ category: currentCategory });
398+
}
399+
400+
let activeFilters = {
401+
username: null,
402+
startDate: null,
403+
endDate: null,
404+
};
405+
406+
document.getElementById('start-date').addEventListener('change', applyFilter);
407+
document.getElementById('end-date').addEventListener('change', applyFilter);
408+
clearUsernameBtn.addEventListener('click', clearUsernameFilter);
409+
410+
clearUsernameBtn.style.display = 'none';
411+
412+
usernameInput.addEventListener('input', function () {
413+
if (usernameInput.value.trim() !== '') {
414+
clearUsernameBtn.style.display = 'inline';
415+
} else {
416+
clearUsernameBtn.style.display = 'none';
417+
}
418+
});
419+
420+
function applyFilter() {
421+
const username = document.getElementById('assignee-search').value.trim();
422+
const startDate = document.getElementById('start-date').value;
423+
const endDate = document.getElementById('end-date').value;
424+
425+
if (startDate && endDate && new Date(startDate) > new Date(endDate)) {
426+
alert('Start Date cannot be later than End Date!');
427+
return;
428+
}
429+
430+
activeFilters.username = username || null;
431+
activeFilters.startDate = startDate
432+
? new Date(startDate).toISOString()
433+
: null;
434+
activeFilters.endDate = endDate ? new Date(endDate).toISOString() : null;
435+
436+
populateActivityFeed({ category: currentCategory, ...activeFilters });
437+
}
438+
439+
function clearUsernameFilter() {
440+
const usernameInput = document.getElementById('assignee-search');
441+
const suggestionBox = document.getElementById('suggestion-box');
442+
const clearUsernameBtn = document.getElementById('clear-username');
443+
444+
usernameInput.value = '';
445+
suggestionBox.style.display = 'none';
446+
clearUsernameBtn.style.display = 'none';
447+
448+
activeFilters.username = null;
449+
populateActivityFeed({ category: currentCategory, ...activeFilters });
450+
}
451+
452+
async function fetchSuggestions() {
453+
const input = document.getElementById('assignee-search');
454+
const query = input.value.trim();
455+
const suggestionBox = document.getElementById('suggestion-box');
456+
457+
if (!query) {
458+
suggestionBox.style.display = 'none';
459+
return;
460+
}
461+
462+
try {
463+
const response = await fetch(`${API_BASE_URL}/users?search=${query}`, {
464+
method: 'GET',
465+
credentials: 'include',
466+
headers: {
467+
'Content-Type': 'application/json',
468+
},
469+
});
470+
471+
if (response.ok) {
472+
const data = await response.json();
473+
const users = data.users || [];
474+
if (users.length > 0) {
475+
renderSuggestions(users);
476+
suggestionBox.style.display = 'block';
477+
} else {
478+
suggestionBox.innerHTML =
479+
'<div class="suggestion-item">No users found</div>';
480+
suggestionBox.style.display = 'block';
481+
}
482+
} else {
483+
console.error('Error fetching suggestions:', response.statusText);
484+
}
485+
} catch (error) {
486+
console.error('Error:', error);
487+
}
488+
}
489+
490+
function renderSuggestions(users) {
491+
const suggestionBox = document.getElementById('suggestion-box');
492+
suggestionBox.innerHTML = users
493+
.map((user, index) => {
494+
const userIcon = `<img src="/feed/assets/user.svg" alt="User Icon" class="user-icon" />`;
495+
return `<div
496+
class="suggestion-item ${
497+
index === activeIndex ? 'active' : ''
498+
}"
499+
onclick="selectAssignee('${user.username}')">
500+
<div class="suggestion-content">
501+
${userIcon}
502+
<span>${user.username}</span>
503+
</div>
504+
</div>`;
505+
})
506+
.join('');
507+
}
508+
509+
function selectAssignee(username) {
510+
const input = document.getElementById('assignee-search');
511+
input.value = username;
512+
const suggestionBox = document.getElementById('suggestion-box');
513+
suggestionBox.style.display = 'none';
514+
applyFilter();
515+
}
516+
517+
document.getElementById('assignee-search').addEventListener('keydown', (e) => {
518+
const suggestionBox = document.getElementById('suggestion-box');
519+
const items = suggestionBox.querySelectorAll('.suggestion-item');
520+
521+
if (e.key === 'ArrowDown') {
522+
e.preventDefault();
523+
activeIndex = (activeIndex + 1) % items.length;
524+
} else if (e.key === 'ArrowUp') {
525+
e.preventDefault();
526+
activeIndex = (activeIndex - 1 + items.length) % items.length;
527+
} else if (e.key === 'Enter') {
528+
e.preventDefault();
529+
if (activeIndex >= 0 && activeIndex < items.length) {
530+
items[activeIndex].click();
531+
}
532+
} else if (e.key === 'Escape') {
533+
suggestionBox.style.display = 'none';
534+
}
535+
536+
items.forEach((item, index) => {
537+
if (index === activeIndex) {
538+
item.classList.add('active');
539+
} else {
540+
item.classList.remove('active');
541+
}
542+
});
543+
});
544+
373545
// main entry
374546
renderFeed();

0 commit comments

Comments
 (0)