diff --git a/__tests__/taskRequests/taskRequestDetails.test.js b/__tests__/taskRequests/taskRequestDetails.test.js index ba63141c..22079c2b 100644 --- a/__tests__/taskRequests/taskRequestDetails.test.js +++ b/__tests__/taskRequests/taskRequestDetails.test.js @@ -11,7 +11,7 @@ describe('Tests the User Management User Listing Screen', () => { beforeAll(async () => { browser = await puppeteer.launch({ - headless: true, + headless: 'new', ignoreHTTPSErrors: true, args: ['--incognito', '--disable-web-security'], devtools: false, diff --git a/__tests__/user-details/task-duedate-hover.test.js b/__tests__/user-details/task-duedate-hover.test.js index ae83c555..2c3dbc25 100644 --- a/__tests__/user-details/task-duedate-hover.test.js +++ b/__tests__/user-details/task-duedate-hover.test.js @@ -1,6 +1,7 @@ const puppeteer = require('puppeteer'); const { userDetailsApi, + usersTasksInDev, } = require('../../mock-data/task-card-date-hover/index'); //has user info const { superUserDetails, @@ -49,6 +50,62 @@ describe('Tasks On User Management Page', () => { }, body: JSON.stringify(superUserDetails), // Y contains the json of a superuser in the server which will grant us the access to view the page without locks }); + } else if ( + url === + 'https://api.realdevsquad.com/tasks/?size=3&dev=true&assignee=ajeyakrishna' + ) { + interceptedRequest.respond({ + status: 200, + contentType: 'application/json', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + body: JSON.stringify(usersTasksInDev['initial']), + }); + } else if ( + url === + 'https://api.realdevsquad.com/tasks?dev=true&assignee=ajeyakrishna&size=3&next=vvTPGHAs9w36oY1UnV8r' + ) { + interceptedRequest.respond({ + status: 200, + contentType: 'application/json', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + body: JSON.stringify(usersTasksInDev['vvTPGHAs9w36oY1UnV8r']), + }); + } else if ( + url === + 'https://api.realdevsquad.com/tasks?dev=true&assignee=ajeyakrishna&size=3&next=i1LQOKkGhhpOxE6yEo3A' + ) { + interceptedRequest.respond({ + status: 200, + contentType: 'application/json', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + body: JSON.stringify(usersTasksInDev['i1LQOKkGhhpOxE6yEo3A']), + }); + } else if ( + url === + 'https://api.realdevsquad.com/tasks?dev=true&assignee=ajeyakrishna&size=3&next=OhNeSTj5J72PhrA4mtrr' + ) { + interceptedRequest.respond({ + status: 200, + contentType: 'application/json', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + body: JSON.stringify(usersTasksInDev['OhNeSTj5J72PhrA4mtrr']), + }); } else { interceptedRequest.continue(); } @@ -57,25 +114,6 @@ describe('Tasks On User Management Page', () => { 'http://localhost:8000/users/details/index.html?username=sunny-s', ); - await page.evaluate(async () => { - // We write the function with superUser as true - - async function accessingUserData() { - const isSuperUser = true; - if (isSuperUser) { - await getUserTasks(); - await getUserPrs(); - await generateAcademicTabDetails(); - toggleAccordionTabsVisibility(); - } else { - lockAccordiansForNonSuperUser(); - } - } - - // Calling the function - await accessingUserData(); - }); - await page.waitForNetworkIdle(); }); @@ -141,4 +179,120 @@ describe('Tasks On User Management Page', () => { await page.waitForTimeout(500); //waiting for a moment to check changes(very helpful when you turn headless into false) }); + + it('Scroll of task should work in dev mode', async () => { + await page.goto( + 'http://localhost:8000/users/details/index.html?username=ajeyakrishna&dev=true', + ); + await page.waitForNetworkIdle(); + const taskDiv = await page.$$('.accordion-tasks'); + expect(taskDiv).toBeTruthy(); + await taskDiv[0].click(); + + const userTasksDevDiv = await page.$('.user-tasks-dev'); + expect(userTasksDevDiv).toBeTruthy(); + + await page.evaluate(() => { + window.scrollBy(0, window.document.body.scrollHeight); + }); + await page.waitForNetworkIdle(); + + await page.evaluate(() => { + window.scrollBy(0, window.document.body.scrollHeight); + }); + await page.waitForNetworkIdle(); + + await page.evaluate(() => { + window.scrollBy(0, window.document.body.scrollHeight); + }); + await page.waitForNetworkIdle(); + + await page.evaluate(() => { + window.scrollBy(0, window.document.body.scrollHeight); + }); + await page.waitForNetworkIdle(); + + let renderedTasks = await userTasksDevDiv.$$('.user-task'); + expect(Array.from(renderedTasks).length).toBe(12); + }); + + it('New task card should have all the detail fields', async () => { + await page.goto( + 'http://localhost:8000/users/details/index.html?username=ajeyakrishna&dev=true', + ); + await page.waitForNetworkIdle(); + const taskDiv = await page.$$('.accordion-tasks'); + expect(taskDiv).toBeTruthy(); + await taskDiv[0].click(); + + const userTasksDevDiv = await page.$('.user-tasks-dev'); + expect(userTasksDevDiv).toBeTruthy(); + + let renderedTasks = await page.$$('.user-task'); + let firstTask = await renderedTasks[0].$$('.task'); + let firstTaskHTML = await page.evaluate( + (element) => element.innerHTML, + firstTask[0], + ); + + expect(firstTaskHTML).toContain('
'); + expect(firstTaskHTML).not.toContain(''); + expect(firstTaskHTML).toContain('
'); + expect(firstTaskHTML).toContain('
'); + expect(firstTaskHTML).toContain('
'); + expect(firstTaskHTML).toContain('
'); + expect(firstTaskHTML).toContain('
'); + + let secondTask = await renderedTasks[1].$$('.task'); + let secondTaskHTML = await page.evaluate( + (element) => element.innerHTML, + secondTask[0], + ); + + expect(secondTaskHTML).toContain('
'); + expect(secondTaskHTML).not.toContain(''); + expect(secondTaskHTML).toContain('
'); + expect(secondTaskHTML).toContain('
'); + expect(secondTaskHTML).toContain('
'); + expect(secondTaskHTML).toContain('
'); + expect(secondTaskHTML).toContain('
'); + + let thirdTask = await renderedTasks[2].$$('.task'); + let thirdTaskHTML = await page.evaluate( + (element) => element.innerHTML, + thirdTask[0], + ); + + expect(thirdTaskHTML).toContain('
'); + expect(thirdTaskHTML).not.toContain(''); + expect(thirdTaskHTML).toContain('
'); + expect(thirdTaskHTML).toContain('
'); + expect(thirdTaskHTML).toContain('
'); + expect(thirdTaskHTML).toContain('
'); + expect(thirdTaskHTML).toContain('
'); + + await page.evaluate(() => { + window.scrollBy(0, window.document.body.scrollHeight); + }); + await page.waitForNetworkIdle(); + + renderedTasks = await page.$$('.user-task'); + let fourthTask = await renderedTasks[3].$$('.task'); + let fourthTaskHTML = await page.evaluate( + (element) => element.innerHTML, + fourthTask[0], + ); + + expect(fourthTaskHTML).toContain('
'); + expect(fourthTaskHTML).toContain(''); + expect(fourthTaskHTML).toContain('
'); + expect(fourthTaskHTML).toContain('
'); + expect(fourthTaskHTML).toContain('
'); + expect(fourthTaskHTML).toContain('
'); + expect(fourthTaskHTML).toContain('
'); + }); }); diff --git a/extension-requests/local-utils.js b/extension-requests/local-utils.js index c802d21a..1c2a3f37 100644 --- a/extension-requests/local-utils.js +++ b/extension-requests/local-utils.js @@ -205,38 +205,6 @@ function formDataToObject(formData) { return result; } -function dateDiff(date1, date2, formatter) { - if (date2 > date1) { - return dateDiff(date2, date1, formatter); - } - - const timeDifference = new Date(date1).getTime() - new Date(date2).getTime(); - - const seconds = Math.floor(timeDifference / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const days = Math.floor(hours / 24); - const months = Math.floor(days / 30); - const years = Math.floor(days / 365); - - let res; - if (seconds < 60) { - res = `${seconds} ${seconds === 1 ? 'second' : 'seconds'}`; - } else if (minutes < 60) { - res = `${minutes} ${minutes === 1 ? 'minute' : 'minutes'}`; - } else if (hours < 24) { - res = `${hours} ${hours === 1 ? 'hour' : 'hours'}`; - } else if (days < 30) { - res = `${days} ${days === 1 ? 'day' : 'days'}`; - } else if (months < 12) { - res = `${months} ${months === 1 ? 'month' : 'months'}`; - } else { - res = `${years} ${years === 1 ? 'year' : 'years'}`; - } - - return formatter ? formatter(res) : res; -} - const addSpinner = (container) => { const spinner = createElement({ type: 'div', diff --git a/footerTest.js b/footerTest.js index c6c03fe7..d292d8b1 100644 --- a/footerTest.js +++ b/footerTest.js @@ -2,7 +2,7 @@ const puppeteer = require('puppeteer'); let config = { launchOptions: { - headless: true, + headless: 'new', ignoreHTTPSErrors: true, }, }; diff --git a/mock-data/task-card-date-hover/index.js b/mock-data/task-card-date-hover/index.js index f56d92b5..62c6d967 100644 --- a/mock-data/task-card-date-hover/index.js +++ b/mock-data/task-card-date-hover/index.js @@ -35,7 +35,7 @@ const userDetailsApi = { }, { id: 'F2A6XVGgM3IshzEd5niL', - percentCompleted: 100, + percentCompleted: 30, endsOn: 1688445662, //Jul 4 2023 isNoteworthy: false, createdBy: 'ankush', @@ -52,4 +52,307 @@ const userDetailsApi = { ], }; -module.exports = { userDetailsApi }; +const usersTasksInDev = { + initial: { + message: 'Tasks returned successfully!', + tasks: [ + { + id: 'leX6wahONOe2BPxJxTkt', + percentCompleted: 100, + endsOn: 1694629740, + github: { + issue: { + id: 1872244306, + status: 'open', + }, + }, + createdBy: 'ankush', + assignee: 'ajeyakrishna', + title: 'Bugs in extension requests page', + type: 'feature', + priority: 'TBD', + status: 'COMPLETED', + assigneeId: '36AGeYsrIZQcPYWlFb1a', + dependsOn: [], + }, + { + id: '1TJzTISp2fuKjZ53ZZfG', + percentCompleted: 100, + endsOn: 1690591252, + github: { + issue: { + id: 1812126813, + status: 'open', + }, + }, + createdBy: 'ankush', + assignee: 'ajeyakrishna', + title: 'Cache new response iff successful', + type: 'feature', + priority: 'TBD', + status: 'DONE', + assigneeId: '36AGeYsrIZQcPYWlFb1a', + dependsOn: [], + }, + { + id: 'SFhZrVn7zBWKEup3M6ZC', + percentCompleted: 100, + endsOn: 1690250700, + github: { + issue: { + id: 1805209342, + assignee: 'Aryex82', + status: 'open', + assigneeRdsInfo: { + firstName: '', + lastName: '', + username: '', + }, + }, + }, + createdBy: 'ankush', + assignee: 'ajeyakrishna', + title: "Clear cache related to tasks when there's an update", + type: 'feature', + priority: 'TBD', + startedOn: 1689552640.848, + status: 'VERIFIED', + assigneeId: '36AGeYsrIZQcPYWlFb1a', + dependsOn: [], + }, + ], + prev: '', + next: '/tasks?dev=true&assignee=ajeyakrishna&size=3&next=vvTPGHAs9w36oY1UnV8r', + }, + vvTPGHAs9w36oY1UnV8r: { + message: 'Tasks returned successfully!', + tasks: [ + { + id: 'vvTPGHAs9w36oY1UnV8r', + percentCompleted: 90, + endsOn: 1699833600, + github: { + issue: { + url: 'https://api.github.com/repos/Real-Dev-Squad/website-status/issues/977', + }, + }, + assignee: 'ajeyakrishna', + title: + 'Create a success message and close icon for task creation requests', + type: 'feature', + priority: 'TBD', + startedOn: 1698883200, + status: 'IN_PROGRESS', + assigneeId: '36AGeYsrIZQcPYWlFb1a', + dependsOn: [], + }, + { + id: '4P8Ax2YyssIX2y2kwGBX', + percentCompleted: 100, + endsOn: 1698221844, + github: { + issue: { + html_url: + 'https://github.com/Real-Dev-Squad/website-backend/issues/1519', + id: 1897464488, + status: 'open', + }, + }, + createdBy: 'ankush', + assignee: 'ajeyakrishna', + title: 'Design a Data restriction layer', + type: 'feature', + priority: 'TBD', + status: 'VERIFIED', + assigneeId: '36AGeYsrIZQcPYWlFb1a', + dependsOn: [], + }, + { + id: 'hW1oz6pqeVeYg4GRT5qT', + percentCompleted: 100, + endsOn: 1691858954, + github: { + issue: { + id: 1824823052, + assignee: 'Aryex82', + status: 'open', + assigneeRdsInfo: { + firstName: '', + lastName: '', + username: '', + }, + }, + }, + createdBy: 'ankush', + assignee: 'ajeyakrishna', + title: 'Improve UI & UX of Extension Request Cards', + type: 'feature', + priority: 'TBD', + startedOn: 1690590131.99, + status: 'VERIFIED', + assigneeId: '36AGeYsrIZQcPYWlFb1a', + dependsOn: [], + }, + ], + prev: '/tasks?dev=true&assignee=ajeyakrishna&size=3&prev=SFhZrVn7zBWKEup3M6ZC', + next: '/tasks?dev=true&assignee=ajeyakrishna&size=3&next=i1LQOKkGhhpOxE6yEo3A', + }, + i1LQOKkGhhpOxE6yEo3A: { + message: 'Tasks returned successfully!', + tasks: [ + { + id: 'i1LQOKkGhhpOxE6yEo3A', + percentCompleted: 100, + endsOn: 1690760553, + github: { + issue: { + id: 1797904552, + assignee: 'Aryex82', + status: 'open', + assigneeRdsInfo: { + firstName: '', + lastName: '', + username: '', + }, + }, + }, + createdBy: 'ankush', + assignee: 'ajeyakrishna', + title: 'Improve UX on input slider in task cards', + type: 'feature', + priority: 'TBD', + startedOn: 1689901777.322, + status: 'VERIFIED', + assigneeId: '36AGeYsrIZQcPYWlFb1a', + dependsOn: [], + }, + { + id: 'Djvf118D3Mo3VRhkF0g2', + percentCompleted: 100, + endsOn: 1693996834, + github: { + issue: { + id: 1862345646, + status: 'open', + }, + }, + createdBy: 'ankush', + assignee: 'ajeyakrishna', + title: 'Merge Update user and external sync api ', + type: 'feature', + priority: 'TBD', + status: 'VERIFIED', + assigneeId: '36AGeYsrIZQcPYWlFb1a', + dependsOn: [], + }, + { + id: 'HtC5LU45raW2YtwwxySA', + percentCompleted: 70, + endsOn: 1703634600, + github: { + issue: { + url: 'https://api.github.com/repos/Real-Dev-Squad/website-backend/issues/1613', + }, + }, + assignee: 'ajeyakrishna', + title: 'Migrate old TaskRequest model schema to new one', + type: 'feature', + priority: 'TBD', + startedOn: 1698192000, + status: 'IN_PROGRESS', + assigneeId: '36AGeYsrIZQcPYWlFb1a', + dependsOn: [], + }, + ], + prev: '/tasks?dev=true&assignee=ajeyakrishna&size=3&prev=hW1oz6pqeVeYg4GRT5qT', + next: '/tasks?dev=true&assignee=ajeyakrishna&size=3&next=OhNeSTj5J72PhrA4mtrr', + }, + OhNeSTj5J72PhrA4mtrr: { + message: 'Tasks returned successfully!', + tasks: [ + { + id: 'OhNeSTj5J72PhrA4mtrr', + percentCompleted: 100, + endsOn: 1698191717, + github: { + issue: { + html_url: + 'https://github.com/Real-Dev-Squad/website-status/issues/920', + id: 1930302167, + assignee: 'Ajeyakrishna-k', + status: 'open', + assigneeRdsInfo: { + firstName: 'Ajeyakrishna', + lastName: 'Karanth', + username: 'ajeyakrishna', + }, + }, + }, + createdBy: 'amitprakash', + assignee: 'ajeyakrishna', + title: 'Milestone 1 release for Task creation request. ', + type: 'feature', + priority: 'TBD', + startedOn: 1696605652, + status: 'VERIFIED', + assigneeId: '36AGeYsrIZQcPYWlFb1a', + dependsOn: [], + }, + { + id: 'vDBopzwCEuPHDor7XXla', + percentCompleted: 100, + endsOn: 1693594060, + github: { + issue: { + id: 1824822401, + assignee: 'Aryex82', + status: 'open', + assigneeRdsInfo: { + firstName: '', + lastName: '', + username: '', + }, + }, + }, + createdBy: 'ankush', + assignee: 'ajeyakrishna', + title: 'Revamp UI & UX of Extensions Requests page', + type: 'feature', + priority: 'TBD', + startedOn: 1692056863.291, + status: 'VERIFIED', + assigneeId: '36AGeYsrIZQcPYWlFb1a', + dependsOn: [], + }, + { + id: 'kdoBS8Z30r2d7FQOonQx', + percentCompleted: 100, + endsOn: 1689258628, + github: { + issue: { + id: 1779908116, + assignee: 'Aryex82', + status: 'open', + assigneeRdsInfo: { + firstName: '', + lastName: '', + username: '', + }, + }, + }, + createdBy: 'ankush', + assignee: 'ajeyakrishna', + title: 'Task cards not reverting changes when api fails.', + type: 'feature', + priority: 'TBD', + startedOn: 1688775795.152, + status: 'VERIFIED', + assigneeId: '36AGeYsrIZQcPYWlFb1a', + dependsOn: [], + }, + ], + prev: '/tasks?dev=true&assignee=ajeyakrishna&size=3&prev=HtC5LU45raW2YtwwxySA', + next: '', + }, +}; +module.exports = { userDetailsApi, usersTasksInDev }; diff --git a/users/details/constants.js b/users/details/constants.js index 7de75182..ba2cdf9a 100644 --- a/users/details/constants.js +++ b/users/details/constants.js @@ -20,3 +20,4 @@ const iconMapper = { const MESSAGE_NOT_FOUND = 'Not Found'; const MESSAGE_YEARS_OF_EXPERIENCE = 'Years of Experience'; +const noProgressbarStatuses = ['COMPLETED', 'DONE', 'VERIFIED']; diff --git a/users/details/script.js b/users/details/script.js index d14f6338..1a7fdf8a 100644 --- a/users/details/script.js +++ b/users/details/script.js @@ -2,15 +2,20 @@ const params = new URLSearchParams(window.location.search); let userData = {}; let userAllTasks = []; +let taskSearchQuery; +let allTasksFetched = false; +let isTaskFetching = false; let userSkills = []; let userAllPrs = []; let userStatusData = {}; let currentPageIndex = 1; let taskPerPage = 3; let prsPerPage = 3; +let isTaskAccordionOpen = false; let totalPrsPages = 0; let totalPages = Math.ceil(userAllTasks.length / taskPerPage); const username = new URLSearchParams(window.location.search).get('username'); +const isDev = params.get('dev') === 'true'; function createElement({ type, classList = [] }) { const element = document.createElement(type); @@ -187,6 +192,16 @@ function toggleAccordionTabsVisibility() { .querySelectorAll('.visible-content'); accordionTabs.forEach((tab) => { tab.addEventListener('click', () => { + if (tab.innerText === 'Tasks' && isDev) { + isTaskAccordionOpen = !isTaskAccordionOpen; + if (isTaskAccordionOpen) { + tab.classList.add('sticky-header'); + document.addEventListener('scroll', onScrollHandler); + } else { + tab.classList.remove('sticky-header'); + document.removeEventListener('scroll', onScrollHandler); + } + } const hiddenContent = tab.nextElementSibling; const arrowIcon = tab.querySelector('img'); if (hiddenContent) { @@ -233,38 +248,64 @@ function generateTasksTabDetails() { type: 'div', classList: ['hidden-content', 'hide'], }); - const tasks = createElement({ type: 'div', classList: ['user-tasks'] }); - const pagination = createElement({ type: 'div', classList: ['pagination'] }); - const prevBtn = createElement({ - type: 'button', - classList: ['pagination-prev-page'], - }); - prevBtn.appendChild(createTextNode('Prev')); - prevBtn.addEventListener('click', fetchPrevTasks); - const nextBtn = createElement({ - type: 'button', - classList: ['pagination-next-page'], + + const tasks = createElement({ + type: 'div', + classList: isDev ? ['user-tasks', 'user-tasks-dev'] : ['user-tasks'], }); - nextBtn.appendChild(createTextNode('Next')); - nextBtn.addEventListener('click', fetchNextTasks); + div.append(tasks); + if (!isDev) { + const pagination = createElement({ + type: 'div', + classList: ['pagination'], + }); + const prevBtn = createElement({ + type: 'button', + classList: ['pagination-prev-page'], + }); + prevBtn.appendChild(createTextNode('Prev')); + prevBtn.addEventListener('click', fetchPrevTasks); + const nextBtn = createElement({ + type: 'button', + classList: ['pagination-next-page'], + }); + nextBtn.appendChild(createTextNode('Next')); + nextBtn.addEventListener('click', fetchNextTasks); + + pagination.append(prevBtn, nextBtn); + div.append(tasks, pagination); + } - pagination.append(prevBtn, nextBtn); - div.append(tasks, pagination); document.querySelector('.accordion-tasks').appendChild(div); } async function getUserTasks() { try { - const res = await makeApiCall(`${API_BASE_URL}/tasks/${username}`); - if (res.status === 200) { - const data = await res.json(); - userAllTasks = data.tasks; - totalPages = Math.ceil(userAllTasks.length / taskPerPage); - const tasks = getTasksToFetch(userAllTasks, currentPageIndex); - generateTasksTabDetails(); - generateUserTaskList(tasks); - getUserSkills(); - getUserAvailabilityStatus(); + taskSearchQuery = isDev + ? taskSearchQuery || `/tasks/?size=3&dev=true&assignee=${username}` + : `/tasks/${username}`; + + //Flag to avoid multiple API calls with same payload + if (!(isDev && isTaskFetching) && !allTasksFetched) { + isTaskFetching = true; + const res = await makeApiCall(`${API_BASE_URL}${taskSearchQuery}`); + if (res.status === 200) { + const data = await res.json(); + generateTasksTabDetails(); + if (isDev) { + taskSearchQuery = data.next; + if (data.next === '') { + allTasksFetched = true; + } + generateUserTaskList(data.tasks); + } else { + userAllTasks = data.tasks; + totalPages = Math.ceil(userAllTasks.length / taskPerPage); + const tasks = getTasksToFetch(userAllTasks, currentPageIndex); + generateUserTaskList(tasks); + } + } + isTaskFetching = false; } } catch (err) { const div = createElement({ @@ -275,6 +316,7 @@ async function getUserTasks() { errorEl.appendChild(createTextNode('Something Went Wrong!')); div.appendChild(errorEl); document.querySelector('.accordion-tasks').appendChild(div); + isTaskFetching = false; } } @@ -285,9 +327,16 @@ function getTasksToFetch(userTasks, currentIndex) { (_, index) => index >= startIndex && index < endIndex, ); } +function onScrollHandler() { + const accordionTasks = document.getElementsByClassName('accordion-tasks'); + if (isTaskAccordionOpen && isBottomBorderInView(accordionTasks[0])) { + getUserTasks(); + } +} function generateUserTaskList(userTasks) { - document.querySelector('.user-tasks').innerHTML = ''; + if (isDev !== true) document.querySelector('.user-tasks').innerHTML = ''; + if (!userTasks.length) { const errorEl = createElement({ type: 'p', classList: ['error'] }); errorEl.appendChild(createTextNode('No Data Found')); @@ -302,22 +351,29 @@ function generateUserTaskList(userTasks) { document.querySelector('.user-tasks').appendChild(taskCard); }); - if (currentPageIndex === 1) { - document.querySelector('.pagination-prev-page').disabled = true; - } else { - document.querySelector('.pagination-prev-page').disabled = false; - } + if (!isDev) { + if (currentPageIndex === 1) { + document.querySelector('.pagination-prev-page').disabled = true; + } else { + document.querySelector('.pagination-prev-page').disabled = false; + } - if (currentPageIndex === totalPages) { - document.querySelector('.pagination-next-page').disabled = true; - } else { - document.querySelector('.pagination-next-page').disabled = false; + if (currentPageIndex === totalPages) { + document.querySelector('.pagination-next-page').disabled = true; + } else { + document.querySelector('.pagination-next-page').disabled = false; + } } } } function createSingleTaskCard(task) { - const container = createElement({ type: 'div', classList: ['user-taks'] }); + const container = createElement({ type: 'div', classList: ['user-task'] }); + if (isDev === true) { + const innerHTMl = generateCardUIInDev(task); + container.innerHTML = innerHTMl; + return container; + } const h2 = createElement({ type: 'h2', classList: ['task-title'] }); h2.appendChild(createTextNode(task?.title)); const p = createElement({ type: 'p', classList: ['task-description'] }); @@ -416,8 +472,10 @@ function fetchPrevTasks() { } } -function fetchNextTasks() { - if (currentPageIndex < totalPages) { +async function fetchNextTasks() { + if (isDev) { + await getUserTasks(); + } else if (currentPageIndex < totalPages) { currentPageIndex++; const tasks = getTasksToFetch(userAllTasks, currentPageIndex); generateUserTaskList(tasks); @@ -895,10 +953,12 @@ function generateUserPrsList(userPrs) { const prsCard = createSinglePrCard(pr); document.querySelector('.user-pr').appendChild(prsCard); }); - document.querySelector('.pagination-next-page').disabled = - currentPageIndex === totalPrsPages; - document.querySelector('.pagination-prev-page').disabled = - currentPageIndex === 1; + if (!isDev) { + document.querySelector('.pagination-next-page').disabled = + currentPageIndex === totalPrsPages; + document.querySelector('.pagination-prev-page').disabled = + currentPageIndex === 1; + } } } @@ -1090,6 +1150,8 @@ async function accessingUserData() { if (isSuperUser) { getUserTasks(); getUserPrs(); + getUserSkills(); + getUserAvailabilityStatus(); generateAcademicTabDetails(); toggleAccordionTabsVisibility(); } else { diff --git a/users/details/style.css b/users/details/style.css index ad4b97fd..d1255fe7 100644 --- a/users/details/style.css +++ b/users/details/style.css @@ -11,6 +11,12 @@ html { --blue-color: #1d1283; --white-color: white; --black-color: black; + --red-background: #ffebee; + --border-color: #d9d9d9; + --green: #008000; + --orange: #ffa500; + --red: #ff0000; + --yellow: #ecef08; --light-gray-color: lightgray; --button-active-color: #a8a8a8; --loader-gray-color: #f3f3f3; @@ -489,3 +495,88 @@ footer p a { position: absolute; color: black; } +.sticky-header { + position: sticky; + top: 0; + background-color: white; +} +.task { + border: 2px solid var(--border-color); + padding: 1rem; + border-radius: 10px; +} +.task-red { + background-color: var(--red-background); + border: 2px solid red; +} +.row { + padding: 10px; + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 17px; +} + +#task-loader { + text-align: center; +} +.task-title { + max-width: 27rem; +} +.task-title a { + text-decoration: none; + font-size: 1.6rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: normal; + font-weight: 500; + color: var(--blue-color); + max-width: 24rem; + word-break: break-word; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; +} + +.progress-content { + display: flex; + gap: 8px; + align-items: center; +} +progress { + height: 8px; + border-radius: 30px; + border: 1px solid; +} + +.detail-block { + display: flex; + align-items: center; + gap: 5px; +} +.div-heading { + font-size: 1.1rem; + font-weight: 500; + color: #aeaeae; +} +.detail { + padding-left: 0.5rem; +} +.eta { + flex-wrap: wrap; +} + +progress.red::-webkit-progress-value { + background-color: var(--red); +} + +progress.green::-webkit-progress-value { + background-color: var(--green); +} + +progress.orange::-webkit-progress-value { + background-color: var(--orange); +} +progress.yellow::-webkit-progress-value { + background-color: var(--yellow); +} diff --git a/users/details/utils.js b/users/details/utils.js index 1f8d6cc1..50a82201 100644 --- a/users/details/utils.js +++ b/users/details/utils.js @@ -36,3 +36,143 @@ function generateNoDataFoundSection(message) { const container = document.querySelector('.user-details-header'); container.appendChild(notFoundDiv); } + +function inputParser(input) { + const parsedDate = new Date(parseInt(input, 10) * 1000); + return parsedDate; +} + +/** + * Calculates the percentage of days remaining between two dates. + * + * @param {Date} startedOn - The start date of the task. + * @param {Date} endsOn - The end date of the task. + * @returns {number} The percentage of days remaining as a value between 0 and 100. + */ +const getPercentageOfDaysLeft = (startedOn, endsOn) => { + const startDate = inputParser(startedOn); + const endDate = inputParser(endsOn); + const totalDays = Math.floor((endDate - startDate) / (1000 * 60 * 60 * 24)); + const currentDate = new Date(); + const daysLeft = Math.floor((endDate - currentDate) / (1000 * 60 * 60 * 24)); + const percentageOfDaysLeft = (daysLeft / totalDays) * 100; + return percentageOfDaysLeft; +}; + +/** + * Determines the color code based on the progress of a task. + * + * @param {number} percentCompleted - The percentage of the task completed. + * @param {Date} startedOn - The date the task was started. + * @param {Date} endsOn - The date the task is expected to end. + * @returns {string} The color code representing the progress status: + */ +const handleProgressColor = (percentCompleted, startedOn, endsOn) => { + const percentageOfDaysLeft = getPercentageOfDaysLeft(startedOn, endsOn); + const percentIncomplete = 100 - percentCompleted; + if (percentCompleted === 100 || percentageOfDaysLeft >= percentIncomplete) { + return 'green'; + } + + if ( + (percentageOfDaysLeft < 25 && percentIncomplete > 35) || + (percentageOfDaysLeft <= 0 && percentIncomplete > 0) + ) { + return 'red'; + } + + if (percentageOfDaysLeft < 50 && percentIncomplete > 75) { + return 'orange'; + } + + return 'yellow'; +}; +/** + * Verify if element's border bottom is in the view or not + * @param {HTML element} element - UI Element + * @returns {boolean} + */ +function isBottomBorderInView(element) { + const rect = element.getBoundingClientRect(); + return rect.bottom <= window.innerHeight; +} + +/** + * Calculates the percentage of days remaining between two dates. + * + * @param {Object} task - Task from list of tasks assigned to a user + * @returns {number} A new UI HTML for better UI/UX + */ +const generateCardUIInDev = (task) => { + const isDeadLineCrossed = Date.now() > task.endsOn * 1000; + const isTaskRed = + isDeadLineCrossed && + task?.percentCompleted !== 0 && + task?.status !== 'COMPLETED'; + + const deadlineDays = task?.endsOn + ? dateDiff(Date.now(), task.endsOn * 1000, (d) => + isDeadLineCrossed ? d + ' ago' : 'in ' + d, + ) + : 'N/A'; + const progressBarClassname = handleProgressColor( + task?.percentCompleted, + task?.startedOn, + task?.endsOn, + ); + const startedOnDays = task?.startedOn + ? dateDiff(Date.now(), task?.startedOn * 1000, (d) => d + ' ago') + : 'N/A'; + + return ` +
+
+ + ${ + !noProgressbarStatuses.includes(task.status) + ? `
+ 0 ? progressBarClassname : '' + } id="file" value="${task?.percentCompleted}" max="100"> ${ + task?.percentCompleted + } +
${task?.percentCompleted ?? 'N/A'}%
+
` + : '' + } +
+
+
+

Estimated Completion

+

${deadlineDays || 'N/A'}

+
+
+

Status

+

${task.status || 'N/A'}

+
+
+
+
+

Started On

+

${startedOnDays || 'N/A'}

+
+
+

Priority

+

${task?.priority || 'N/A'}

+
+
+
+
+

Created By

+

${task?.createdBy || 'N/A'}

+
+
+

Type

+

${task?.type || 'N/A'}

+
+
+
+ `; +}; diff --git a/utils.js b/utils.js index 89670272..07f1003b 100644 --- a/utils.js +++ b/utils.js @@ -10,7 +10,7 @@ async function getSelfUser(endpoint = '/users/self') { if (endpoint === '/users/self') { const self_user = await res.json(); if (res.status === 200) { - return self_user; + return self_user?.user || self_user; } } else { location.reload(); @@ -69,3 +69,34 @@ function debounce(func, delay) { async function addDelay(milliSeconds) { await new Promise((resolve) => setTimeout(resolve, milliSeconds)); } +function dateDiff(date1, date2, formatter) { + if (date2 > date1) { + return dateDiff(date2, date1, formatter); + } + + const timeDifference = new Date(date1).getTime() - new Date(date2).getTime(); + + const seconds = Math.floor(timeDifference / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + const months = Math.floor(days / 30); + const years = Math.floor(days / 365); + + let res; + if (seconds < 60) { + res = `${seconds} ${seconds === 1 ? 'second' : 'seconds'}`; + } else if (minutes < 60) { + res = `${minutes} ${minutes === 1 ? 'minute' : 'minutes'}`; + } else if (hours < 24) { + res = `${hours} ${hours === 1 ? 'hour' : 'hours'}`; + } else if (days < 30) { + res = `${days} ${days === 1 ? 'day' : 'days'}`; + } else if (months < 12) { + res = `${months} ${months === 1 ? 'month' : 'months'}`; + } else { + res = `${years} ${years === 1 ? 'year' : 'years'}`; + } + + return formatter ? formatter(res) : res; +}