From 7c40c594841158e17a91585b9d61af7f9b6d6354 Mon Sep 17 00:00:00 2001 From: cmekeirl Date: Sun, 28 Apr 2024 16:04:17 +0200 Subject: [PATCH 1/4] adjusted courses frontend to use deadlines Date instantiated right after fetch instead of str str 2d --- .../Courses/CourseDetailTeacher.tsx | 9 +++-- .../Courses/CourseUtilComponents.tsx | 39 +++++++++++++++---- .../src/components/Courses/CourseUtils.tsx | 38 ++++++++++++++---- 3 files changed, 68 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/Courses/CourseDetailTeacher.tsx b/frontend/src/components/Courses/CourseDetailTeacher.tsx index 03308c3c..02963470 100644 --- a/frontend/src/components/Courses/CourseDetailTeacher.tsx +++ b/frontend/src/components/Courses/CourseDetailTeacher.tsx @@ -18,7 +18,7 @@ import { } from "@mui/material"; import { ChangeEvent, useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Course, Project, apiHost, getIdFromLink, getNearestFutureDate, getUserName, appHost } from "./CourseUtils"; +import { Course, Project, apiHost, getIdFromLink, getNearestFutureDate, getUserName, appHost, ProjectDetail } from "./CourseUtils"; import { Link, useNavigate, NavigateFunction, useLoaderData } from "react-router-dom"; import { Title } from "../Header/Title"; import ClearIcon from '@mui/icons-material/Clear'; @@ -103,10 +103,11 @@ export function CourseDetailTeacher(): JSX.Element { const courseDetail = useLoaderData() as { //TODO CATCH ERROR course: Course , - projects:Project[] , + projects:ProjectDetail[] , admins: UserUid[], students: UserUid[] }; + const { course, projects, admins, students } = courseDetail; const { t } = useTranslation('translation', { keyPrefix: 'courseDetailTeacher' }); const { i18n } = useTranslation(); @@ -179,7 +180,7 @@ export function CourseDetailTeacher(): JSX.Element { * @param projects - The array of projects. * @returns Either a place holder for no projects or a grid of cards describing the projects. */ -function EmptyOrNotProjects({projects}: {projects: Project[]}): JSX.Element { +function EmptyOrNotProjects({projects}: {projects: ProjectDetail[]}): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'courseDetailTeacher' }); if(projects === undefined || projects.length === 0){ return ( @@ -199,7 +200,7 @@ function EmptyOrNotProjects({projects}: {projects: Project[]}): JSX.Element { {getNearestFutureDate(project.deadlines) && ( - {`${t('deadline')}: ${getNearestFutureDate(project.deadlines)?.toLocaleDateString()}`} + {`${t('deadline')}: ${getNearestFutureDate(project.deadlines)?.[1].toLocaleDateString()}`} )} diff --git a/frontend/src/components/Courses/CourseUtilComponents.tsx b/frontend/src/components/Courses/CourseUtilComponents.tsx index 48d6bdb2..15aab70b 100644 --- a/frontend/src/components/Courses/CourseUtilComponents.tsx +++ b/frontend/src/components/Courses/CourseUtilComponents.tsx @@ -13,6 +13,7 @@ import { import { Course, Project, + ProjectDetail, apiHost, getIdFromLink, getNearestFutureDate, @@ -100,7 +101,7 @@ export function SideScrollableCourses({ const [teacherNameFilter, setTeacherNameFilter] = useState( initialTeacherNameFilter ); - const [projects, setProjects] = useState<{ [courseId: string]: Project[] }>( + const [projects, setProjects] = useState<{ [courseId: string]: ProjectDetail[] }>( {} ); @@ -159,10 +160,33 @@ export function SideScrollableCourses({ ); const projectResults = await Promise.all(projectPromises); - const projectsMap: { [courseId: string]: Project[] } = {}; + const projectsMap: { [courseId: string]: ProjectDetail[] } = {}; projectResults.forEach((result, index) => { - projectsMap[getIdFromLink(courses[index].course_id)] = result.data; + const detailProjectPromises = result.data.map(async (item: Project) => { + const projectRes = await authenticatedFetch(item.project_id); + if (projectRes.status !== 200) { + throw new Response("Failed to fetch project data", { + status: projectRes.status, + }); + } + const projectJson = await projectRes.json(); + const projectData = projectJson.data; + const project: ProjectDetail = { + ...item, + deadlines: projectData.deadlines.map( + ([description, dateString]: [string, string]) => [ + description, + new Date(dateString), + ] + ), + }; + return project; + }); + Promise.all(detailProjectPromises).then((projects) => { + projectsMap[getIdFromLink(courses[index].course_id)] = projects; + setProjects({ ...projectsMap }); + }); }); setProjects(projectsMap); @@ -216,7 +240,7 @@ function EmptyOrNotFilteredCourses({ projects, }: { filteredCourses: Course[]; - projects: { [courseId: string]: Project[] }; + projects: { [courseId: string]: ProjectDetail[] }; }): JSX.Element { const { t } = useTranslation("translation", { keyPrefix: "courseDetailTeacher", @@ -286,7 +310,7 @@ function EmptyOrNotProjects({ projects, noProjectsText, }: { - projects: Project[]; + projects: ProjectDetail[]; noProjectsText: string; }): JSX.Element { if (projects === undefined || projects.length === 0) { @@ -305,10 +329,11 @@ function EmptyOrNotProjects({ {projects.slice(0, 3).map((project) => { let timeLeft = ""; if (project.deadlines != undefined) { - const deadlineDate = getNearestFutureDate(project.deadlines); - if (deadlineDate == null) { + const deadline = getNearestFutureDate(project.deadlines); + if (deadline == null) { return <>; } + const deadlineDate = deadline[1]; const diffTime = Math.abs(deadlineDate.getTime() - now.getTime()); const diffHours = Math.ceil(diffTime / (1000 * 60 * 60)); const diffDays = Math.ceil(diffHours * 24); diff --git a/frontend/src/components/Courses/CourseUtils.tsx b/frontend/src/components/Courses/CourseUtils.tsx index 331b5ea5..4fbb28be 100644 --- a/frontend/src/components/Courses/CourseUtils.tsx +++ b/frontend/src/components/Courses/CourseUtils.tsx @@ -12,7 +12,12 @@ export interface Course { export interface Project { title: string; project_id: string; - deadlines: string[][]; +} + +export interface ProjectDetail { + title: string; + project_id: string; + deadlines: [string, Date][]; } export const apiHost = import.meta.env.VITE_APP_API_HOST; @@ -84,11 +89,9 @@ export function getIdFromLink(link: string): string { * @param dates - Array of dates * @returns The nearest future date */ -export function getNearestFutureDate(dates: string[][]): Date | null { +export function getNearestFutureDate(dates: [string, Date][]): [string, Date] | null { const now = new Date(); - const futureDates = dates - .map((date) => new Date(date[1])) - .filter((date) => date > now); + const futureDates = dates.filter((date) => date[1] > now); if (futureDates.length === 0) return null; return futureDates.reduce((nearest, current) => current < nearest ? current : nearest @@ -125,7 +128,28 @@ const dataLoaderCourse = async (courseId: string) => { const dataLoaderProjects = async (courseId: string) => { const params = new URLSearchParams({ course_id: courseId }); - return fetchData(`projects`, params); + const uri = `${apiHost}/projects?${params}`; + + const res = await authenticatedFetch(uri); + if (res.status !== 200) { + throw new Response("Failed to fetch data", { status: res.status }); + } + const jsonResult = await res.json(); + const projects: ProjectDetail[] = jsonResult.data.map(async (item: Project) => { + const projectRes = await authenticatedFetch(item.project_id); + if (projectRes.status !== 200) { + throw new Response("Failed to fetch project data", { status: projectRes.status }); + } + const projectJson = await projectRes.json(); + const projectData = projectJson.data; + const project: ProjectDetail = { + ...item, + deadlines: projectData.deadlines.map(([description, dateString]: [string, string]) => [description, new Date(dateString)]) + }; + return project; + }); + + return Promise.all(projects); }; const dataLoaderAdmins = async (courseId: string) => { @@ -145,10 +169,10 @@ export const dataLoaderCourseDetail = async ({ if (!courseId) { throw new Error("Course ID is undefined."); } + const course = await dataLoaderCourse(courseId); const projects = await dataLoaderProjects(courseId); const admins = await dataLoaderAdmins(courseId); const students = await dataLoaderStudents(courseId); - return { course, projects, admins, students }; }; From c04106a2953ec412e6671935c8d6136ad1dbda44 Mon Sep 17 00:00:00 2001 From: cmekeirl Date: Sun, 28 Apr 2024 16:24:36 +0200 Subject: [PATCH 2/4] lintr --- frontend/src/components/Courses/CourseDetailTeacher.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Courses/CourseDetailTeacher.tsx b/frontend/src/components/Courses/CourseDetailTeacher.tsx index 02963470..7605fa9b 100644 --- a/frontend/src/components/Courses/CourseDetailTeacher.tsx +++ b/frontend/src/components/Courses/CourseDetailTeacher.tsx @@ -18,7 +18,7 @@ import { } from "@mui/material"; import { ChangeEvent, useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Course, Project, apiHost, getIdFromLink, getNearestFutureDate, getUserName, appHost, ProjectDetail } from "./CourseUtils"; +import { Course, apiHost, getIdFromLink, getNearestFutureDate, getUserName, appHost, ProjectDetail } from "./CourseUtils"; import { Link, useNavigate, NavigateFunction, useLoaderData } from "react-router-dom"; import { Title } from "../Header/Title"; import ClearIcon from '@mui/icons-material/Clear'; From 3d71914b3df19873ad08bad4572d4793196f9b5b Mon Sep 17 00:00:00 2001 From: cmekeirl Date: Sun, 28 Apr 2024 23:18:06 +0200 Subject: [PATCH 3/4] removed TODO --- frontend/src/components/Courses/CourseDetailTeacher.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Courses/CourseDetailTeacher.tsx b/frontend/src/components/Courses/CourseDetailTeacher.tsx index 7605fa9b..c9f0d278 100644 --- a/frontend/src/components/Courses/CourseDetailTeacher.tsx +++ b/frontend/src/components/Courses/CourseDetailTeacher.tsx @@ -101,7 +101,7 @@ export function CourseDetailTeacher(): JSX.Element { setAnchorElStudent(null); }; - const courseDetail = useLoaderData() as { //TODO CATCH ERROR + const courseDetail = useLoaderData() as { course: Course , projects:ProjectDetail[] , admins: UserUid[], From 503269561e713a4e8e69f28fa9e5953bf4282d06 Mon Sep 17 00:00:00 2001 From: cmekeirl Date: Tue, 30 Apr 2024 09:05:56 +0200 Subject: [PATCH 4/4] deadlines now have their own datatype! --- .../Courses/CourseDetailTeacher.tsx | 2 +- .../Courses/CourseUtilComponents.tsx | 21 +++++++-------- .../src/components/Courses/CourseUtils.tsx | 26 ++++++++++++------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/Courses/CourseDetailTeacher.tsx b/frontend/src/components/Courses/CourseDetailTeacher.tsx index c9f0d278..60564260 100644 --- a/frontend/src/components/Courses/CourseDetailTeacher.tsx +++ b/frontend/src/components/Courses/CourseDetailTeacher.tsx @@ -200,7 +200,7 @@ function EmptyOrNotProjects({projects}: {projects: ProjectDetail[]}): JSX.Elemen {getNearestFutureDate(project.deadlines) && ( - {`${t('deadline')}: ${getNearestFutureDate(project.deadlines)?.[1].toLocaleDateString()}`} + {`${t('deadline')}: ${getNearestFutureDate(project.deadlines)?.date.toLocaleDateString()}`} )} diff --git a/frontend/src/components/Courses/CourseUtilComponents.tsx b/frontend/src/components/Courses/CourseUtilComponents.tsx index 15aab70b..58d01874 100644 --- a/frontend/src/components/Courses/CourseUtilComponents.tsx +++ b/frontend/src/components/Courses/CourseUtilComponents.tsx @@ -175,10 +175,10 @@ export function SideScrollableCourses({ const project: ProjectDetail = { ...item, deadlines: projectData.deadlines.map( - ([description, dateString]: [string, string]) => [ + ([description, dateString]: [string, string]) => ({ description, - new Date(dateString), - ] + date: new Date(dateString), + }) ), }; return project; @@ -330,15 +330,14 @@ function EmptyOrNotProjects({ let timeLeft = ""; if (project.deadlines != undefined) { const deadline = getNearestFutureDate(project.deadlines); - if (deadline == null) { - return <>; - } - const deadlineDate = deadline[1]; - const diffTime = Math.abs(deadlineDate.getTime() - now.getTime()); - const diffHours = Math.ceil(diffTime / (1000 * 60 * 60)); - const diffDays = Math.ceil(diffHours * 24); + if(deadline !== null){ + const deadlineDate = deadline.date; + const diffTime = Math.abs(deadlineDate.getTime() - now.getTime()); + const diffHours = Math.ceil(diffTime / (1000 * 60 * 60)); + const diffDays = Math.ceil(diffHours * 24); - timeLeft = diffDays > 1 ? `${diffDays} days` : `${diffHours} hours`; + timeLeft = diffDays > 1 ? `${diffDays} days` : `${diffHours} hours`; + } } return ( diff --git a/frontend/src/components/Courses/CourseUtils.tsx b/frontend/src/components/Courses/CourseUtils.tsx index 4fbb28be..92c55aab 100644 --- a/frontend/src/components/Courses/CourseUtils.tsx +++ b/frontend/src/components/Courses/CourseUtils.tsx @@ -17,7 +17,12 @@ export interface Project { export interface ProjectDetail { title: string; project_id: string; - deadlines: [string, Date][]; + deadlines: Deadline[]; +} + +interface Deadline { + description: string; + date: Date; } export const apiHost = import.meta.env.VITE_APP_API_HOST; @@ -85,15 +90,15 @@ export function getIdFromLink(link: string): string { } /** - * Function to find the nearest future date from a list of dates - * @param dates - Array of dates - * @returns The nearest future date + * Function to find the nearest future deadline from a list of deadlines + * @param deadlines - List of deadlines + * @returns The nearest future deadline */ -export function getNearestFutureDate(dates: [string, Date][]): [string, Date] | null { +export function getNearestFutureDate(deadlines: Deadline[]): Deadline | null { const now = new Date(); - const futureDates = dates.filter((date) => date[1] > now); - if (futureDates.length === 0) return null; - return futureDates.reduce((nearest, current) => + const futureDeadlines = deadlines.filter((deadline) => deadline.date > now); + if (futureDeadlines.length === 0) return null; + return futureDeadlines.reduce((nearest, current) => current < nearest ? current : nearest ); } @@ -144,7 +149,10 @@ const dataLoaderProjects = async (courseId: string) => { const projectData = projectJson.data; const project: ProjectDetail = { ...item, - deadlines: projectData.deadlines.map(([description, dateString]: [string, string]) => [description, new Date(dateString)]) + deadlines: projectData.deadlines.map((deadline: Deadline) => ({ + description: deadline.description, + date: new Date(deadline.date), + })), }; return project; });