Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

Courses now converts deadlines from string to Date right after fetch #295

Merged
merged 5 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions frontend/src/components/Courses/CourseDetailTeacher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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';
Expand Down Expand Up @@ -101,12 +101,13 @@ export function CourseDetailTeacher(): JSX.Element {
setAnchorElStudent(null);
};

const courseDetail = useLoaderData() as { //TODO CATCH ERROR
const courseDetail = useLoaderData() as {
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();
Expand Down Expand Up @@ -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 (
Expand All @@ -199,7 +200,7 @@ function EmptyOrNotProjects({projects}: {projects: Project[]}): JSX.Element {
{getNearestFutureDate(project.deadlines) &&
(
<Typography variant="body1">
{`${t('deadline')}: ${getNearestFutureDate(project.deadlines)?.toLocaleDateString()}`}
{`${t('deadline')}: ${getNearestFutureDate(project.deadlines)?.date.toLocaleDateString()}`}
</Typography>
)}
</CardContent>
Expand Down
50 changes: 37 additions & 13 deletions frontend/src/components/Courses/CourseUtilComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import {
Course,
Project,
ProjectDetail,
apiHost,
getIdFromLink,
getNearestFutureDate,
Expand Down Expand Up @@ -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[] }>(
{}
);

Expand Down Expand Up @@ -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,
date: new Date(dateString),
})
),
};
return project;
});
Promise.all(detailProjectPromises).then((projects) => {
projectsMap[getIdFromLink(courses[index].course_id)] = projects;
setProjects({ ...projectsMap });
});
});

setProjects(projectsMap);
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -286,7 +310,7 @@ function EmptyOrNotProjects({
projects,
noProjectsText,
}: {
projects: Project[];
projects: ProjectDetail[];
noProjectsText: string;
}): JSX.Element {
if (projects === undefined || projects.length === 0) {
Expand All @@ -305,15 +329,15 @@ function EmptyOrNotProjects({
{projects.slice(0, 3).map((project) => {
let timeLeft = "";
if (project.deadlines != undefined) {
const deadlineDate = getNearestFutureDate(project.deadlines);
if (deadlineDate == null) {
return <></>;
}
const diffTime = Math.abs(deadlineDate.getTime() - now.getTime());
const diffHours = Math.ceil(diffTime / (1000 * 60 * 60));
const diffDays = Math.ceil(diffHours * 24);
const deadline = getNearestFutureDate(project.deadlines);
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 (
<Grid item key={project.project_id}>
Expand Down
56 changes: 44 additions & 12 deletions frontend/src/components/Courses/CourseUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@ export interface Course {
export interface Project {
title: string;
project_id: string;
deadlines: string[][];
}

export interface ProjectDetail {
title: string;
project_id: string;
deadlines: Deadline[];
}

interface Deadline {
description: string;
date: Date;
}

export const apiHost = import.meta.env.VITE_APP_API_HOST;
Expand Down Expand Up @@ -80,17 +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 | null {
export function getNearestFutureDate(deadlines: Deadline[]): Deadline | null {
const now = new Date();
const futureDates = dates
.map((date) => new Date(date[1]))
.filter((date) => date > 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
);
}
Expand Down Expand Up @@ -125,7 +133,31 @@ 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((deadline: Deadline) => ({
description: deadline.description,
date: new Date(deadline.date),
})),
};
return project;
});

return Promise.all(projects);
};

const dataLoaderAdmins = async (courseId: string) => {
Expand All @@ -145,10 +177,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 };
};