From db44f8da0d9c016b3f68e240497ca38885dc216a Mon Sep 17 00:00:00 2001 From: tanishqsingla Date: Sat, 15 Jul 2023 07:20:10 +0530 Subject: [PATCH 1/7] feat: Add task request details page --- taskRequests/details/index.html | 71 ++++++++++ taskRequests/details/script.js | 206 +++++++++++++++++++++++++++ taskRequests/details/style.css | 242 ++++++++++++++++++++++++++++++++ taskRequests/index.html | 1 + taskRequests/script.js | 35 +---- taskRequests/util.js | 25 ++++ 6 files changed, 551 insertions(+), 29 deletions(-) create mode 100644 taskRequests/details/index.html create mode 100644 taskRequests/details/script.js create mode 100644 taskRequests/details/style.css create mode 100644 taskRequests/util.js diff --git a/taskRequests/details/index.html b/taskRequests/details/index.html new file mode 100644 index 00000000..8197e1f1 --- /dev/null +++ b/taskRequests/details/index.html @@ -0,0 +1,71 @@ + + + + + + + + + + + + + Task Requests | Real Dev Squad + + + + + +
+
+ RDS logo + Home +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+
+

Requestors

+
    +
      +
    • +
    • +
    • +
    • +
    +
    +
    +
    + + diff --git a/taskRequests/details/script.js b/taskRequests/details/script.js new file mode 100644 index 00000000..6d38e759 --- /dev/null +++ b/taskRequests/details/script.js @@ -0,0 +1,206 @@ +const API_BASE_URL = window.API_BASE_URL; + +const taskRequestStatus = { + WAITING: 'WAITING', + APPROVED: 'APPROVED', +}; + +let taskRequest; + +const taskRequestSkeleton = document.querySelector('.taskRequest__skeleton'); +const taskSkeleton = document.querySelector('.task__skeleton'); +const requestorSkeleton = document.querySelector( + '.requestors__container__list__skeleton', +); + +const taskRequestContainer = document.getElementById('task-request-details'); +const taskContainer = document.getElementById('task-details'); +const requestorsContainer = document.getElementById('requestors-details'); + +const taskRequestId = new URLSearchParams(window.location.search).get('id'); +history.pushState({}, '', window.location.href); + +let taskId; + +function renderTaskRequestDetails(taskRequest) { + taskRequestContainer.append( + createCustomElement({ + tagName: 'h1', + textContent: `Task Request `, + class: 'taskRequest__title', + child: [ + createCustomElement({ + tagName: 'span', + class: 'taskRequest__title__subtitle', + textContent: `#${taskRequest.id}`, + }), + ], + }), + createCustomElement({ + tagName: 'p', + textContent: 'Status: ', + class: 'taskRequest__status', + child: [ + createCustomElement({ + tagName: 'span', + textContent: taskRequest.status, + class: [ + 'taskRequest__status__chip', + `taskRequest__status__chip--${taskRequest.status.toLowerCase()}`, + ], + }), + ], + }), + ); +} + +async function renderTaskDetails(taskId) { + try { + const res = await fetch(`${API_BASE_URL}/tasks/${taskId}/details`); + taskSkeleton.classList.add('hidden'); + const data = await res.json(); + + const { taskData } = data; + + taskContainer.append( + createCustomElement({ + tagName: 'h2', + class: 'task__title', + textContent: taskData.title, + }), + createCustomElement({ + tagName: 'p', + class: 'task_type', + textContent: 'Type: ', + child: [ + createCustomElement({ + tagName: 'span', + class: ['task__type__chip', `task__type__chip--${taskData.type}`], + textContent: taskData.type, + }), + taskData.isNoteworthy + ? createCustomElement({ + tagName: 'span', + class: ['task__type__chip', `task__type__chip--noteworthy`], + textContent: 'Note worthy', + }) + : '', + ], + }), + createCustomElement({ + tagName: 'p', + class: 'task__createdBy', + textContent: `Created By: `, + child: [ + createCustomElement({ + tagName: 'a', + href: `https://members.realdevsquad.com/${taskData.createdBy}`, + textContent: taskData.createdBy, + }), + ], + }), + createCustomElement({ + tagName: 'p', + class: 'task__purpose', + textContent: taskData.purpose, + }), + ); + } catch (e) { + console.log(e); + } +} + +function getAvatar(user) { + if (user.user?.picture?.url) { + return createCustomElement({ + tagName: 'img', + src: user.user.picture.url, + alt: user.user.first_name, + title: user.user.first_name, + }); + } + return createCustomElement({ + tagName: 'span', + title: user.user.first_name, + textContent: user.user.first_name[0], + }); +} + +function getActionButton(requestor) { + if (taskRequest.status === taskRequestStatus.APPROVED) { + if (taskRequest?.approvedTo === requestor.user.id) { + return createCustomElement({ + tagName: 'p', + textContent: 'Approved', + class: ['requestors__container__list__approved'], + }); + } else { + return ''; + } + } + return createCustomElement({ + tagName: 'button', + textContent: 'Approve', + class: 'requestors__conatainer__list__button', + }); +} + +async function renderRequestors(requestors) { + requestorSkeleton.classList.remove('hidden'); + const data = await Promise.all( + requestors.map((requestor) => { + return fetch(`${API_BASE_URL}/users/userId/${requestor}`).then((res) => + res.json(), + ); + }), + ); + + requestorSkeleton.classList.add('hidden'); + + data.forEach((requestor) => { + requestorsContainer.append( + createCustomElement({ + tagName: 'li', + child: [ + createCustomElement({ + tagName: 'div', + class: 'requestors__container__list__userDetails', + child: [ + createCustomElement({ + tagName: 'div', + class: 'requestors__container__list__userDetails__avatar', + child: [getAvatar(requestor)], + }), + createCustomElement({ + tagName: 'p', + textContent: requestor.user.first_name, + }), + ], + }), + getActionButton(requestor), + ], + }), + ); + }); +} + +const fetchTaskRequest = async () => { + taskRequestSkeleton.classList.remove('hidden'); + taskContainer.classList.remove('hidden'); + try { + const res = await fetch(`${API_BASE_URL}/taskRequests/${taskRequestId}`); + const data = await res.json(); + taskRequestSkeleton.classList.add('hidden'); + taskRequest = data.data; + + renderTaskRequestDetails(data.data); + renderTaskDetails(data.data.taskId); + renderRequestors(data.data.requestors); + + return data; + } catch (e) { + console.log(e); + } +}; + +fetchTaskRequest(); diff --git a/taskRequests/details/style.css b/taskRequests/details/style.css new file mode 100644 index 00000000..f879bf37 --- /dev/null +++ b/taskRequests/details/style.css @@ -0,0 +1,242 @@ +:root { + font-family: 'Inter', sans-serif; +} + +body { + padding: 0; + margin: 0; +} + +.hidden { + display: none !important; +} + +.skeleton { + animation: skeleton 2s linear infinite; + border-radius: 0.5rem; + min-height: 0.5rem; + margin: 0.5rem 0; +} + +.header { + background: #1d1283; + padding: 1rem; +} +.header__contents { + max-width: 1440px; + padding: 0.5rem 1rem; + margin: 0 auto; + color: white; + display: flex; + align-items: center; + gap: 0.5rem; +} +.header__contents__navlink { + color: white; + text-decoration: none; +} +.header__contents__navlink:hover { + text-decoration: underline; +} + +.container { + max-width: 1440px; + margin: 0 auto; + display: grid; + grid-template-columns: repeat(12, 1fr); +} + +.taskRequest { + padding: 1rem; + grid-column: 1 / span 12; +} +.taskRequest__skeleton__title { + height: 1.5rem; + width: 50ch; + margin: 0.5rem 0; +} +.taskRequest__skeleton__subtitle { + height: 1rem; + max-width: 30ch; + animation: skeleton 2s linear infinite; +} +.taskRequest__title { + font-weight: 400; + font-size: 2rem; + line-height: 2.5rem; +} +.taskRequest__title__subtitle { + font-size: 1rem; + font-weight: 700; + color: #888; + font-size: 0.875rem; +} +.taskRequest__status__chip { + padding: 0.5rem; + line-height: 1.5rem; + border-radius: 1rem; + font-weight: 700; +} +.taskRequest__status__chip--approved { + background: #e1f9f1; + color: #19805e; +} +.taskRequest__status__chip--waiting { + background: #fcf1e0; + color: #c78112; +} + +.task__skeleton__title { + height: 1.25rem; + max-width: 45ch; +} +.task__skeleton__details { + height: 0.75rem; + max-width: 20ch; +} +.task__skeleton__description { + height: 0.75rem; + max-width: 75ch; +} + +.task { + grid-column: 1 / span 8; + padding: 1rem; +} +.task__title { + font-size: 1.5rem; + line-height: 2rem; + color: #1d1283; + margin: 0; +} +.task__purpose { + font-size: 0.875rem; + line-height: 1.25rem; + margin-top: 1rem; + max-width: 80ch; +} +.task__type__chip { + padding: 0.5rem; + line-height: 1.5rem; + border-radius: 1rem; + font-weight: 700; + margin: 0 0.25rem; + white-space: nowrap; +} +.task__type__chip--feature { + background: #dfe4ff; + border: solid 1px #9eadfe; + color: #0224df; +} +.task__type__chip--refactor { + background: #fadee0; + border: solid 1px #f19ca1; + color: #ae1820; +} +.task__type__chip--bug { + background: #e1f9f1; + border: solid 1px #7fe6c4; + color: #14664b; +} +.task__type__chip--noteworthy { + background: #14664b; + color: white; +} + +.requestors { + grid-column: auto / span 4; + padding: 1rem; + align-self: flex-start; + border-left: solid 1px rgba(0, 0, 0, 0.1); +} +.requestors__container__title { + font-size: 1.375rem; + line-height: 1.75rem; + font-weight: 400; + margin: 0; +} +.requestors__container__list { + list-style-type: none; + padding: 0; +} +.requestors__container__list li { + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: center; +} +.requestors__container__list__userDetails { + display: flex; + gap: 1rem; + align-items: center; +} +.requestors__container__list__userDetails__avatar { + height: 2rem; + width: 2rem; + display: grid; + background-color: #e2e2e2; + place-items: center; + border-radius: 50%; +} +.requestors__container__list li:nth-child(even) { + background: #eee; +} +.requestors__conatainer__list__button { + padding: 0.375rem 0.5rem; + background: #fff; + border: solid 1px #19805e; + font-weight: 700; + font-size: 1rem; + line-height: 1.5rem; + color: #19805e; + border-radius: 0.25rem; + cursor: pointer; +} +.requestors__conatainer__list__button:hover { + color: white; + background: #19805e; + transition: 0.3s ease-in-out; +} +.requestors__container__list__approved { + background: transparent; + border: none; + color: #c3c3c3; + font-weight: 600; +} + +@keyframes skeleton { + 0% { + background: hsl(0, 0%, 75%); + } + 50% { + background: hsl(0, 0%, 95%); + } + 100% { + background: hsl(0, 0%, 75%); + } +} + +@media (max-width: 599px) { + .taskRequest__title { + font-size: 1.5rem; + line-height: 1.75rem; + } + .taskRequest__title__subtitle { + font-size: 0.875rem; + line-height: 1rem; + } + .taskRequest__status { + font-size: 0.75rem; + } +} + +@media (max-width: 904px) { + .task { + grid-column: 1 / span 12; + } + + .requestors { + grid-column: 1 / span 12; + border: none; + } +} diff --git a/taskRequests/index.html b/taskRequests/index.html index 37fff320..76b23433 100644 --- a/taskRequests/index.html +++ b/taskRequests/index.html @@ -16,6 +16,7 @@ + diff --git a/taskRequests/script.js b/taskRequests/script.js index d19a5ab2..cd6304d5 100644 --- a/taskRequests/script.js +++ b/taskRequests/script.js @@ -9,32 +9,6 @@ const loader = document.querySelector('.container__body__loader'); const startLoading = () => loader.classList.remove('hidden'); const stopLoading = () => loader.classList.add('hidden'); -function createCustomElement(domObjectMap) { - const el = document.createElement(domObjectMap.tagName); - for (const [key, value] of Object.entries(domObjectMap)) { - if (key === 'tagName') { - continue; - } - if (key === 'eventListeners') { - value.forEach((obj) => { - el.addEventListener(obj.event, obj.func); - }); - } - if (key === 'class') { - if (Array.isArray(value)) { - el.classList.add(...value); - } else { - el.classList.add(value); - } - } else if (key === 'child') { - el.append(...value); - } else { - el[key] = value; - } - } - return el; -} - async function getTaskRequests() { startLoading(); try { @@ -101,9 +75,12 @@ function getRemainingCount(requestors) { }); } } - +console.log(window.location.hostname); function openTaskDetails(id) { - window.location.href = new URL(`/taskRequest/details?id=${id}`, API_BASE_URL); + const url = new URL(`/taskRequests/details`, window.location.href); + + url.searchParams.append('id', id); + window.location.href = url; } function createTaskRequestCard({ id, task, requestors, status }) { @@ -177,7 +154,7 @@ function renderTaskRequestCards(taskRequests) { if (taskRequests.length > 0) { filterContainer.classList.remove('hidden'); taskRequests.forEach((taskRequest) => { - taskRequestContainer.appendChild(createTaskRequestCard(taskRequest)); + taskRequestDetails.appendChild(createTaskRequestCard(taskRequest)); }); } } diff --git a/taskRequests/util.js b/taskRequests/util.js new file mode 100644 index 00000000..99a9a276 --- /dev/null +++ b/taskRequests/util.js @@ -0,0 +1,25 @@ +function createCustomElement(domObjectMap) { + const el = document.createElement(domObjectMap.tagName); + for (const [key, value] of Object.entries(domObjectMap)) { + if (key === 'tagName') { + continue; + } + if (key === 'eventListeners') { + value.forEach((obj) => { + el.addEventListener(obj.event, obj.func); + }); + } + if (key === 'class') { + if (Array.isArray(value)) { + el.classList.add(...value); + } else { + el.classList.add(value); + } + } else if (key === 'child') { + el.append(...value); + } else { + el[key] = value; + } + } + return el; +} From 24caf8532ce1e8a3fcbebeccfaef2c22e8657129 Mon Sep 17 00:00:00 2001 From: tanishqsingla Date: Sat, 15 Jul 2023 07:36:42 +0530 Subject: [PATCH 2/7] fix: task request title skeleton --- taskRequests/details/style.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/taskRequests/details/style.css b/taskRequests/details/style.css index f879bf37..c0f27415 100644 --- a/taskRequests/details/style.css +++ b/taskRequests/details/style.css @@ -239,4 +239,12 @@ body { grid-column: 1 / span 12; border: none; } + + .taskRequest__skeleton__title { + max-width: 80%; + height: 1rem; + } + .taskRequest__skeleton__subtitle { + max-width: 40%; + } } From 0ff42db2d903e3247cfbe54366b3c70fcda173db Mon Sep 17 00:00:00 2001 From: tanishqsingla Date: Sat, 15 Jul 2023 07:55:25 +0530 Subject: [PATCH 3/7] fix: console statements and variable names --- taskRequests/script.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/taskRequests/script.js b/taskRequests/script.js index cd6304d5..9b8ec65e 100644 --- a/taskRequests/script.js +++ b/taskRequests/script.js @@ -75,7 +75,6 @@ function getRemainingCount(requestors) { }); } } -console.log(window.location.hostname); function openTaskDetails(id) { const url = new URL(`/taskRequests/details`, window.location.href); @@ -154,7 +153,7 @@ function renderTaskRequestCards(taskRequests) { if (taskRequests.length > 0) { filterContainer.classList.remove('hidden'); taskRequests.forEach((taskRequest) => { - taskRequestDetails.appendChild(createTaskRequestCard(taskRequest)); + taskRequestContainer.appendChild(createTaskRequestCard(taskRequest)); }); } } From b8d4b35edc704c98a0a2852da0d14e2901794718 Mon Sep 17 00:00:00 2001 From: tanishqsingla Date: Sat, 15 Jul 2023 08:40:23 +0530 Subject: [PATCH 4/7] feat: Add approve api call to button --- taskRequests/details/script.js | 50 +++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/taskRequests/details/script.js b/taskRequests/details/script.js index 6d38e759..cf666f26 100644 --- a/taskRequests/details/script.js +++ b/taskRequests/details/script.js @@ -126,6 +126,31 @@ function getAvatar(user) { }); } +async function approveTaskRequest(userId) { + try { + console.log(taskRequestId, userId); + const res = await fetch(`${API_BASE_URL}/taskRequests/approve`, { + credentials: 'include', + method: 'PATCH', + body: JSON.stringify({ + taskRequestId: taskRequestId, + userId, + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (res.ok) { + taskRequest = await fetchTaskRequest(); + requestorsContainer.innerHTML = ''; + renderRequestors(taskRequest.requestors); + } + } catch (e) { + console.log(e); + } +} + function getActionButton(requestor) { if (taskRequest.status === taskRequestStatus.APPROVED) { if (taskRequest?.approvedTo === requestor.user.id) { @@ -142,6 +167,9 @@ function getActionButton(requestor) { tagName: 'button', textContent: 'Approve', class: 'requestors__conatainer__list__button', + eventListeners: [ + { event: 'click', func: () => approveTaskRequest(requestor.user.id) }, + ], }); } @@ -184,23 +212,25 @@ async function renderRequestors(requestors) { }); } -const fetchTaskRequest = async () => { +async function fetchTaskRequest() { + const res = await fetch(`${API_BASE_URL}/taskRequests/${taskRequestId}`); + const data = await res.json(); + return data.data; +} + +const renderTaskRequest = async () => { taskRequestSkeleton.classList.remove('hidden'); taskContainer.classList.remove('hidden'); try { - const res = await fetch(`${API_BASE_URL}/taskRequests/${taskRequestId}`); - const data = await res.json(); + taskRequest = await fetchTaskRequest(); taskRequestSkeleton.classList.add('hidden'); - taskRequest = data.data; - - renderTaskRequestDetails(data.data); - renderTaskDetails(data.data.taskId); - renderRequestors(data.data.requestors); - return data; + renderTaskRequestDetails(taskRequest); + renderTaskDetails(taskRequest.taskId); + renderRequestors(taskRequest.requestors); } catch (e) { console.log(e); } }; -fetchTaskRequest(); +renderTaskRequest(); From b7e5759edccc13cfe3a5a498287d5654fe756ccc Mon Sep 17 00:00:00 2001 From: tanishqsingla Date: Sat, 15 Jul 2023 19:18:02 +0530 Subject: [PATCH 5/7] fix: Review comments Use console.error in catch statement Avoid unexpected error due to undefined value --- taskRequests/details/script.js | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/taskRequests/details/script.js b/taskRequests/details/script.js index cf666f26..4eb4838c 100644 --- a/taskRequests/details/script.js +++ b/taskRequests/details/script.js @@ -60,25 +60,30 @@ async function renderTaskDetails(taskId) { taskSkeleton.classList.add('hidden'); const data = await res.json(); - const { taskData } = data; + const { taskData } = data ?? {}; taskContainer.append( createCustomElement({ tagName: 'h2', class: 'task__title', - textContent: taskData.title, + textContent: taskData?.title || 'N/A', }), createCustomElement({ tagName: 'p', class: 'task_type', textContent: 'Type: ', child: [ - createCustomElement({ - tagName: 'span', - class: ['task__type__chip', `task__type__chip--${taskData.type}`], - textContent: taskData.type, - }), - taskData.isNoteworthy + taskData?.type + ? createCustomElement({ + tagName: 'span', + class: [ + 'task__type__chip', + `task__type__chip--${taskData.type}`, + ], + textContent: taskData?.type, + }) + : '', + taskData?.isNoteworthy ? createCustomElement({ tagName: 'span', class: ['task__type__chip', `task__type__chip--noteworthy`], @@ -95,18 +100,18 @@ async function renderTaskDetails(taskId) { createCustomElement({ tagName: 'a', href: `https://members.realdevsquad.com/${taskData.createdBy}`, - textContent: taskData.createdBy, + textContent: taskData?.createdBy || 'N/A', }), ], }), createCustomElement({ tagName: 'p', class: 'task__purpose', - textContent: taskData.purpose, + textContent: taskData?.purpose || 'N/A', }), ); } catch (e) { - console.log(e); + console.error(e); } } @@ -128,7 +133,7 @@ function getAvatar(user) { async function approveTaskRequest(userId) { try { - console.log(taskRequestId, userId); + console.error(taskRequestId, userId); const res = await fetch(`${API_BASE_URL}/taskRequests/approve`, { credentials: 'include', method: 'PATCH', @@ -147,7 +152,7 @@ async function approveTaskRequest(userId) { renderRequestors(taskRequest.requestors); } } catch (e) { - console.log(e); + console.error(e); } } @@ -229,7 +234,7 @@ const renderTaskRequest = async () => { renderTaskDetails(taskRequest.taskId); renderRequestors(taskRequest.requestors); } catch (e) { - console.log(e); + console.error(e); } }; From 8d33633854eb206859e7bc4a7e9624f1d251ae4c Mon Sep 17 00:00:00 2001 From: tanishqsingla Date: Sun, 16 Jul 2023 03:04:27 +0530 Subject: [PATCH 6/7] fix: use console.error instead of console.log for catch --- taskRequests/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskRequests/script.js b/taskRequests/script.js index 9b8ec65e..e17c53d3 100644 --- a/taskRequests/script.js +++ b/taskRequests/script.js @@ -37,7 +37,7 @@ async function getTaskRequests() { showMessage('ERROR', ErrorMessages.SERVER_ERROR); } catch (e) { - console.log(e); + console.error(e); } finally { stopLoading(); } From 838820024a153f3c22126bd5f20c83056b8e5279 Mon Sep 17 00:00:00 2001 From: tanishqsingla Date: Sun, 30 Jul 2023 20:46:44 +0530 Subject: [PATCH 7/7] fix: review comments --- taskRequests/details/script.js | 3 +-- taskRequests/details/style.css | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/taskRequests/details/script.js b/taskRequests/details/script.js index 4eb4838c..6df3faf5 100644 --- a/taskRequests/details/script.js +++ b/taskRequests/details/script.js @@ -98,8 +98,7 @@ async function renderTaskDetails(taskId) { textContent: `Created By: `, child: [ createCustomElement({ - tagName: 'a', - href: `https://members.realdevsquad.com/${taskData.createdBy}`, + tagName: 'p', textContent: taskData?.createdBy || 'N/A', }), ], diff --git a/taskRequests/details/style.css b/taskRequests/details/style.css index c0f27415..e3ec5a74 100644 --- a/taskRequests/details/style.css +++ b/taskRequests/details/style.css @@ -7,10 +7,6 @@ body { margin: 0; } -.hidden { - display: none !important; -} - .skeleton { animation: skeleton 2s linear infinite; border-radius: 0.5rem; @@ -204,6 +200,10 @@ body { font-weight: 600; } +.hidden { + display: none; +} + @keyframes skeleton { 0% { background: hsl(0, 0%, 75%);