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
+
+
+
+
+
+
+
+
+
diff --git a/taskRequests/details/script.js b/taskRequests/details/script.js
new file mode 100644
index 00000000..6df3faf5
--- /dev/null
+++ b/taskRequests/details/script.js
@@ -0,0 +1,240 @@
+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 || 'N/A',
+ }),
+ createCustomElement({
+ tagName: 'p',
+ class: 'task_type',
+ textContent: 'Type: ',
+ child: [
+ 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`],
+ textContent: 'Note worthy',
+ })
+ : '',
+ ],
+ }),
+ createCustomElement({
+ tagName: 'p',
+ class: 'task__createdBy',
+ textContent: `Created By: `,
+ child: [
+ createCustomElement({
+ tagName: 'p',
+ textContent: taskData?.createdBy || 'N/A',
+ }),
+ ],
+ }),
+ createCustomElement({
+ tagName: 'p',
+ class: 'task__purpose',
+ textContent: taskData?.purpose || 'N/A',
+ }),
+ );
+ } catch (e) {
+ console.error(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],
+ });
+}
+
+async function approveTaskRequest(userId) {
+ try {
+ console.error(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.error(e);
+ }
+}
+
+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',
+ eventListeners: [
+ { event: 'click', func: () => approveTaskRequest(requestor.user.id) },
+ ],
+ });
+}
+
+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),
+ ],
+ }),
+ );
+ });
+}
+
+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 {
+ taskRequest = await fetchTaskRequest();
+ taskRequestSkeleton.classList.add('hidden');
+
+ renderTaskRequestDetails(taskRequest);
+ renderTaskDetails(taskRequest.taskId);
+ renderRequestors(taskRequest.requestors);
+ } catch (e) {
+ console.error(e);
+ }
+};
+
+renderTaskRequest();
diff --git a/taskRequests/details/style.css b/taskRequests/details/style.css
new file mode 100644
index 00000000..e3ec5a74
--- /dev/null
+++ b/taskRequests/details/style.css
@@ -0,0 +1,250 @@
+:root {
+ font-family: 'Inter', sans-serif;
+}
+
+body {
+ padding: 0;
+ margin: 0;
+}
+
+.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;
+}
+
+.hidden {
+ display: none;
+}
+
+@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;
+ }
+
+ .taskRequest__skeleton__title {
+ max-width: 80%;
+ height: 1rem;
+ }
+ .taskRequest__skeleton__subtitle {
+ max-width: 40%;
+ }
+}
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..e17c53d3 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 {
@@ -63,7 +37,7 @@ async function getTaskRequests() {
showMessage('ERROR', ErrorMessages.SERVER_ERROR);
} catch (e) {
- console.log(e);
+ console.error(e);
} finally {
stopLoading();
}
@@ -101,9 +75,11 @@ function getRemainingCount(requestors) {
});
}
}
-
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 }) {
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;
+}