From 63da8af70a79270764cfeb3e6684f66e7eefae4d Mon Sep 17 00:00:00 2001 From: Thibaud Collyn Date: Thu, 23 May 2024 20:11:46 +0200 Subject: [PATCH 1/7] Added code documentation, code cleanup and a couple of bugfixes --- .../app/[locale]/components/AccountMenu.tsx | 22 ++- .../app/[locale]/components/AddButton.tsx | 6 +- .../[locale]/components/AddProjectButton.tsx | 6 +- .../app/[locale]/components/BackButton.tsx | 6 +- .../app/[locale]/components/CASButton.tsx | 4 +- .../components/CopyToClipboardButton.tsx | 4 + .../app/[locale]/components/CourseBanner.tsx | 10 +- .../app/[locale]/components/CourseCard.tsx | 6 +- .../[locale]/components/CourseControls.tsx | 11 +- .../app/[locale]/components/CourseDetails.tsx | 4 + .../app/[locale]/components/CoursesGrid.tsx | 11 +- .../[locale]/components/CreateCourseForm.tsx | 8 ++ .../[locale]/components/EditCourseButton.tsx | 4 + .../[locale]/components/EditCourseForm.tsx | 9 +- .../app/[locale]/components/EditUserForm.tsx | 13 +- frontend/app/[locale]/components/Footer.tsx | 74 +++++----- .../components/GroupSubmissionList.tsx | 14 +- .../app/[locale]/components/HomeButton.tsx | 3 + .../components/JoinCourseWithToken.tsx | 7 +- .../[locale]/components/LanguageSelect.tsx | 6 + frontend/app/[locale]/components/ListView.tsx | 11 +- .../app/[locale]/components/LoginCard.tsx | 3 + frontend/app/[locale]/components/NavBar.tsx | 9 +- .../app/[locale]/components/ProfileCard.tsx | 4 + .../[locale]/components/ProfileEditCard.tsx | 3 + .../[locale]/components/ProjectCalendar.tsx | 14 +- .../components/ProjectDetailsPage.tsx | 14 +- .../components/ProjectReturnButton.tsx | 5 + .../components/ProjectSubmissionsList.tsx | 14 +- .../app/[locale]/components/StatusButton.tsx | 12 +- .../components/StudentCoTeacherButtons.tsx | 7 +- .../components/SubmissionDetailsPage.tsx | 6 + .../[locale]/components/SubmitDetailsPage.tsx | 12 +- .../components/YearStateComponent.tsx | 4 + .../components/admin_components/UserList.tsx | 4 + .../course_components/ArchiveButton.tsx | 4 + .../course_components/CancelButton.tsx | 34 ----- .../course_components/DeleteButton.tsx | 130 +++++++++--------- .../[locale]/components/general/ItemsList.tsx | 8 ++ .../components/general/RequiredFilesList.tsx | 12 ++ .../project_components/testfiles.tsx | 8 ++ .../components/project_components/title.tsx | 9 ++ .../project_components/uploadButton.tsx | 13 +- .../user_components/CancelButton.tsx | 3 + .../user_components/DeleteButton.tsx | 6 +- 45 files changed, 369 insertions(+), 208 deletions(-) delete mode 100644 frontend/app/[locale]/components/course_components/CancelButton.tsx diff --git a/frontend/app/[locale]/components/AccountMenu.tsx b/frontend/app/[locale]/components/AccountMenu.tsx index a9efdd43..e58ab51f 100644 --- a/frontend/app/[locale]/components/AccountMenu.tsx +++ b/frontend/app/[locale]/components/AccountMenu.tsx @@ -20,13 +20,16 @@ import {useEffect, useState} from "react"; const backend_url = process.env['NEXT_PUBLIC_BACKEND_URL']; export default function AccountMenu() { + /* + * Account menu component, used to display the user's name and a dropdown menu with options to go to the profile page and logout(right side of the navbar) + * */ const [user, setUser] = useState(null); const [error, setError] = useState(null); + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); const { t } = useTranslation() useEffect(() => { - - const fetchCourses = async () => { try{ setUser(await getUserData()); @@ -39,26 +42,23 @@ export default function AccountMenu() { fetchCourses(); }, []); - - - const [anchorEl, setAnchorEl] = React.useState(null); - const open = Boolean(anchorEl); + //Utility & navigation functions const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { - //TODO: Handle settings and My profile actions!! setAnchorEl(null); }; const toProfile = () => { window.location.href = '/profile' - } + }; const handleLogout = () => { setAnchorEl(null); window.location.href = backend_url + "/auth/logout"; }; + return ( @@ -108,12 +108,6 @@ export default function AccountMenu() { {t('my_profile')} - - - - - {t('settings')} - diff --git a/frontend/app/[locale]/components/AddButton.tsx b/frontend/app/[locale]/components/AddButton.tsx index ca45fe66..2f8485f3 100644 --- a/frontend/app/[locale]/components/AddButton.tsx +++ b/frontend/app/[locale]/components/AddButton.tsx @@ -3,8 +3,12 @@ import { Button } from '@mui/material'; import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; import {useTranslation} from "react-i18next"; -//TODO: route to add project page function AddButton({translationkey, href} : {translationkey: string, href : string|undefined}){ + /* + * General add button component + * @param translationkey: The key of the translation in the i18n file + * @param href: The href of the button + * */ const { t } = useTranslation() return( diff --git a/frontend/app/[locale]/components/AddProjectButton.tsx b/frontend/app/[locale]/components/AddProjectButton.tsx index 0b17a986..3bdc91a0 100644 --- a/frontend/app/[locale]/components/AddProjectButton.tsx +++ b/frontend/app/[locale]/components/AddProjectButton.tsx @@ -9,6 +9,10 @@ interface EditCourseButtonProps{ } const AddProjectButton = ({course_id}: EditCourseButtonProps) => { + /* + * Specific add project button component + * @param course_id: The id of the course to which the project will be added + * */ const {t} = useTranslation(); const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); @@ -38,7 +42,7 @@ const AddProjectButton = ({course_id}: EditCourseButtonProps) => { }} /> : <> - {user?.role !== 3 && ( + {user?.role !== 3 && ( // If the user is not a student - - - ) -} - -export default CancelButton \ No newline at end of file diff --git a/frontend/app/[locale]/components/course_components/DeleteButton.tsx b/frontend/app/[locale]/components/course_components/DeleteButton.tsx index 67094e37..e3d1ca27 100644 --- a/frontend/app/[locale]/components/course_components/DeleteButton.tsx +++ b/frontend/app/[locale]/components/course_components/DeleteButton.tsx @@ -1,63 +1,67 @@ -'use client'; - -import React, { useState } from 'react'; -import { useTranslation } from "react-i18next"; -import { deleteCourse } from "@lib/api"; -import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Typography } from '@mui/material'; - -interface DeleteButtonProps { - courseId: number -} - -const DeleteButton = ({ courseId }: DeleteButtonProps) => { - const { t } = useTranslation(); - const [open, setOpen] = useState(false); - - const handleOpen = () => { - setOpen(true); - }; - - const handleClose = () => { - setOpen(false); - }; - - const handleDelete = async () => { - await deleteCourse(courseId); - window.location.href = "/home"; - }; - - return ( - <> - - - {t("Are you sure you want to delete this course?")} - - - - - - - ); -}; - -export default DeleteButton; +'use client'; + +import React, { useState } from 'react'; +import { useTranslation } from "react-i18next"; +import { deleteCourse } from "@lib/api"; +import { Button, Dialog, DialogActions, DialogTitle } from '@mui/material'; + +interface DeleteButtonProps { + courseId: number +} + +const DeleteButton = ({ courseId }: DeleteButtonProps) => { + /* + * This component displays the delete button for a course. + * @param courseId: The id of the course + */ + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + + const handleOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + const handleDelete = async () => { + await deleteCourse(courseId); + window.location.href = "/home"; + }; + + return ( + <> + + + {t("Are you sure you want to delete this course?")} + + + + + + + ); +}; + +export default DeleteButton; diff --git a/frontend/app/[locale]/components/general/ItemsList.tsx b/frontend/app/[locale]/components/general/ItemsList.tsx index e2564ed1..a353e112 100644 --- a/frontend/app/[locale]/components/general/ItemsList.tsx +++ b/frontend/app/[locale]/components/general/ItemsList.tsx @@ -14,6 +14,14 @@ interface ItemsListProps { } const ItemsList = ({items, setItems, input_placeholder, empty_list_placeholder, button_text}: ItemsListProps) => { + /* + * This component displays a list of items and allows the user to add and delete items. + * @param items: The list of items + * @param setItems: The function to set the list of items + * @param input_placeholder: The placeholder for the input field + * @param empty_list_placeholder: The placeholder for the list when it is empty + * @param button_text: The text for the button + */ const [newItem, setNewItem] = useState('') const [noInput, setNoInput] = useState(false) diff --git a/frontend/app/[locale]/components/general/RequiredFilesList.tsx b/frontend/app/[locale]/components/general/RequiredFilesList.tsx index 5cd34792..983b09b8 100644 --- a/frontend/app/[locale]/components/general/RequiredFilesList.tsx +++ b/frontend/app/[locale]/components/general/RequiredFilesList.tsx @@ -26,6 +26,18 @@ const ItemsList = ({ items_status, setItemsStatus }: ItemsListProps) => { + /* + * This component is a variation of the ItemsList that works specifically for the required files of a project. + * It displays a list of files and allows the user to add and delete files. + * It also allows the user to set the status of the files. + * @param items: The list of files + * @param setItems: The function to set the list of files + * @param input_placeholder: The placeholder for the input field + * @param empty_list_placeholder: The placeholder for the list when it is empty + * @param button_text: The text for the button + * @param items_status: The list of statuses for the files + * @param setItemsStatus: The function to set the list of statuses for the files + */ const [newItem, setNewItem] = useState('') const [noInput, setNoInput] = useState(false) const {t} = useTranslation(); diff --git a/frontend/app/[locale]/components/project_components/testfiles.tsx b/frontend/app/[locale]/components/project_components/testfiles.tsx index c3bf135e..a98ff9b8 100644 --- a/frontend/app/[locale]/components/project_components/testfiles.tsx +++ b/frontend/app/[locale]/components/project_components/testfiles.tsx @@ -14,6 +14,14 @@ interface TestFilesProps { } function TestFiles({testfilesName, setTestfilesName, testfilesData, setTestfilesData}: TestFilesProps) { + /* + * This component displays the list of test files for a project. + * It allows the user to delete test files. + * @param testfilesName: The list of names of the test files + * @param setTestfilesName: The function to set the list of names of the test files + * @param testfilesData: The list of data of the test files + * @param setTestfilesData: The function to set the list of data of the test files + */ const {t} = useTranslation(); return
diff --git a/frontend/app/[locale]/components/project_components/title.tsx b/frontend/app/[locale]/components/project_components/title.tsx index 2c4f24fe..2862f183 100644 --- a/frontend/app/[locale]/components/project_components/title.tsx +++ b/frontend/app/[locale]/components/project_components/title.tsx @@ -13,6 +13,15 @@ interface TitleProps { } function Title({isTitleEmpty, setTitle, title, score, isScoreEmpty, setScore}: TitleProps) { + /* + * This component displays the title and the maximum score of a project. + * @param isTitleEmpty: A boolean that indicates if the title is empty + * @param setTitle: The function to set the title + * @param title: The title of the project + * @param score: The maximum score of the project + * @param isScoreEmpty: A boolean that indicates if the score is empty + * @param setScore: The function to set the score + */ const {t} = useTranslation(); const handleScoreChange = (event: any) => { diff --git a/frontend/app/[locale]/components/project_components/uploadButton.tsx b/frontend/app/[locale]/components/project_components/uploadButton.tsx index b4644d0b..71258d7b 100644 --- a/frontend/app/[locale]/components/project_components/uploadButton.tsx +++ b/frontend/app/[locale]/components/project_components/uploadButton.tsx @@ -11,14 +11,19 @@ interface UploadTestFileProps { setTestfilesData: (value: (((prevState: JSZipObject[]) => JSZipObject[]) | JSZipObject[])) => void, } -function UploadTestFile( - { +function UploadTestFile({ testfilesName, setTestfilesName, testfilesData, setTestfilesData, - }: UploadTestFileProps -) { + }: UploadTestFileProps) { + /* + * This component allows the user to upload test files for a project. + * @param testfilesName: The list of names of the test files + * @param setTestfilesName: The function to set the list of names of the test files + * @param testfilesData: The list of data of the test files + * @param setTestfilesData: The function to set the list of data of the test files + */ const {t} = useTranslation(); const handleFileChange = async (event: any) => { let zip = new JSZip(); diff --git a/frontend/app/[locale]/components/user_components/CancelButton.tsx b/frontend/app/[locale]/components/user_components/CancelButton.tsx index b9817030..acb17c78 100644 --- a/frontend/app/[locale]/components/user_components/CancelButton.tsx +++ b/frontend/app/[locale]/components/user_components/CancelButton.tsx @@ -4,6 +4,9 @@ import React from 'react' import {useTranslation} from "react-i18next"; const CancelButton = () => { + /* + * This component displays the cancel button. + */ const {t} = useTranslation() const handleCancel = () => { diff --git a/frontend/app/[locale]/components/user_components/DeleteButton.tsx b/frontend/app/[locale]/components/user_components/DeleteButton.tsx index 390c9f59..03eceb3b 100644 --- a/frontend/app/[locale]/components/user_components/DeleteButton.tsx +++ b/frontend/app/[locale]/components/user_components/DeleteButton.tsx @@ -3,13 +3,17 @@ import React, { useState } from 'react'; import { useTranslation } from "react-i18next"; import { deleteUser } from "@lib/api"; -import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Typography } from '@mui/material'; +import { Button, Dialog, DialogActions, DialogTitle, Typography } from '@mui/material'; interface DeleteButtonProps { userId: number } const DeleteButton = ({ userId }: DeleteButtonProps) => { + /* + * This component displays the delete button for a user. + * @param userId: The id of the user + */ const { t } = useTranslation(); const [open, setOpen] = useState(false); From 41864d31aad93034a6ac2b83dc28a79b80901d76 Mon Sep 17 00:00:00 2001 From: Axel Lorreyne Date: Thu, 23 May 2024 20:15:46 +0200 Subject: [PATCH 2/7] fix submission download --- backend/pigeonhole/apps/projects/views.py | 27 ++++++------- backend/pigeonhole/apps/submissions/views.py | 40 +++++++------------- 2 files changed, 25 insertions(+), 42 deletions(-) diff --git a/backend/pigeonhole/apps/projects/views.py b/backend/pigeonhole/apps/projects/views.py index 8f606cab..c411cfc9 100644 --- a/backend/pigeonhole/apps/projects/views.py +++ b/backend/pigeonhole/apps/projects/views.py @@ -1,4 +1,3 @@ -import zipfile from os.path import basename, realpath from django.db import transaction @@ -16,7 +15,7 @@ from backend.pigeonhole.apps.groups.models import GroupSerializer from backend.pigeonhole.apps.submissions.models import ( Submissions, - SubmissionsSerializer, + SubmissionsSerializer, submission_folder_path, ) from backend.pigeonhole.apps.users.models import User from backend.pigeonhole.filters import ( @@ -26,6 +25,7 @@ ) from .models import Project, ProjectSerializer from .permissions import CanAccessProject +from ..submissions.views import ZipUtilities class CsrfExemptSessionAuthentication(SessionAuthentication): @@ -253,22 +253,19 @@ def download_submissions(self, request, *args, **kwargs): if len(submissions) == 0: return Response(status=status.HTTP_400_BAD_REQUEST) - path = "" + path = 'backend/downloads/submissions.zip' + submission_folders = [] - if len(submissions) == 1: - path = submissions[0].file.path - - else: - path = "backend/downloads/submissions.zip" - zipf = zipfile.ZipFile(file=path, mode="w", compression=zipfile.ZIP_STORED) - - for submission in submissions: - zipf.write( - filename=submission.file.path, - arcname=basename(submission.file.path), + for submission in submissions: + submission_folders.append( + submission_folder_path( + submission.group_id.group_id, submission.submission_id ) + ) - zipf.close() + utilities = ZipUtilities() + filename = path + utilities.toZip(submission_folders, filename) path = realpath(path) response = FileResponse( diff --git a/backend/pigeonhole/apps/submissions/views.py b/backend/pigeonhole/apps/submissions/views.py index a4c23f60..7710bc5c 100644 --- a/backend/pigeonhole/apps/submissions/views.py +++ b/backend/pigeonhole/apps/submissions/views.py @@ -206,40 +206,26 @@ def download_selection(self, request, *args, **kwargs): if not ids: return Response(status=status.HTTP_400_BAD_REQUEST) - path = "" + path = 'backend/downloads/submissions.zip' + submission_folders = [] - if len(ids) == 1: - submission = Submissions.objects.get(submission_id=ids[0]) + for sid in ids: + submission = Submissions.objects.get(submission_id=sid) if submission is None: return Response( - {"message": f"Submission with id {ids[0]} not found", + {"message": f"Submission with id {id} not found", "errorcode": "ERROR_SUBMISSION_NOT_FOUND"}, - status=status.HTTP_404_NOT_FOUND, + status=status.HTTP_404_NOT_FOUND ) - - path = submission.file.path - - else: - path = 'backend/downloads/submissions.zip' - submission_folders = [] - - for sid in ids: - submission = Submissions.objects.get(submission_id=sid) - if submission is None: - return Response( - {"message": f"Submission with id {id} not found", - "errorcode": "ERROR_SUBMISSION_NOT_FOUND"}, - status=status.HTTP_404_NOT_FOUND - ) - submission_folders.append( - submission_folder_path( - submission.group_id.group_id, submission.submission_id - ) + submission_folders.append( + submission_folder_path( + submission.group_id.group_id, submission.submission_id ) + ) - utilities = ZipUtilities() - filename = path - utilities.toZip(submission_folders, filename) + utilities = ZipUtilities() + filename = path + utilities.toZip(submission_folders, filename) path = realpath(path) response = FileResponse( From bdb5aea3500cef7facb212c4d1146955717529fd Mon Sep 17 00:00:00 2001 From: rdyselinck Date: Thu, 23 May 2024 20:23:11 +0200 Subject: [PATCH 3/7] Set image as null when creating course with default image --- frontend/app/[locale]/components/CreateCourseForm.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/app/[locale]/components/CreateCourseForm.tsx b/frontend/app/[locale]/components/CreateCourseForm.tsx index b777c0ff..6de04e4c 100644 --- a/frontend/app/[locale]/components/CreateCourseForm.tsx +++ b/frontend/app/[locale]/components/CreateCourseForm.tsx @@ -59,7 +59,13 @@ const CreateCourseForm = () => { }); } } - if (selectedImage) fileReader.readAsArrayBuffer(selectedImage); + if (selectedImage) { + fileReader.readAsArrayBuffer(selectedImage); + } else { + await postData("/courses/", formData).then((response) => { + window.location.href = `/course/${response.course_id}`; + }); + } }; useEffect(() => { From 5723394a4b27ee95a018db5c2b26228b07f3163a Mon Sep 17 00:00:00 2001 From: Reinhard Date: Thu, 23 May 2024 20:30:50 +0200 Subject: [PATCH 4/7] tests fixed --- frontend/__test__/AccountMenu.test.tsx | 1 - .../__test__/course_components/CancelButton.test.tsx | 10 ---------- 2 files changed, 11 deletions(-) delete mode 100644 frontend/__test__/course_components/CancelButton.test.tsx diff --git a/frontend/__test__/AccountMenu.test.tsx b/frontend/__test__/AccountMenu.test.tsx index 104a73f6..08c3bf65 100644 --- a/frontend/__test__/AccountMenu.test.tsx +++ b/frontend/__test__/AccountMenu.test.tsx @@ -43,7 +43,6 @@ describe('AccountMenu', () => { fireEvent.click(screen.getByRole('button')); const menu = screen.getByRole('menu'); expect(menu).toBeVisible(); - fireEvent.click(screen.getByRole('menuitem', {name: 'settings'})); expect(menu).not.toBeVisible(); }); diff --git a/frontend/__test__/course_components/CancelButton.test.tsx b/frontend/__test__/course_components/CancelButton.test.tsx deleted file mode 100644 index e134106e..00000000 --- a/frontend/__test__/course_components/CancelButton.test.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import {render, screen} from "@testing-library/react"; -import React from "react"; -import CancelButton from "@app/[locale]/components/course_components/CancelButton"; - -describe("CancelButton", () => { - it("renders cancel button and click", async () => { - render(); - screen.getByText(/cancel/i).click(); - }); -}); From 8217e61b7cf46ac30d4cfd1efa13927ab96f2ae1 Mon Sep 17 00:00:00 2001 From: Reinhard Date: Thu, 23 May 2024 20:33:19 +0200 Subject: [PATCH 5/7] tests fix 2 --- frontend/__test__/AccountMenu.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/__test__/AccountMenu.test.tsx b/frontend/__test__/AccountMenu.test.tsx index 08c3bf65..7e605243 100644 --- a/frontend/__test__/AccountMenu.test.tsx +++ b/frontend/__test__/AccountMenu.test.tsx @@ -43,7 +43,6 @@ describe('AccountMenu', () => { fireEvent.click(screen.getByRole('button')); const menu = screen.getByRole('menu'); expect(menu).toBeVisible(); - expect(menu).not.toBeVisible(); }); From da0710fa89272b591e69182ea6c08831dc18d4df Mon Sep 17 00:00:00 2001 From: rdyselinck Date: Thu, 23 May 2024 20:38:38 +0200 Subject: [PATCH 6/7] Check if new image is uploaded --- frontend/app/[locale]/components/CreateCourseForm.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/app/[locale]/components/CreateCourseForm.tsx b/frontend/app/[locale]/components/CreateCourseForm.tsx index 6de04e4c..c62502e0 100644 --- a/frontend/app/[locale]/components/CreateCourseForm.tsx +++ b/frontend/app/[locale]/components/CreateCourseForm.tsx @@ -18,6 +18,7 @@ import dayjs from "dayjs"; const CreateCourseForm = () => { const { t } = useTranslation(); const [selectedImage, setSelectedImage] = useState(null); + const [newImage, setNewImage] = useState(false); const [selectedImageURL, setSelectedImageURL] = useState(""); const [name, setName] = useState(''); const [description, setDescription] = useState(''); @@ -31,6 +32,8 @@ const CreateCourseForm = () => { const imageURL = URL.createObjectURL(imageFile); setSelectedImageURL(imageURL); + + setNewImage(newImage); }; const handleSubmit = async (event: any) => { @@ -59,7 +62,7 @@ const CreateCourseForm = () => { }); } } - if (selectedImage) { + if (selectedImage && newImage) { fileReader.readAsArrayBuffer(selectedImage); } else { await postData("/courses/", formData).then((response) => { From d54bba15dc15069baec3d7adfd91e4ccb7d9b0a8 Mon Sep 17 00:00:00 2001 From: rdyselinck Date: Thu, 23 May 2024 20:54:35 +0200 Subject: [PATCH 7/7] Editing project now fills in in projectEditForm.tsx --- .../app/[locale]/project/[project_id]/edit/projectEditForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/[locale]/project/[project_id]/edit/projectEditForm.tsx b/frontend/app/[locale]/project/[project_id]/edit/projectEditForm.tsx index a7a9ee17..ef3100f9 100644 --- a/frontend/app/[locale]/project/[project_id]/edit/projectEditForm.tsx +++ b/frontend/app/[locale]/project/[project_id]/edit/projectEditForm.tsx @@ -89,13 +89,13 @@ function ProjectEditForm({project_id, add_course_id}: ProjectEditFormProps) { if (project.deadline !== null) setDeadline(dayjs(project["deadline"])); setDescription(project.description) if (project.file_structure !== null && project.file_structure !== "") { - console.log(project.file_structure) const file_structure = project.file_structure.split(",").map((item: string) => item.trim().replace(/"/g, '')); const file_structure_status = file_structure.map((item: string) => item[0]); const file_structure_name = file_structure.map((item: string) => item.substring(1)); setFiles(file_structure_name); setStatusFiles(file_structure_status); } + setDockerImage(project["test_docker_image"]) setGroupSize(project["group_size"]) setTitle(project["name"]) setGroupAmount(project["number_of_groups"])