From 23f2206a374d45a70f5be7cdde94cd1899800681 Mon Sep 17 00:00:00 2001 From: gerwoud Date: Mon, 20 May 2024 17:30:50 +0200 Subject: [PATCH 1/5] changes bekijken --- frontend/public/locales/en/translation.json | 11 +-- frontend/public/locales/nl/translation.json | 3 +- frontend/src/App.tsx | 2 + .../projectAdminView/projectAdminView.tsx | 89 +++++++++++++++++++ .../projectAdminViewDeadlineDatagrid.tsx | 14 +++ .../pages/project/projectView/ProjectView.tsx | 30 +++++-- 6 files changed, 138 insertions(+), 11 deletions(-) create mode 100644 frontend/src/pages/project/projectAdminView/projectAdminView.tsx create mode 100644 frontend/src/pages/project/projectAdminView/projectAdminViewDeadlineDatagrid.tsx diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 86ed53b3..929cdd1c 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -52,7 +52,7 @@ "cancel": "Cancel", "create": "Create", "activeCourses": "Active Courses", - "archivedCourses":"Archived Courses" + "archivedCourses": "Archived Courses" }, "courseForm": { "courseName": "Course Name", @@ -72,7 +72,8 @@ "running": "Running", "submitTime": "Time submitted", "status": "Status" - } + }, + "projectOverview": "Overview" }, "time": { "yearsAgo": "years ago", @@ -127,11 +128,11 @@ "unauthorized": "You are unauthorized to upload a project for this course", "submissionError": "Submission failed, please try again" }, - "student" : { + "student": { "myProjects": "My Projects", "myCourses": "My Courses", "deadlines": "Past deadlines", - "last_submission" : "Last submission", + "last_submission": "Last submission", "course": "Course", "SUCCESS": "Success", "FAIL": "Fail", @@ -139,7 +140,7 @@ "LATE": "Late", "deadlinesOnDay": "Deadlines on: ", "noDeadline": "No deadlines on this day", - "no_submission_yet" : "No submission yet", + "no_submission_yet": "No submission yet", "loading": "Loading...", "no_projects": "There are no projects here, sign up for a course to see projects" }, diff --git a/frontend/public/locales/nl/translation.json b/frontend/public/locales/nl/translation.json index 0b693055..6aed358c 100644 --- a/frontend/public/locales/nl/translation.json +++ b/frontend/public/locales/nl/translation.json @@ -109,7 +109,8 @@ "running": "Aan het uitvoeren", "submitTime": "Indientijd", "status": "Status" - } + }, + "projectOverview": "Overzicht" }, "time": { "yearsAgo": "jaren geleden", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c3a967a0..86cf9a0e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -23,6 +23,7 @@ import { synchronizeJoinCode } from "./loaders/join-code.ts"; import { fetchMe } from "./utils/fetches/FetchMe.ts"; import {fetchProjectForm} from "./components/ProjectForm/project-form.ts"; import loadSubmissionOverview from "./loaders/submission-overview-loader.ts"; +import ProjectAdminView from "./pages/project/projectAdminView/projectAdminView.tsx"; const router = createBrowserRouter( createRoutesFromElements( @@ -51,6 +52,7 @@ const router = createBrowserRouter( path=":projectId/overview" element={} /> + } path=":projectId/admin"> }> } loader={fetchProjectForm}/> diff --git a/frontend/src/pages/project/projectAdminView/projectAdminView.tsx b/frontend/src/pages/project/projectAdminView/projectAdminView.tsx new file mode 100644 index 00000000..cf3c39db --- /dev/null +++ b/frontend/src/pages/project/projectAdminView/projectAdminView.tsx @@ -0,0 +1,89 @@ +import { + Card, + CardContent, + CardHeader, + Container, + Grid, + Link, + Stack, + Typography, +} from "@mui/material"; +import { useEffect, useState } from "react"; +import Markdown from "react-markdown"; +import { useParams } from "react-router-dom"; +import SubmissionCard from "./SubmissionCard"; +import { Course } from "../../../types/course"; +import { Title } from "../../../components/Header/Title"; +import { authenticatedFetch } from "../../../utils/authenticated-fetch"; +import i18next from "i18next"; +import {useTranslation} from "react-i18next"; +import {Deadline} from "../../../types/deadline.ts"; +import ProjectAdminViewDeadlineDatagrid from "./projectAdminViewDeadlineDatagrid.tsx"; + +const API_URL = import.meta.env.VITE_APP_API_HOST; + +interface Project { + title: string; + description: string; + regex_expressions: string[]; +} + +/** + * + * @returns - ProjectAdminView component which displays the project details + * and submissions of the current admin for that project + */ +export default function ProjectAdminView() { + + const { t } = useTranslation('projectAdminView', { keyPrefix: 'projectsAdminView' }); + + const { projectId } = useParams<{ projectId: string }>(); + const [projectData, setProjectData] = useState(null); + const [deadlines, setDeadlines] = useState([]) + + useEffect(() => { + authenticatedFetch(`${API_URL}/projects/${projectId}`).then((response) => { + if (response.ok) { + response.json().then((data) => { + const project = data["data"] + setProjectData(project); + setDeadlines(project.deadlines) + }) + } + }) + }, []); + + return ( + + + { + projectData && ( + + + <CardHeader + color="secondary" + title={t("projectInfo")} + ></CardHeader> + <CardContent> + <Typography variant="h6">{projectData.title}</Typography> + <Typography>{projectData.description}</Typography> + </CardContent> + <CardContent> + <Typography></Typography> + </CardContent> + <CardContent> + <ProjectAdminViewDeadlineDatagrid deadlines={deadlines}></ProjectAdminViewDeadlineDatagrid> + </CardContent> + </Card> + ) + } + </Grid> + </Grid> + ); +} diff --git a/frontend/src/pages/project/projectAdminView/projectAdminViewDeadlineDatagrid.tsx b/frontend/src/pages/project/projectAdminView/projectAdminViewDeadlineDatagrid.tsx new file mode 100644 index 00000000..ff969d4f --- /dev/null +++ b/frontend/src/pages/project/projectAdminView/projectAdminViewDeadlineDatagrid.tsx @@ -0,0 +1,14 @@ +import {Deadline} from "../../../types/deadline.ts"; + +/** + * + */ +export default function ProjectAdminViewDeadlineDatagrid({ deadlines }) { + return ( + <div> + {deadlines.map((deadline: Deadline, index) => ( + <p key={index}>{deadline}</p> + ))} + </div> + ); +} diff --git a/frontend/src/pages/project/projectView/ProjectView.tsx b/frontend/src/pages/project/projectView/ProjectView.tsx index 209279de..1b30878a 100644 --- a/frontend/src/pages/project/projectView/ProjectView.tsx +++ b/frontend/src/pages/project/projectView/ProjectView.tsx @@ -1,21 +1,23 @@ import { + Box, + Button, Card, CardContent, CardHeader, Container, Grid, - Link, Stack, Typography, } from "@mui/material"; import { useEffect, useState } from "react"; import Markdown from "react-markdown"; -import { useParams } from "react-router-dom"; +import {useLocation, useParams} from "react-router-dom"; import SubmissionCard from "./SubmissionCard"; import { Course } from "../../../types/course"; import { Title } from "../../../components/Header/Title"; import { authenticatedFetch } from "../../../utils/authenticated-fetch"; import i18next from "i18next"; +import {useTranslation} from "react-i18next"; const API_URL = import.meta.env.VITE_APP_API_HOST; @@ -31,6 +33,11 @@ interface Project { * and submissions of the current user for that project */ export default function ProjectView() { + + const location = useLocation(); + + const { t } = useTranslation('translation', { keyPrefix: 'projectView' }); + const { projectId } = useParams<{ projectId: string }>(); const [projectData, setProjectData] = useState<Project | null>(null); const [courseData, setCourseData] = useState<Course | null>(null); @@ -88,9 +95,9 @@ export default function ProjectView() { <Typography>{projectData.description}</Typography> <Typography flex="1" /> {courseData && ( - <Link href={`/${i18next.resolvedLanguage}/courses/${courseData.course_id}`}> - <Typography>{courseData.name}</Typography> - </Link> + <Button variant="contained" href={`/${i18next.resolvedLanguage}/courses/${courseData.course_id}`}> + {courseData.name} + </Button> )} </Stack> </> @@ -112,6 +119,19 @@ export default function ProjectView() { /> </Container> </Grid> + <Grid item sm={12}> + <Container> + <Box sx={{ + display: 'flex', + justifyContent: 'flex-end', + p: 1, + m: 1, + width: '100%' + }}> + <Button sx={{marginRight: "30px"}} variant="contained" href={location.pathname+"/overview"}>{t("projectOverview")}</Button> + </Box> + </Container> + </Grid> </Grid> ); } From 3f57d59620248adb6e2c9ca330fa8daa40794ec9 Mon Sep 17 00:00:00 2001 From: gerwoud <gerwoud@hotmail.be> Date: Mon, 20 May 2024 22:13:24 +0200 Subject: [PATCH 2/5] beaucoup --- .../endpoints/projects/endpoint_parser.py | 6 +- frontend/public/locales/en/translation.json | 13 +- frontend/public/locales/nl/translation.json | 241 +++++++++--------- frontend/src/App.tsx | 2 - .../components/DeadlineView/DeadlineGrid.tsx | 43 ++++ .../components/ProjectForm/ProjectForm.tsx | 26 +- .../projectAdminView/projectAdminView.tsx | 89 ------- .../projectAdminViewDeadlineDatagrid.tsx | 14 - .../pages/project/projectView/ProjectView.tsx | 183 ++++++++++--- 9 files changed, 328 insertions(+), 289 deletions(-) create mode 100644 frontend/src/components/DeadlineView/DeadlineGrid.tsx delete mode 100644 frontend/src/pages/project/projectAdminView/projectAdminView.tsx delete mode 100644 frontend/src/pages/project/projectAdminView/projectAdminViewDeadlineDatagrid.tsx diff --git a/backend/project/endpoints/projects/endpoint_parser.py b/backend/project/endpoints/projects/endpoint_parser.py index 48ef4874..8214c893 100644 --- a/backend/project/endpoints/projects/endpoint_parser.py +++ b/backend/project/endpoints/projects/endpoint_parser.py @@ -28,7 +28,7 @@ help='Projects visibility for students', location="form" ) -parser.add_argument("archived", type=bool, help='Projects', location="form") +parser.add_argument("archived", type=str, help='Projects', location="form") parser.add_argument( "regex_expressions", type=str, @@ -61,7 +61,9 @@ def parse_project_params(): ) ) result_dict[key] = new_deadlines + elif "archived" == key: + result_dict[key] = True if value == "true" else False else: result_dict[key] = value - return result_dict + return result_dict \ No newline at end of file diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 929cdd1c..e6b5c6e5 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -52,7 +52,7 @@ "cancel": "Cancel", "create": "Create", "activeCourses": "Active Courses", - "archivedCourses": "Archived Courses" + "archivedCourses":"Archived Courses" }, "courseForm": { "courseName": "Course Name", @@ -73,7 +73,8 @@ "submitTime": "Time submitted", "status": "Status" }, - "projectOverview": "Overview" + "projectOverview": "Overview", + "archive": "Archive" }, "time": { "yearsAgo": "years ago", @@ -128,11 +129,11 @@ "unauthorized": "You are unauthorized to upload a project for this course", "submissionError": "Submission failed, please try again" }, - "student": { + "student" : { "myProjects": "My Projects", "myCourses": "My Courses", "deadlines": "Past deadlines", - "last_submission": "Last submission", + "last_submission" : "Last submission", "course": "Course", "SUCCESS": "Success", "FAIL": "Fail", @@ -140,7 +141,7 @@ "LATE": "Late", "deadlinesOnDay": "Deadlines on: ", "noDeadline": "No deadlines on this day", - "no_submission_yet": "No submission yet", + "no_submission_yet" : "No submission yet", "loading": "Loading...", "no_projects": "There are no projects here, sign up for a course to see projects" }, @@ -150,4 +151,4 @@ "no_projects": "There are no projects here.", "new_project": "New Project" } -} +} \ No newline at end of file diff --git a/frontend/public/locales/nl/translation.json b/frontend/public/locales/nl/translation.json index 6aed358c..d9de01c9 100644 --- a/frontend/public/locales/nl/translation.json +++ b/frontend/public/locales/nl/translation.json @@ -1,144 +1,153 @@ { "header": { - "myProjects": "Mijn Projecten", - "myCourses": "Mijn Vakken", - "login": "Aanmelden", + "myProjects": "My Projects", + "myCourses": "My Courses", + "projectCreate": "Create Project", + "login": "Login", "home": "Home", - "tag": "nl", + "tag": "en", "homepage": "Homepage", - "projectUploadForm": "Project uploaden", - "logout": "Afmelden" + "projectUploadForm": "Project upload form", + "logout": "Logout" }, "home": { "home": "Home", - "tag": "nl", - "homepage": "Homepagina", - "welcomeDescription": "Welkom bij Peristerónas, het online indieningsplatform van UGent", - "login": "Aanmelden" + "tag": "en", + "homepage": "Homepage", + "welcomeDescription": "Welcome to Peristerónas, the online submission platform of UGent", + "login": "Login" }, "courseDetailTeacher": { - "title": "Vak Details", - "noCoursesFound": "Geen vakken gevonden", - "unauthorizedDelete": "U heeft niet de juiste rechten om dit vak te verwijderen", - "noProjects": "Geen projecten", - "noStudents": "Geen studenten voor dit vak", - "deleteCourse": "Verwijder vak", + "title": "Course Details", + "deleteCourse": "Delete Course", + "unauthorizedDelete": "You are unauthorized to delete this course", + "noCoursesFound": "No courses found", + "noProjects": "No projects", + "noStudents": "No students in this course", "joinCodes": "Join Codes", - "forAdmins": "Voor Admins", - "forStudents": "Voor Studenten", - "noExpiryDate": "Geen vervaldatum", - "expiryDate": "Vervaldatum", - "newJoinCode": "Nieuwe Join Code", - "deleteSelected": "Verwijder geselecteerde studenten", - "projects": "Projecten", - "newProject": "Nieuw Project", - "assistantList": "Lijst co-leerkrachten/assistenten", - "newTeacher": "Nieuwe leerkracht", - "studentList": "Lijst studenten", - "newStudent": "Nieuwe student(en)", + "forAdmins": "For Admins", + "forStudents": "For Students", + "noExpiryDate": "No expiry date", + "expiryDate": "Expiry Date", + "newJoinCode": "New Join Code", + "deleteSelected": "Delete Selected Students", + "projects": "Projects", + "newProject": "New Project", + "assistantList": "List of co-teachers/assistants", + "newTeacher": "New teacher", + "studentList": "List of students", + "newStudent": "New student(s)", "deadline": "Deadline", - "teacher": "Leerkracht", - "view": "Bekijk", + "teacher": "Teacher", + "view": "View", "admins": "Admins", - "students": "Studenten" + "students": "Students" }, "allCoursesTeacher": { - "title": "Alle Vakken", - "courseForm": "Vak Form", - "courseName": "Vak Naam", - "submit": "Opslaan", - "emptyCourseNameError": "Vak naam mag niet leeg zijn", - "cancel": "Annuleer", - "create": "Nieuw Vak", - "activeCourses": "Actieve Vakken", - "archivedCourses":"Gearchiveerde Vakken" + "title": "All Courses", + "courseForm": "Course Form", + "courseName": "Course Name", + "submit": "Submit", + "emptyCourseNameError": "Course name should not be empty", + "cancel": "Cancel", + "create": "Create", + "activeCourses": "Active Courses", + "archivedCourses":"Archived Courses" }, "courseForm": { - "courseName": "Vak Naam", - "submit": "Opslaan", - "emptyCourseNameError": "Vak naam mag niet leeg zijn" - }, - "student": { - "myProjects": "Mijn Projecten", - "myCourses": "Mijn Vakken", - "deadlines": "Verlopen Deadlines", - "course": "Vak", - "last_submission": "Laatste indiening", - "SUCCESS": "Geslaagd", - "FAIL": "Gefaald", - "RUNNING": "Aan het lopen", - "LATE": "Te laat", - "deadlinesOnDay": "Deadlines op: ", - "noDeadline": "Geen deadlines op deze dag", - "no_submission_yet" : "Nog geen indiening", - "loading": "Laden...", - "no_projects": "Er zijn hier geen projecten, meld je aan voor een vak om projecten te zien" - - }, - "projectForm": { - "projectTitle": "Titel", - "projectDescription": "Beschrijving", - "projectCourse": "Vak", - "projectDeadline": "Project deadline", - "visibleForStudents": "Zichtbaar voor studenten", - "uploadFile": "Upload bestand", - "regex": "Voeg Regex toe", - "selectCourseText": "Selecteer een vak", - "testWarning": "Opgelet: Deze opgave bevat geen tests", - "helperText": "Selecteer een geldige deadline voor het project", - "uploadProject": "Upload project", - "regexStructure": "Regex structuur", - "uploadError": "Project is niet goed geformatteerd", - "noDeadlinesPlaceholder": "Nog geen opgegeven deadlines", - "noFilesPlaceholder": "Nog geen opgave bestanden geupload", - "noRegexPlaceholder": "Nog geen regex toegevoegd", - "unauthorized": "U heeft niet de juiste rechten om een project aan te maken voor dit vak", - "submissionError": "Er is een fout opgetreden bij het indienen van uw project, probeer het later opnieuw.", - "clearSelected": "Deselecteer keuze" + "courseName": "Course Name", + "submit": "Submit", + "emptyCourseNameError": "Course name should not be empty" }, "projectView": { - "submitNetworkError": "Er is iets mislopen bij het opslaan van uw indiening. Probeer het later opnieuw.", - "selected": "Geselecteerd", - "submit": "Indienen", - "previousSubmissions": "Vorige indieningen", - "noFileSelected": "Er is geen bestand geselecteerd", + "submitNetworkError": "Failed to upload file, please try again.", + "selected": "Selected", + "submit": "Submit", + "previousSubmissions": "Previous Submissions", + "noFileSelected": "No file selected", "submissionGrid": { - "late": "Te laat", - "fail": "Gefaald", - "success": "Succesvol", - "running": "Aan het uitvoeren", - "submitTime": "Indientijd", + "late": "Late", + "fail": "Fail", + "success": "Success", + "running": "Running", + "submitTime": "Time submitted", "status": "Status" }, "projectOverview": "Overzicht" }, "time": { - "yearsAgo": "jaren geleden", - "monthsAgo": "maanden geleden", - "daysAgo": "dagen geleden", - "hoursAgo": "uur geleden", - "minutesAgo": "minuten geleden", - "justNow": "Zonet", - "yearsLater": "jaren later", - "monthsLater": "maanden later", - "daysLater": "dagen later", - "hoursLater": "uur later", - "minutesLater": "minuten later" - }, - "projectsOverview": { - "past_deadline": "Verlopen Projecten", - "future_deadline": "Opkomende Deadlines", - "no_projects": "Er zijn hier geen projecten.", - "new_project": "Nieuw Project" + "yearsAgo": "years ago", + "monthsAgo": "months ago", + "daysAgo": "days ago", + "hoursAgo": "hours ago", + "minutesAgo": "minutes ago", + "justNow": "just now", + "yearsLater": "years later", + "monthsLater": "months later", + "daysLater": "days later", + "hoursLater": "hours later", + "minutesLater": "minutes later" }, "error": { - "pageNotFound": "Pagina Niet Gevonden", - "pageNotFoundMessage": "De opgevraagde pagina werd niet gevonden.", - "forbidden": "Verboden", - "forbiddenMessage": "Je hebt geen toegang tot deze bron.", - "clientError": "Client Fout", - "clientErrorMessage": "Er is een client fout opgetreden.", - "serverError": "Server Fout", - "serverErrorMessage": "Er is een server fout opgetreden." + "pageNotFound": "Page Not Found", + "pageNotFoundMessage": "The requested page was not found.", + "forbidden": "Forbidden", + "forbiddenMessage": "You don't have access to this resource.", + "clientError": "Client Error", + "clientErrorMessage": "A client error has occured.", + "serverError": "Server Error", + "serverErrorMessage": "A server error has occured." + }, + "projectForm": { + "projectTitle": "Title", + "projectDescription": "Project description", + "projectCourse": "Course", + "projectDeadline": "Project deadline", + "visibleForStudents": "Visible for students", + "uploadFile": "Upload file", + "regex": "Add Regex", + "selectCourseText": "Select a course", + "testWarning": "Warning: This assignment doesn't contain tests", + "helperText": "Please fill in a valid deadline for the project", + "uploadProject": "Upload project", + "regexStructure": "Regex structure", + "uploadError": "Project isn't formatted appropriately", + "projectHeader": "Upload a project", + "deadline": "deadline", + "description": "Description", + "zipFile": "Zipfile", + "helperRegexText": "Regex can't be empty or already added", + "fileInfo": "The uploaded file must be a .zip file, if you want automatic tests you should include a Dockerfile or a run_tests.sh.\n For more info you should see", + "userDocs": "user guide", + "visibleForStudentsTooltip": "If this is checked the project will be visible to the students after upload", + "noDeadlinesPlaceholder": "No deadlines present yet", + "noFilesPlaceholder": "No assignment files given yet", + "noRegexPlaceholder": "No regex added yet", + "clearSelected": "Clear Selection", + "faultySubmission": "Some fields were left open or there is no valid runner/file combination", + "unauthorized": "You are unauthorized to upload a project for this course", + "submissionError": "Submission failed, please try again" + }, + "student" : { + "myProjects": "My Projects", + "myCourses": "My Courses", + "deadlines": "Past deadlines", + "last_submission" : "Last submission", + "course": "Course", + "SUCCESS": "Success", + "FAIL": "Fail", + "RUNNING": "Is running", + "LATE": "Late", + "deadlinesOnDay": "Deadlines on: ", + "noDeadline": "No deadlines on this day", + "no_submission_yet" : "No submission yet", + "loading": "Loading...", + "no_projects": "There are no projects here, sign up for a course to see projects" + }, + "projectsOverview": { + "past_deadline": "Past Projects", + "future_deadline": "Upcoming Deadlines", + "no_projects": "There are no projects here.", + "new_project": "New Project" } } \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 86cf9a0e..c3a967a0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -23,7 +23,6 @@ import { synchronizeJoinCode } from "./loaders/join-code.ts"; import { fetchMe } from "./utils/fetches/FetchMe.ts"; import {fetchProjectForm} from "./components/ProjectForm/project-form.ts"; import loadSubmissionOverview from "./loaders/submission-overview-loader.ts"; -import ProjectAdminView from "./pages/project/projectAdminView/projectAdminView.tsx"; const router = createBrowserRouter( createRoutesFromElements( @@ -52,7 +51,6 @@ const router = createBrowserRouter( path=":projectId/overview" element={<SubmissionsOverview />} /> - <Route element={<ProjectAdminView />} path=":projectId/admin"></Route> <Route path=":projectId" element={<ProjectView />}></Route> <Route path="create" element={<ProjectCreateHome />} loader={fetchProjectForm}/> </Route> diff --git a/frontend/src/components/DeadlineView/DeadlineGrid.tsx b/frontend/src/components/DeadlineView/DeadlineGrid.tsx new file mode 100644 index 00000000..5fcf3c15 --- /dev/null +++ b/frontend/src/components/DeadlineView/DeadlineGrid.tsx @@ -0,0 +1,43 @@ +import {Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@mui/material"; +import {Deadline} from "../../types/deadline.ts"; +import {useTranslation} from "react-i18next"; + +interface Props { + deadlines: Deadline[]; + minWidth: number +} + +/** + * + */ +export default function DeadlineGrid({deadlines, minWidth}: Props) { + + const { t } = useTranslation('translation', { keyPrefix: 'projectForm' }); + + return ( + <TableContainer component={Paper}> + <Table sx={{ minWidth: minWidth }}> + <TableHead> + <TableRow> + <TableCell sx={{ fontWeight: 'bold' }}>{t("deadline")}</TableCell> + <TableCell sx={{ fontWeight: 'bold' }} align="right">{t("description")}</TableCell> + </TableRow> + </TableHead> + <TableBody> + {deadlines.length === 0 ? ( // Check if deadlines is empty + <TableRow> + <TableCell colSpan={2} align="center">{t("noDeadlinesPlaceholder")}</TableCell> + </TableRow> + ) : ( + deadlines.map((deadline, index) => ( + <TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> + <TableCell component="th" scope="row">{deadline.deadline}</TableCell> + <TableCell align="right">{deadline.description}</TableCell> + </TableRow> + )) + )} + </TableBody> + </Table> + </TableContainer> + ) +} \ No newline at end of file diff --git a/frontend/src/components/ProjectForm/ProjectForm.tsx b/frontend/src/components/ProjectForm/ProjectForm.tsx index 39f81dc0..04be21de 100644 --- a/frontend/src/components/ProjectForm/ProjectForm.tsx +++ b/frontend/src/components/ProjectForm/ProjectForm.tsx @@ -31,6 +31,7 @@ import AdvancedRegex from "./AdvancedRegex.tsx"; import RunnerSelecter from "./RunnerSelecter.tsx"; import { authenticatedFetch } from "../../utils/authenticated-fetch.ts"; import i18next from "i18next"; +import DeadlineGrid from "../DeadlineView/DeadlineGrid.tsx"; interface Course { course_id: string; @@ -332,30 +333,7 @@ export default function ProjectForm() { deadlines={[]} onChange={(deadlines: Deadline[]) => handleDeadlineChange(deadlines)} editable={true} /> - <TableContainer component={Paper}> - <Table sx={{ minWidth: 650 }}> - <TableHead> - <TableRow> - <TableCell sx={{ fontWeight: 'bold' }}>{t("deadline")}</TableCell> - <TableCell sx={{ fontWeight: 'bold' }} align="right">{t("description")}</TableCell> - </TableRow> - </TableHead> - <TableBody> - {deadlines.length === 0 ? ( // Check if deadlines is empty - <TableRow> - <TableCell colSpan={2} align="center">{t("noDeadlinesPlaceholder")}</TableCell> - </TableRow> - ) : ( - deadlines.map((deadline, index) => ( - <TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> - <TableCell component="th" scope="row">{deadline.deadline}</TableCell> - <TableCell align="right">{deadline.description}</TableCell> - </TableRow> - )) - )} - </TableBody> - </Table> - </TableContainer> + <DeadlineGrid deadlines={deadlines} minWidth={650} /> </Grid> <Grid item> <Stack direction="row" style={{display: "flex", alignItems:"center"}}> diff --git a/frontend/src/pages/project/projectAdminView/projectAdminView.tsx b/frontend/src/pages/project/projectAdminView/projectAdminView.tsx deleted file mode 100644 index cf3c39db..00000000 --- a/frontend/src/pages/project/projectAdminView/projectAdminView.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { - Card, - CardContent, - CardHeader, - Container, - Grid, - Link, - Stack, - Typography, -} from "@mui/material"; -import { useEffect, useState } from "react"; -import Markdown from "react-markdown"; -import { useParams } from "react-router-dom"; -import SubmissionCard from "./SubmissionCard"; -import { Course } from "../../../types/course"; -import { Title } from "../../../components/Header/Title"; -import { authenticatedFetch } from "../../../utils/authenticated-fetch"; -import i18next from "i18next"; -import {useTranslation} from "react-i18next"; -import {Deadline} from "../../../types/deadline.ts"; -import ProjectAdminViewDeadlineDatagrid from "./projectAdminViewDeadlineDatagrid.tsx"; - -const API_URL = import.meta.env.VITE_APP_API_HOST; - -interface Project { - title: string; - description: string; - regex_expressions: string[]; -} - -/** - * - * @returns - ProjectAdminView component which displays the project details - * and submissions of the current admin for that project - */ -export default function ProjectAdminView() { - - const { t } = useTranslation('projectAdminView', { keyPrefix: 'projectsAdminView' }); - - const { projectId } = useParams<{ projectId: string }>(); - const [projectData, setProjectData] = useState<Project | null>(null); - const [deadlines, setDeadlines] = useState<Deadline[]>([]) - - useEffect(() => { - authenticatedFetch(`${API_URL}/projects/${projectId}`).then((response) => { - if (response.ok) { - response.json().then((data) => { - const project = data["data"] - setProjectData(project); - setDeadlines(project.deadlines) - }) - } - }) - }, []); - - return ( - <Grid - width="100%" - container - direction="column" - rowGap="2rem" - margin="2rem 0" - > - <Grid item> - { - projectData && ( - <Card> - <Title title={projectData.title} /> - <CardHeader - color="secondary" - title={t("projectInfo")} - ></CardHeader> - <CardContent> - <Typography variant="h6">{projectData.title}</Typography> - <Typography>{projectData.description}</Typography> - </CardContent> - <CardContent> - <Typography></Typography> - </CardContent> - <CardContent> - <ProjectAdminViewDeadlineDatagrid deadlines={deadlines}></ProjectAdminViewDeadlineDatagrid> - </CardContent> - </Card> - ) - } - </Grid> - </Grid> - ); -} diff --git a/frontend/src/pages/project/projectAdminView/projectAdminViewDeadlineDatagrid.tsx b/frontend/src/pages/project/projectAdminView/projectAdminViewDeadlineDatagrid.tsx deleted file mode 100644 index ff969d4f..00000000 --- a/frontend/src/pages/project/projectAdminView/projectAdminViewDeadlineDatagrid.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import {Deadline} from "../../../types/deadline.ts"; - -/** - * - */ -export default function ProjectAdminViewDeadlineDatagrid({ deadlines }) { - return ( - <div> - {deadlines.map((deadline: Deadline, index) => ( - <p key={index}>{deadline}</p> - ))} - </div> - ); -} diff --git a/frontend/src/pages/project/projectView/ProjectView.tsx b/frontend/src/pages/project/projectView/ProjectView.tsx index 1b30878a..a5dd7bfb 100644 --- a/frontend/src/pages/project/projectView/ProjectView.tsx +++ b/frontend/src/pages/project/projectView/ProjectView.tsx @@ -5,19 +5,24 @@ import { CardContent, CardHeader, Container, + Fade, Grid, Stack, Typography, } from "@mui/material"; import { useEffect, useState } from "react"; import Markdown from "react-markdown"; -import {useLocation, useParams} from "react-router-dom"; +import {useLocation, useNavigate, useParams} from "react-router-dom"; import SubmissionCard from "./SubmissionCard"; import { Course } from "../../../types/course"; import { Title } from "../../../components/Header/Title"; import { authenticatedFetch } from "../../../utils/authenticated-fetch"; import i18next from "i18next"; import {useTranslation} from "react-i18next"; +import {Me} from "../../../types/me.ts"; +import {fetchMe} from "../../../utils/fetches/FetchMe.ts"; +import DeadlineGrid from "../../../components/DeadlineView/DeadlineGrid.tsx"; +import {Deadline} from "../../../types/deadline.ts"; const API_URL = import.meta.env.VITE_APP_API_HOST; @@ -25,6 +30,7 @@ interface Project { title: string; description: string; regex_expressions: string[]; + archived: string; } /** @@ -35,6 +41,7 @@ interface Project { export default function ProjectView() { const location = useLocation(); + const [me, setMe] = useState<Me | null>(null); const { t } = useTranslation('translation', { keyPrefix: 'projectView' }); @@ -42,15 +49,33 @@ export default function ProjectView() { const [projectData, setProjectData] = useState<Project | null>(null); const [courseData, setCourseData] = useState<Course | null>(null); const [assignmentRawText, setAssignmentRawText] = useState<string>(""); + const [deadlines, setDeadlines] = useState<Deadline[]>([]); + const [alertVisibility, setAlertVisibility] = useState(false) - useEffect(() => { + const navigate = useNavigate() + const deleteProject = () => { + authenticatedFetch(`${API_URL}/projects/${projectId}`, { + method: "DELETE" + }); + navigate('/projects'); + } + + const updateProject = async () => { authenticatedFetch(`${API_URL}/projects/${projectId}`).then((response) => { if (response.ok) { response.json().then((data) => { const projectData = data["data"]; setProjectData(projectData); + + const transformedDeadlines = projectData.deadlines.map((deadlineArray: string[]): Deadline => ({ + description: deadlineArray[0], + deadline: deadlineArray[1] + })); + + setDeadlines(transformedDeadlines); + authenticatedFetch( - `${API_URL}/courses/${projectData.course_id}` + `${API_URL}/courses/${projectData.course_id}` ).then((response) => { if (response.ok) { response.json().then((data) => { @@ -61,6 +86,23 @@ export default function ProjectView() { }); } }); + } + + const archiveProject = async () => { + const newArchived = !projectData.archived; + const formData = new FormData(); + formData.append('archived', newArchived.toString()); + + await authenticatedFetch(`${API_URL}/projects/${projectId}`, { + method: "PATCH", + body: formData + }) + + await updateProject(); + } + + useEffect(() => { + updateProject(); authenticatedFetch( `${API_URL}/projects/${projectId}/assignment?lang=${i18next.resolvedLanguage}` @@ -69,20 +111,24 @@ export default function ProjectView() { response.text().then((data) => setAssignmentRawText(data)); } }); + + fetchMe().then((data) => { + setMe(data); + }); + }, [projectId]); if (!projectId) return null; return ( - <Grid - width="100%" - container - direction="column" - rowGap="2rem" - margin="2rem 0" - > - <Grid item sm={12}> - <Container> + <Container> + <Grid + container + direction="row" + spacing={2} + margin="2rem 0" + > + <Grid item sm={8}> {projectData && ( <Card> <Title title={projectData.title} /> @@ -95,7 +141,7 @@ export default function ProjectView() { <Typography>{projectData.description}</Typography> <Typography flex="1" /> {courseData && ( - <Button variant="contained" href={`/${i18next.resolvedLanguage}/courses/${courseData.course_id}`}> + <Button variant="outlined" type="link" href={`/${i18next.resolvedLanguage}/courses/${courseData.course_id}`}> {courseData.name} </Button> )} @@ -108,30 +154,95 @@ export default function ProjectView() { </CardContent> </Card> )} - </Container> - </Grid> - <Grid item sm={12}> - <Container> - <SubmissionCard - regexRequirements={projectData ? projectData.regex_expressions : []} - submissionUrl={`${API_URL}/submissions`} - projectId={projectId} - /> - </Container> - </Grid> - <Grid item sm={12}> - <Container> - <Box sx={{ - display: 'flex', - justifyContent: 'flex-end', - p: 1, - m: 1, - width: '100%' - }}> - <Button sx={{marginRight: "30px"}} variant="contained" href={location.pathname+"/overview"}>{t("projectOverview")}</Button> + <Box marginTop="2rem"> + <SubmissionCard + regexRequirements={projectData ? projectData.regex_expressions : []} + submissionUrl={`${API_URL}/submissions`} + projectId={projectId} + /> + </Box> + {me && me.role == "TEACHER" && ( + <Box + width="100%"> + <Box display="flex" + flexDirection="row" + sx={{ + justifyContent: "space-around" + }} + pt={2} + width="100%" + > + <Box + display="flex" + flexDirection="row" + pt={2} + width="100%" + > + <Button + type="link" + variant="contained" + href={location.pathname + "/overview"} + sx={{marginRight: 1}} + > + {t("projectOverview")} + </Button> + <Button + variant="contained" + onClick={archiveProject} + > + {t("archive")} + </Button> + </Box> + <Box + display="flex" + flexDirection="row-reverse" + pt={2} + width="100%"> + <Button variant="contained" color="error" onClick={() => setAlertVisibility(true)}> + Delete + </Button> + </Box> + </Box> + <Box display="flex" style={{width: "100%" }}> + <div style={{flexGrow: 1}} /> + <Fade + style={{width: "fit-content"}} + in={alertVisibility} + timeout={{ enter: 1000, exit: 1000 }} + addEndListener={() => { + setTimeout(() => { + setAlertVisibility(false); + }, 4000); + }} + > + <Box sx={{ border: 1, p: 1, bgcolor: 'background.paper' }}> + <Typography>Are you sure you want to delete this project</Typography> + <Box display="flex" + flexDirection="row" + sx={{ + justifyContent: "center" + }} + pt={2} + width="100%" + > + <Button variant="contained" onClick={deleteProject}> + Yes I'm Sure + </Button> + </Box> + </Box> + </Fade> + </Box> + + </Box> + )} + </Grid> + <Grid item sm={4}> + <Box marginTop="2rem"> + <DeadlineGrid deadlines={deadlines} minWidth={0} /> </Box> - </Container> + </Grid> </Grid> - </Grid> + </Container> ); + } From 32ef9c93bd6cce8d45541e09b4bd86e78537a2fa Mon Sep 17 00:00:00 2001 From: gerwoud <gerwoud@hotmail.be> Date: Tue, 21 May 2024 19:46:03 +0200 Subject: [PATCH 3/5] edit title and description functionality --- .../endpoints/projects/endpoint_parser.py | 4 +- frontend/public/locales/nl/translation.json | 244 +++++++++--------- .../pages/project/projectView/ProjectView.tsx | 105 +++++++- 3 files changed, 213 insertions(+), 140 deletions(-) diff --git a/backend/project/endpoints/projects/endpoint_parser.py b/backend/project/endpoints/projects/endpoint_parser.py index 8214c893..895658be 100644 --- a/backend/project/endpoints/projects/endpoint_parser.py +++ b/backend/project/endpoints/projects/endpoint_parser.py @@ -62,8 +62,8 @@ def parse_project_params(): ) result_dict[key] = new_deadlines elif "archived" == key: - result_dict[key] = True if value == "true" else False + result_dict[key] = value == "true" else: result_dict[key] = value - return result_dict \ No newline at end of file + return result_dict diff --git a/frontend/public/locales/nl/translation.json b/frontend/public/locales/nl/translation.json index d9de01c9..7715aecd 100644 --- a/frontend/public/locales/nl/translation.json +++ b/frontend/public/locales/nl/translation.json @@ -1,153 +1,145 @@ { "header": { - "myProjects": "My Projects", - "myCourses": "My Courses", - "projectCreate": "Create Project", - "login": "Login", + "myProjects": "Mijn Projecten", + "myCourses": "Mijn Vakken", + "login": "Aanmelden", "home": "Home", - "tag": "en", + "tag": "nl", "homepage": "Homepage", - "projectUploadForm": "Project upload form", - "logout": "Logout" + "projectUploadForm": "Project uploaden", + "logout": "Afmelden" }, "home": { "home": "Home", - "tag": "en", - "homepage": "Homepage", - "welcomeDescription": "Welcome to Peristerónas, the online submission platform of UGent", - "login": "Login" + "tag": "nl", + "homepage": "Homepagina", + "welcomeDescription": "Welkom bij Peristerónas, het online indieningsplatform van UGent", + "login": "Aanmelden" }, "courseDetailTeacher": { - "title": "Course Details", - "deleteCourse": "Delete Course", - "unauthorizedDelete": "You are unauthorized to delete this course", - "noCoursesFound": "No courses found", - "noProjects": "No projects", - "noStudents": "No students in this course", + "title": "Vak Details", + "noCoursesFound": "Geen vakken gevonden", + "unauthorizedDelete": "U heeft niet de juiste rechten om dit vak te verwijderen", + "noProjects": "Geen projecten", + "noStudents": "Geen studenten voor dit vak", + "deleteCourse": "Verwijder vak", "joinCodes": "Join Codes", - "forAdmins": "For Admins", - "forStudents": "For Students", - "noExpiryDate": "No expiry date", - "expiryDate": "Expiry Date", - "newJoinCode": "New Join Code", - "deleteSelected": "Delete Selected Students", - "projects": "Projects", - "newProject": "New Project", - "assistantList": "List of co-teachers/assistants", - "newTeacher": "New teacher", - "studentList": "List of students", - "newStudent": "New student(s)", + "forAdmins": "Voor Admins", + "forStudents": "Voor Studenten", + "noExpiryDate": "Geen vervaldatum", + "expiryDate": "Vervaldatum", + "newJoinCode": "Nieuwe Join Code", + "deleteSelected": "Verwijder geselecteerde studenten", + "projects": "Projecten", + "newProject": "Nieuw Project", + "assistantList": "Lijst co-leerkrachten/assistenten", + "newTeacher": "Nieuwe leerkracht", + "studentList": "Lijst studenten", + "newStudent": "Nieuwe student(en)", "deadline": "Deadline", - "teacher": "Teacher", - "view": "View", + "teacher": "Leerkracht", + "view": "Bekijk", "admins": "Admins", - "students": "Students" + "students": "Studenten" }, "allCoursesTeacher": { - "title": "All Courses", - "courseForm": "Course Form", - "courseName": "Course Name", - "submit": "Submit", - "emptyCourseNameError": "Course name should not be empty", - "cancel": "Cancel", - "create": "Create", - "activeCourses": "Active Courses", - "archivedCourses":"Archived Courses" + "title": "Alle Vakken", + "courseForm": "Vak Form", + "courseName": "Vak Naam", + "submit": "Opslaan", + "emptyCourseNameError": "Vak naam mag niet leeg zijn", + "cancel": "Annuleer", + "create": "Nieuw Vak", + "activeCourses": "Actieve Vakken", + "archivedCourses":"Gearchiveerde Vakken" }, "courseForm": { - "courseName": "Course Name", - "submit": "Submit", - "emptyCourseNameError": "Course name should not be empty" + "courseName": "Vak Naam", + "submit": "Opslaan", + "emptyCourseNameError": "Vak naam mag niet leeg zijn" + }, + "student": { + "myProjects": "Mijn Projecten", + "myCourses": "Mijn Vakken", + "deadlines": "Verlopen Deadlines", + "course": "Vak", + "last_submission": "Laatste indiening", + "SUCCESS": "Geslaagd", + "FAIL": "Gefaald", + "RUNNING": "Aan het lopen", + "LATE": "Te laat", + "deadlinesOnDay": "Deadlines op: ", + "noDeadline": "Geen deadlines op deze dag", + "no_submission_yet" : "Nog geen indiening", + "loading": "Laden...", + "no_projects": "Er zijn hier geen projecten, meld je aan voor een vak om projecten te zien" + + }, + "projectForm": { + "projectTitle": "Titel", + "projectDescription": "Beschrijving", + "projectCourse": "Vak", + "projectDeadline": "Project deadline", + "visibleForStudents": "Zichtbaar voor studenten", + "uploadFile": "Upload bestand", + "regex": "Voeg Regex toe", + "selectCourseText": "Selecteer een vak", + "testWarning": "Opgelet: Deze opgave bevat geen tests", + "helperText": "Selecteer een geldige deadline voor het project", + "uploadProject": "Upload project", + "regexStructure": "Regex structuur", + "uploadError": "Project is niet goed geformatteerd", + "noDeadlinesPlaceholder": "Nog geen opgegeven deadlines", + "noFilesPlaceholder": "Nog geen opgave bestanden geupload", + "noRegexPlaceholder": "Nog geen regex toegevoegd", + "unauthorized": "U heeft niet de juiste rechten om een project aan te maken voor dit vak", + "submissionError": "Er is een fout opgetreden bij het indienen van uw project, probeer het later opnieuw.", + "clearSelected": "Deselecteer keuze" }, "projectView": { - "submitNetworkError": "Failed to upload file, please try again.", - "selected": "Selected", - "submit": "Submit", - "previousSubmissions": "Previous Submissions", - "noFileSelected": "No file selected", + "submitNetworkError": "Er is iets mislopen bij het opslaan van uw indiening. Probeer het later opnieuw.", + "selected": "Geselecteerd", + "submit": "Indienen", + "previousSubmissions": "Vorige indieningen", + "noFileSelected": "Er is geen bestand geselecteerd", "submissionGrid": { - "late": "Late", - "fail": "Fail", - "success": "Success", - "running": "Running", - "submitTime": "Time submitted", + "late": "Te laat", + "fail": "Gefaald", + "success": "Succesvol", + "running": "Aan het uitvoeren", + "submitTime": "Indientijd", "status": "Status" }, - "projectOverview": "Overzicht" + "projectOverview": "Overzicht", + "archive": "Archiveer" }, "time": { - "yearsAgo": "years ago", - "monthsAgo": "months ago", - "daysAgo": "days ago", - "hoursAgo": "hours ago", - "minutesAgo": "minutes ago", - "justNow": "just now", - "yearsLater": "years later", - "monthsLater": "months later", - "daysLater": "days later", - "hoursLater": "hours later", - "minutesLater": "minutes later" - }, - "error": { - "pageNotFound": "Page Not Found", - "pageNotFoundMessage": "The requested page was not found.", - "forbidden": "Forbidden", - "forbiddenMessage": "You don't have access to this resource.", - "clientError": "Client Error", - "clientErrorMessage": "A client error has occured.", - "serverError": "Server Error", - "serverErrorMessage": "A server error has occured." - }, - "projectForm": { - "projectTitle": "Title", - "projectDescription": "Project description", - "projectCourse": "Course", - "projectDeadline": "Project deadline", - "visibleForStudents": "Visible for students", - "uploadFile": "Upload file", - "regex": "Add Regex", - "selectCourseText": "Select a course", - "testWarning": "Warning: This assignment doesn't contain tests", - "helperText": "Please fill in a valid deadline for the project", - "uploadProject": "Upload project", - "regexStructure": "Regex structure", - "uploadError": "Project isn't formatted appropriately", - "projectHeader": "Upload a project", - "deadline": "deadline", - "description": "Description", - "zipFile": "Zipfile", - "helperRegexText": "Regex can't be empty or already added", - "fileInfo": "The uploaded file must be a .zip file, if you want automatic tests you should include a Dockerfile or a run_tests.sh.\n For more info you should see", - "userDocs": "user guide", - "visibleForStudentsTooltip": "If this is checked the project will be visible to the students after upload", - "noDeadlinesPlaceholder": "No deadlines present yet", - "noFilesPlaceholder": "No assignment files given yet", - "noRegexPlaceholder": "No regex added yet", - "clearSelected": "Clear Selection", - "faultySubmission": "Some fields were left open or there is no valid runner/file combination", - "unauthorized": "You are unauthorized to upload a project for this course", - "submissionError": "Submission failed, please try again" - }, - "student" : { - "myProjects": "My Projects", - "myCourses": "My Courses", - "deadlines": "Past deadlines", - "last_submission" : "Last submission", - "course": "Course", - "SUCCESS": "Success", - "FAIL": "Fail", - "RUNNING": "Is running", - "LATE": "Late", - "deadlinesOnDay": "Deadlines on: ", - "noDeadline": "No deadlines on this day", - "no_submission_yet" : "No submission yet", - "loading": "Loading...", - "no_projects": "There are no projects here, sign up for a course to see projects" + "yearsAgo": "jaren geleden", + "monthsAgo": "maanden geleden", + "daysAgo": "dagen geleden", + "hoursAgo": "uur geleden", + "minutesAgo": "minuten geleden", + "justNow": "Zonet", + "yearsLater": "jaren later", + "monthsLater": "maanden later", + "daysLater": "dagen later", + "hoursLater": "uur later", + "minutesLater": "minuten later" }, "projectsOverview": { - "past_deadline": "Past Projects", - "future_deadline": "Upcoming Deadlines", - "no_projects": "There are no projects here.", - "new_project": "New Project" + "past_deadline": "Verlopen Projecten", + "future_deadline": "Opkomende Deadlines", + "no_projects": "Er zijn hier geen projecten.", + "new_project": "Nieuw Project" + }, + "error": { + "pageNotFound": "Pagina Niet Gevonden", + "pageNotFoundMessage": "De opgevraagde pagina werd niet gevonden.", + "forbidden": "Verboden", + "forbiddenMessage": "Je hebt geen toegang tot deze bron.", + "clientError": "Client Fout", + "clientErrorMessage": "Er is een client fout opgetreden.", + "serverError": "Server Fout", + "serverErrorMessage": "Er is een server fout opgetreden." } } \ No newline at end of file diff --git a/frontend/src/pages/project/projectView/ProjectView.tsx b/frontend/src/pages/project/projectView/ProjectView.tsx index a5dd7bfb..79497ca5 100644 --- a/frontend/src/pages/project/projectView/ProjectView.tsx +++ b/frontend/src/pages/project/projectView/ProjectView.tsx @@ -6,8 +6,9 @@ import { CardHeader, Container, Fade, - Grid, + Grid, IconButton, Stack, + TextField, Typography, } from "@mui/material"; import { useEffect, useState } from "react"; @@ -23,6 +24,9 @@ import {Me} from "../../../types/me.ts"; import {fetchMe} from "../../../utils/fetches/FetchMe.ts"; import DeadlineGrid from "../../../components/DeadlineView/DeadlineGrid.tsx"; import {Deadline} from "../../../types/deadline.ts"; +import EditIcon from '@mui/icons-material/Edit'; +import CheckIcon from '@mui/icons-material/Check'; +import CloseIcon from '@mui/icons-material/Close'; const API_URL = import.meta.env.VITE_APP_API_HOST; @@ -51,6 +55,9 @@ export default function ProjectView() { const [assignmentRawText, setAssignmentRawText] = useState<string>(""); const [deadlines, setDeadlines] = useState<Deadline[]>([]); const [alertVisibility, setAlertVisibility] = useState(false) + const [edit, setEdit] = useState(false); + let [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); const navigate = useNavigate() const deleteProject = () => { @@ -60,12 +67,43 @@ export default function ProjectView() { navigate('/projects'); } + const patchTitleAndDescription = async () => { + setEdit(false); + const formData = new FormData(); + formData.append('title', title); + formData.append('description', description); + + const response = await authenticatedFetch(`${API_URL}/projects/${projectId}`, { + method: "PATCH", + body: formData + }); + + // Check if the response is ok (status code 2xx) + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + updateProject(); + } + + const discardEditTitle = () => { + const title = projectData?.title; + setEdit(false); + if (title) + setTitle(title); + + if (projectData?.description) + setDescription(projectData?.description); + } + const updateProject = async () => { authenticatedFetch(`${API_URL}/projects/${projectId}`).then((response) => { if (response.ok) { response.json().then((data) => { const projectData = data["data"]; setProjectData(projectData); + setTitle(projectData.title); + setDescription(projectData.description); const transformedDeadlines = projectData.deadlines.map((deadlineArray: string[]): Deadline => ({ description: deadlineArray[0], @@ -75,7 +113,7 @@ export default function ProjectView() { setDeadlines(transformedDeadlines); authenticatedFetch( - `${API_URL}/courses/${projectData.course_id}` + `${API_URL}/courses/${projectData.course_id}` ).then((response) => { if (response.ok) { response.json().then((data) => { @@ -89,7 +127,7 @@ export default function ProjectView() { } const archiveProject = async () => { - const newArchived = !projectData.archived; + const newArchived = !projectData?.archived; const formData = new FormData(); formData.append('archived', newArchived.toString()); @@ -134,23 +172,66 @@ export default function ProjectView() { <Title title={projectData.title} /> <CardHeader color="secondary" - title={projectData.title} + title={ + <Box + display="flex" + justifyContent="space-between" + alignItems="center" + > + { + !edit && <>{projectData.title}</> + } + { + edit && <><TextField id="edit-title" label="title" variant="outlined" size="small" defaultValue={title} onChange={(event) => setTitle(event.target.value)}/></> + } + {courseData && ( + <Button variant="outlined" type="link" href={`/${i18next.resolvedLanguage}/courses/${courseData.course_id}`}> + {courseData.name} + </Button> + )} + </Box> + } subheader={ - <> + <Box position="relative" height="100%" sx={{marginTop: "10px"}}> <Stack direction="row" spacing={2}> - <Typography>{projectData.description}</Typography> + { + !edit && <><Typography>{projectData.description}</Typography></> + } + { + edit && <><TextField id="edit-description" label="description" variant="outlined" size="small" defaultValue={description} onChange={(event) => setDescription(event.target.value)}/></> + } <Typography flex="1" /> - {courseData && ( - <Button variant="outlined" type="link" href={`/${i18next.resolvedLanguage}/courses/${courseData.course_id}`}> - {courseData.name} - </Button> - )} </Stack> - </> + </Box> } /> <CardContent> <Markdown>{assignmentRawText}</Markdown> + <Box + display="flex" + alignItems="flex-end" + justifyContent="end" + > + { + edit && ( + <> + <IconButton onClick={patchTitleAndDescription}> + <CheckIcon /> + </IconButton> + <IconButton onClick={discardEditTitle}> + <CloseIcon /> + </IconButton> + </> + ) + } + { + !edit && ( + <IconButton onClick={() => setEdit(true)}> + <EditIcon /> + </IconButton> + ) + } + </Box> </CardContent> </Card> )} From ac43be4d5a5bdac7063cc3005eba7de937521c0d Mon Sep 17 00:00:00 2001 From: gerwoud <gerwoud@hotmail.be> Date: Tue, 21 May 2024 19:47:10 +0200 Subject: [PATCH 4/5] linter :nerd: --- frontend/src/pages/project/projectView/ProjectView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/project/projectView/ProjectView.tsx b/frontend/src/pages/project/projectView/ProjectView.tsx index 79497ca5..2aeb6aaf 100644 --- a/frontend/src/pages/project/projectView/ProjectView.tsx +++ b/frontend/src/pages/project/projectView/ProjectView.tsx @@ -56,7 +56,7 @@ export default function ProjectView() { const [deadlines, setDeadlines] = useState<Deadline[]>([]); const [alertVisibility, setAlertVisibility] = useState(false) const [edit, setEdit] = useState(false); - let [title, setTitle] = useState(""); + const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const navigate = useNavigate() From 88e47ad921770292a5d719bc021a3e4dec6656dd Mon Sep 17 00:00:00 2001 From: gerwoud <gerwoud@hotmail.be> Date: Tue, 21 May 2024 19:57:38 +0200 Subject: [PATCH 5/5] frontend linter --- frontend/src/components/DeadlineView/DeadlineGrid.tsx | 2 +- frontend/src/pages/project/projectView/ProjectView.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/DeadlineView/DeadlineGrid.tsx b/frontend/src/components/DeadlineView/DeadlineGrid.tsx index 5fcf3c15..154442da 100644 --- a/frontend/src/components/DeadlineView/DeadlineGrid.tsx +++ b/frontend/src/components/DeadlineView/DeadlineGrid.tsx @@ -8,7 +8,7 @@ interface Props { } /** - * + * @returns grid that displays deadlines in a grid */ export default function DeadlineGrid({deadlines, minWidth}: Props) { diff --git a/frontend/src/pages/project/projectView/ProjectView.tsx b/frontend/src/pages/project/projectView/ProjectView.tsx index 2aeb6aaf..8b1f8dd8 100644 --- a/frontend/src/pages/project/projectView/ProjectView.tsx +++ b/frontend/src/pages/project/projectView/ProjectView.tsx @@ -11,7 +11,7 @@ import { TextField, Typography, } from "@mui/material"; -import { useEffect, useState } from "react"; +import {useCallback, useEffect, useState} from "react"; import Markdown from "react-markdown"; import {useLocation, useNavigate, useParams} from "react-router-dom"; import SubmissionCard from "./SubmissionCard"; @@ -96,7 +96,7 @@ export default function ProjectView() { setDescription(projectData?.description); } - const updateProject = async () => { + const updateProject = useCallback(async () => { authenticatedFetch(`${API_URL}/projects/${projectId}`).then((response) => { if (response.ok) { response.json().then((data) => { @@ -124,7 +124,7 @@ export default function ProjectView() { }); } }); - } + }, [projectId]); const archiveProject = async () => { const newArchived = !projectData?.archived; @@ -154,7 +154,7 @@ export default function ProjectView() { setMe(data); }); - }, [projectId]); + }, [projectId, updateProject]); if (!projectId) return null;