From 4b5a77dbc0cbf50805ef19a5d50870597d4fd93e Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Wed, 13 Mar 2024 14:56:34 +0100 Subject: [PATCH 001/268] kleine visuele optimalisatie --- .../src/pages/groupsPage/groupsPage.tsx | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/frontend/frontend/src/pages/groupsPage/groupsPage.tsx b/frontend/frontend/src/pages/groupsPage/groupsPage.tsx index e20645cce..fbfaf8251 100644 --- a/frontend/frontend/src/pages/groupsPage/groupsPage.tsx +++ b/frontend/frontend/src/pages/groupsPage/groupsPage.tsx @@ -1,5 +1,5 @@ import {Header} from "../../components/Header.tsx"; -import {Card, Divider, Grid, List, ListItem, ListItemText, Stack, TextField, Typography} from "@mui/material"; +import {Box, Card, Divider, Grid, List, ListItem, ListItemText, Stack, TextField, Typography} from "@mui/material"; import Switch from '@mui/material/Switch'; @@ -46,9 +46,10 @@ export function GroupListItem({name}: GroupListItemProps) { <> @@ -65,8 +66,8 @@ export function GroupsPage() { return ( <>
- + Groepen: @@ -77,8 +78,8 @@ export function GroupsPage() { Leden per groep: - - + + @@ -93,19 +94,19 @@ export function GroupsPage() { + + :not(style)': {marginBottom: '8px', width: "100vh"}}}> + + Naam Student + Ingeschreven groep + + - :not(style)': {marginBottom: '8px', width: "100vh"}}}> - - Naam Student - Ingeschreven groep - - - - {groups.map((res) => - - )} - - + {groups.map((res) => + + )} + + From ce7d10ef7de7c7319086158d6b0560aa26b08f4f Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Wed, 13 Mar 2024 14:57:55 +0100 Subject: [PATCH 002/268] plaats gemaakt voor extra knoppen --- frontend/frontend/src/pages/groupsPage/groupsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/frontend/src/pages/groupsPage/groupsPage.tsx b/frontend/frontend/src/pages/groupsPage/groupsPage.tsx index fbfaf8251..b74944857 100644 --- a/frontend/frontend/src/pages/groupsPage/groupsPage.tsx +++ b/frontend/frontend/src/pages/groupsPage/groupsPage.tsx @@ -94,7 +94,7 @@ export function GroupsPage() { - + :not(style)': {marginBottom: '8px', width: "100vh"}}}> Naam Student From 6e8c92e3fd7b7c164d5cf64b7b69556fc471c1ee Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Wed, 13 Mar 2024 22:24:07 +0100 Subject: [PATCH 003/268] taal configuraties verplaatst naar eigen bestanden voor duidelijkheid --- frontend/frontend/src/i18n/config.ts | 37 +-------------- frontend/frontend/src/i18n/en.ts | 19 ++++++++ frontend/frontend/src/i18n/nl.ts | 19 ++++++++ .../AddChangeAssignmentPage.tsx | 45 +++++++++++++++++++ .../assignmentPage/assignmentStudentPage.tsx | 5 +-- 5 files changed, 87 insertions(+), 38 deletions(-) create mode 100644 frontend/frontend/src/i18n/en.ts create mode 100644 frontend/frontend/src/i18n/nl.ts create mode 100644 frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx diff --git a/frontend/frontend/src/i18n/config.ts b/frontend/frontend/src/i18n/config.ts index 7b0c659fe..6b02c1132 100644 --- a/frontend/frontend/src/i18n/config.ts +++ b/frontend/frontend/src/i18n/config.ts @@ -1,41 +1,8 @@ import {initReactI18next} from "react-i18next"; import i18n from "i18next"; import LanguageDetector from "i18next-browser-languagedetector"; - -const english = { - logo: "/assets/logo_UGent_EN_RGB_2400_white.png", - logo_blue: "/assets/logo_UGent_EN_RGB_2400_color.png", - login: "Log in with your UGent account", - back: "Back", - current_courses: "Current courses", - archived: "Archived", - students: "Students: ", - no_deadline: "No deadline", - add_admin: "Add admin", - submission: "Submission", - error: "Something went wrong.", - assignment: "Assignment:", - filename: "Submitted file:", - restrictions: "Restrictions:", - current_projects: "Current projects", -}; -const dutch = { - logo: "/assets/logo_UGent_NL_RGB_2400_wit.png", - logo_blue: "/assets/logo_UGent_NL_RGB_2400_kleur.png", - login: "Log in met je UGent account", - back: "Terug", - current_courses: "Huidige vakken", - archived: "Gearchiveerd", - students: "Studenten: ", - no_deadline: "Geen deadline", - add_admin: "Voeg admin toe", - submission: "Indiening", - error: "Er is iets misgegaan.", - assignment: "Opgave:", - filename: "Ingediend bestand:", - restrictions: "Restricties:", - current_projects: "Huidige projecten", -}; +import english from './en'; +import dutch from './nl'; i18n.use(initReactI18next).use(LanguageDetector).init({ fallbackLng: "en", diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts new file mode 100644 index 000000000..abce6983d --- /dev/null +++ b/frontend/frontend/src/i18n/en.ts @@ -0,0 +1,19 @@ +const english = { + logo: "/assets/logo_UGent_EN_RGB_2400_white.png", + logo_blue: "/assets/logo_UGent_EN_RGB_2400_color.png", + login: "Log in with your UGent account", + back: "Back", + current_courses: "Current courses", + archived: "Archived", + students: "Students: ", + no_deadline: "No deadline", + add_admin: "Add admin", + submission: "Submission", + error: "Something went wrong.", + assignment: "Assignment:", + filename: "Submitted file:", + restrictions: "Restrictions:", + current_projects: "Current projects", +}; + +export default english; \ No newline at end of file diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts new file mode 100644 index 000000000..8f2d8673e --- /dev/null +++ b/frontend/frontend/src/i18n/nl.ts @@ -0,0 +1,19 @@ +const dutch = { + logo: "/assets/logo_UGent_NL_RGB_2400_wit.png", + logo_blue: "/assets/logo_UGent_NL_RGB_2400_kleur.png", + login: "Log in met je UGent account", + back: "Terug", + current_courses: "Huidige vakken", + archived: "Gearchiveerd", + students: "Studenten: ", + no_deadline: "Geen deadline", + add_admin: "Voeg admin toe", + submission: "Indiening", + error: "Er is iets misgegaan.", + assignment: "Opgave:", + filename: "Ingediend bestand:", + restrictions: "Restricties:", + current_projects: "Huidige projecten", +}; + +export default dutch; \ No newline at end of file diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx new file mode 100644 index 000000000..69fb24052 --- /dev/null +++ b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx @@ -0,0 +1,45 @@ +import {Stack} from "@mui/material"; +import {Header} from "../../components/Header.tsx"; +import {useState} from "react"; +import {Dayjs} from "dayjs"; + +interface assignment { + title: string, + description: string, + dueDate: Dayjs, + restrictions: restriction[], + groups: boolean, + visible: boolean, +} + +interface restriction { + type: string, + value: string, + artifact: string, +} + +export function AddChangeAssignmentPage() { + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [dueDate, setDueDate] = useState(); + const [restrictions, setRestrictions] = useState([]); + const [groups, setGroups] = useState(false); + const [visible, setVisible] = useState(false); + + //upload the details of the assignment trough a text file + + const uploadDescription = (event: React.ChangeEvent) => { + + + setDescription(event.target.value); + } + + return ( + <> + +
+ + + + ); +} \ No newline at end of file diff --git a/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx b/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx index 83b137f29..9fa2a79c3 100644 --- a/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx +++ b/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx @@ -98,14 +98,13 @@ export function AssignmentStudentPage() { {assignments.map((assignment) => ( - <> + - - + ))} From 677839aa3afd0616dea280e20bd4a1eddf813793 Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Sat, 16 Mar 2024 20:40:07 +0100 Subject: [PATCH 004/268] - gewerkt aan add_change assignment pagina - commentaar geschreven voor componenten/paginas - grote error verholpen met grid2 (import Grid2 from "@mui/material/Unstable_Grid2/Grid2"; ipv import Grid2 from "@mui/material/Unstable_Grid2";) - aan uploadfunctionaliteit gewerkt --- frontend/frontend/src/Theme.ts | 3 +- .../src/components/DeadlineCalendar.tsx | 16 +- .../src/components/FileUploadButton.tsx | 41 +++ frontend/frontend/src/components/Header.tsx | 275 ++++++++++-------- frontend/frontend/src/i18n/en.ts | 3 + frontend/frontend/src/i18n/nl.ts | 3 + frontend/frontend/src/main.tsx | 7 +- .../AddChangeAssignmentPage.tsx | 85 +++++- .../frontend/src/pages/mainPage/MainPage.tsx | 18 +- .../pages/submissionPage/SubmissionPage.tsx | 12 +- 10 files changed, 312 insertions(+), 151 deletions(-) create mode 100644 frontend/frontend/src/components/FileUploadButton.tsx diff --git a/frontend/frontend/src/Theme.ts b/frontend/frontend/src/Theme.ts index 4b54483a5..d0656f058 100644 --- a/frontend/frontend/src/Theme.ts +++ b/frontend/frontend/src/Theme.ts @@ -17,7 +17,7 @@ const theme = createTheme({ }, text: { primary: '#47464A', - secondary: '#FCF8FD' + secondary: '#cbc8cc' }, error: { main: '#FF5445' @@ -27,7 +27,6 @@ const theme = createTheme({ }, }, - }); export default theme; \ No newline at end of file diff --git a/frontend/frontend/src/components/DeadlineCalendar.tsx b/frontend/frontend/src/components/DeadlineCalendar.tsx index 9d00b54ff..8611562c7 100644 --- a/frontend/frontend/src/components/DeadlineCalendar.tsx +++ b/frontend/frontend/src/components/DeadlineCalendar.tsx @@ -4,6 +4,14 @@ import {Badge, SxProps} from "@mui/material"; import {useEffect, useRef, useState} from "react"; import AssignmentIcon from '@mui/icons-material/Assignment'; +/* +* This component is a calendar that displays deadlines. +* It uses the DateCalendar component from @mui/x-date-pickers to display the calendar. +* The calendar is read-only and the user can't select a date. +* The deadlines are passed as an array of Dayjs objects. +* The deadlines are displayed as a badge on the day of the deadline. + */ + //TODO: fix highlights for day with deadlines in the displayed month function fakeFetch(date: Dayjs, {signal}: { signal: AbortSignal }, deadlines: Dayjs[]) { @@ -40,14 +48,6 @@ function ServerDay(props: PickersDayProps & { highlightedDays?: number[] ); } -/* -* This component is a calendar that displays deadlines. -* It uses the DateCalendar component from @mui/x-date-pickers to display the calendar. -* The calendar is read-only and the user can't select a date. -* The deadlines are passed as an array of Dayjs objects. -* The deadlines are displayed as a badge on the day of the deadline. - */ - interface DeadlineCalendarProps { deadlines: Dayjs[]; } diff --git a/frontend/frontend/src/components/FileUploadButton.tsx b/frontend/frontend/src/components/FileUploadButton.tsx new file mode 100644 index 000000000..2012c081c --- /dev/null +++ b/frontend/frontend/src/components/FileUploadButton.tsx @@ -0,0 +1,41 @@ +import {styled} from '@mui/material/styles'; +import Button from '@mui/material/Button'; +import UploadFileIcon from "@mui/icons-material/UploadFile"; + +const VisuallyHiddenInput = styled('input')({ + clipPath: 'inset(50%)', + height: 1, + overflow: 'hidden', + position: 'absolute', + bottom: 0, + left: 0, + whiteSpace: 'nowrap', + width: 1, +}); + +interface InputFileUploadProps { + name: string; + fileTypes: string[]; + path: string; + multiple?: boolean; +} + +export default function InputFileUpload({name, fileTypes, path, multiple}: InputFileUploadProps) { + return ( + + ); +} \ No newline at end of file diff --git a/frontend/frontend/src/components/Header.tsx b/frontend/frontend/src/components/Header.tsx index ef99bc5ed..50142b5b2 100644 --- a/frontend/frontend/src/components/Header.tsx +++ b/frontend/frontend/src/components/Header.tsx @@ -1,137 +1,162 @@ -import { - AppBar, - Box, - IconButton, - Menu, - MenuItem, - Toolbar, Tooltip, - Typography, -} from "@mui/material"; -import { useTranslation } from "react-i18next"; +import {AppBar, Box, IconButton, Menu, MenuItem, Toolbar, Tooltip, Typography,} from "@mui/material"; +import {useTranslation} from "react-i18next"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import EditIcon from "@mui/icons-material/Edit"; import React from "react"; -import { AccountCircle } from "@mui/icons-material"; -import { useNavigate } from "react-router-dom"; -import { LanguageSwitcher } from "./LanguageSwitcher.tsx"; +import {AccountCircle} from "@mui/icons-material"; +import {useNavigate} from "react-router-dom"; +import {LanguageSwitcher} from "./LanguageSwitcher.tsx"; + +/** + * Header component + * Header bar for each page, contains the logo, title, back button, edit button, and user menu + * IMPORTANT: put margin on box that comes directly under the header because of the fixed position of the header + * variants: "not_main" - pages that need a back button, "editable" - for pages that are editable, "default" - no extra buttons + * @param {Props} variant, title - The variant and title of the header + * @returns {JSX.Element} - The header component + */ + +/** + * Interface for Header props + */ interface Props { - variant: "not_main" | "editable" | "default"; - title: string; + variant: "not_main" | "editable" | "default"; + title: string; } -export const Header = ({ variant, title }: Props) => { - const { t } = useTranslation(); - const [anchorEl, setAnchorEl] = React.useState(null); - const handleMenu = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; +/** + * Header component + * @param {Props} variant, title - The variant and title of the header + */ +export const Header = ({variant, title}: Props) => { + const {t} = useTranslation(); + const [anchorEl, setAnchorEl] = React.useState(null); - const handleEdit = () => { - console.log("edit"); - navigate("edit") - } + /** + * Function to handle menu opening + * @param {React.MouseEvent} event - The event object + */ + const handleMenu = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; - const handleClose = () => { - setAnchorEl(null); - }; + /** + * Function to handle edit action + */ + const handleEdit = () => { + console.log("edit"); + navigate("edit") + } - const navigate = useNavigate(); + /** + * Function to handle menu closing + */ + const handleClose = () => { + setAnchorEl(null); + }; - const logout = () => { - localStorage.removeItem("token"); - navigate("/"); - }; + const navigate = useNavigate(); - return ( - <> - - - - - navigate("/")} - sx={{ padding: 0, borderRadius: 5 }} - > - - - - {variant !== "default" && ( - - navigate(-1)} - size="large" - edge="start" - color="inherit" - aria-label="back" - sx={{ mr: 2 }} - > - - - - )} - - - {title} - {variant === "editable" && ( - - - - )} - -
- - - - { + localStorage.removeItem("token"); + navigate("/"); + }; + + return ( + <> + - - Logout - -
-
-
- - ); -}; + + + + navigate("/")} + sx={{padding: 0, borderRadius: 5}} + > + + + + {variant !== "default" && ( + + navigate(-1)} + size="large" + edge="start" + color="inherit" + aria-label="back" + sx={{mr: 2}} + > + + + + )} + + + {title} + {variant === "editable" && ( + + + + )} + +
+ + + + + + Logout + +
+
+ + + ); +}; \ No newline at end of file diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index abce6983d..fa27f6b51 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -14,6 +14,9 @@ const english = { filename: "Submitted file:", restrictions: "Restrictions:", current_projects: "Current projects", + assignmentName: "Name assignment:", + upload: "Upload assignment", + description: "Description assignment:", }; export default english; \ No newline at end of file diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts index 8f2d8673e..f8c5e20d4 100644 --- a/frontend/frontend/src/i18n/nl.ts +++ b/frontend/frontend/src/i18n/nl.ts @@ -14,6 +14,9 @@ const dutch = { filename: "Ingediend bestand:", restrictions: "Restricties:", current_projects: "Huidige projecten", + assignmentName: "Naam opdracht:", + upload: "Upload opgavebestand", + description: "Beschrijving opgave:", }; export default dutch; \ No newline at end of file diff --git a/frontend/frontend/src/main.tsx b/frontend/frontend/src/main.tsx index 201c4b5be..897a88895 100644 --- a/frontend/frontend/src/main.tsx +++ b/frontend/frontend/src/main.tsx @@ -20,6 +20,7 @@ import {AdapterDayjs} from '@mui/x-date-pickers/AdapterDayjs/AdapterDayjs'; import {SubmissionPage} from "./pages/submissionPage/SubmissionPage.tsx"; import {SimpleRequestsPage} from "./pages/simpleRequestsPage/SimpleRequestsPage.tsx"; +import {AddChangeAssignmentPage} from "./pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx"; const router = createBrowserRouter([ @@ -57,9 +58,11 @@ const router = createBrowserRouter([ path: "/subjects_student", element: , }, - { - + path: "/add_change_assignment", + element: , + }, + { path: "/submission/:project", element: , }, diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx index 69fb24052..022750eb2 100644 --- a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx +++ b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx @@ -1,11 +1,34 @@ -import {Stack} from "@mui/material"; +import {Box, Card, Stack, TextField, Typography} from "@mui/material"; import {Header} from "../../components/Header.tsx"; -import {useState} from "react"; +import {ChangeEvent, useState} from "react"; import {Dayjs} from "dayjs"; +import {t} from "i18next"; +import {DateTimePicker, LocalizationProvider, renderTimeViewClock} from "@mui/x-date-pickers"; +import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs/AdapterDayjs"; +import 'dayjs/locale/nl'; +import FileUploadButton from "../../components/FileUploadButton"; + + +//TODO: fix api integration + +/** + * This page is used to add or change an assignment. + * The page should only be accessible to teachers of the course. + * The page should contain a form to fill in the details of the assignment. + * The form should contain the following fields: + * - Title + * - Description + * - Due date with datepicker + * - Restrictions + * - Groups + * - Visible + * The form should also contain a button to upload the assignment file for ease of use. + */ interface assignment { title: string, description: string, + assignmentFile: File, dueDate: Dayjs, restrictions: restriction[], groups: boolean, @@ -19,18 +42,20 @@ interface restriction { } export function AddChangeAssignmentPage() { + // State for the different fields of the assignment const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); - const [dueDate, setDueDate] = useState(); + const [dueDate, setDueDate] = useState(null); const [restrictions, setRestrictions] = useState([]); const [groups, setGroups] = useState(false); const [visible, setVisible] = useState(false); + const [assignmentFile, setAssignmentFile] = useState(""); - //upload the details of the assignment trough a text file - - const uploadDescription = (event: React.ChangeEvent) => { - - + /** + * Function to upload the details of the assignment through a text file + * @param {ChangeEvent} event - The event object + */ + const uploadDescription = (event: ChangeEvent) => { setDescription(event.target.value); } @@ -38,7 +63,49 @@ export function AddChangeAssignmentPage() { <>
- + + + + {t('assignmentName')} + setTitle(event.target.value)}/> + + + + + Deadline: + + setDueDate(newValue)}/> + + + + + {t('description')} + setDescription(event.target.value)} + fullWidth + sx={{overflowY: 'auto', maxHeight: '25svh'}}/> + + + ); diff --git a/frontend/frontend/src/pages/mainPage/MainPage.tsx b/frontend/frontend/src/pages/mainPage/MainPage.tsx index 0f399517d..296a7ccb2 100644 --- a/frontend/frontend/src/pages/mainPage/MainPage.tsx +++ b/frontend/frontend/src/pages/mainPage/MainPage.tsx @@ -8,9 +8,18 @@ import dayjs from "dayjs"; import {t} from "i18next"; import {useEffect, useState} from "react"; +/** + * MainPage function component + * This is the main page of the application. + * It contains a header, a tab switcher for current and archived courses, a deadline calendar, and an admin button. + */ export function MainPage() { + // State for role const [role, setRole] = useState(getRole("1")); + /** + * useEffect hook to set the role of the user and log it + */ useEffect(() => { setRole(getRole("1")); console.log("current user is: " + role); @@ -52,7 +61,12 @@ export function MainPage() { ); } -//TODO: use api to check user role +//TODO: implement api integration +/** + * Function to get the role of the user + * @param {string} id - The id of the user + * @returns {string} - The role of the user + */ function getRole(id: string): string { return "teacher"; -} +} \ No newline at end of file diff --git a/frontend/frontend/src/pages/submissionPage/SubmissionPage.tsx b/frontend/frontend/src/pages/submissionPage/SubmissionPage.tsx index 4863a2e07..c5c41a553 100644 --- a/frontend/frontend/src/pages/submissionPage/SubmissionPage.tsx +++ b/frontend/frontend/src/pages/submissionPage/SubmissionPage.tsx @@ -1,4 +1,3 @@ -import Grid2 from "@mui/material/Unstable_Grid2"; import {Header} from "../../components/Header.tsx"; import {useParams} from "react-router-dom"; import {t} from "i18next"; @@ -7,9 +6,16 @@ import {Box, Button, Card, Divider, ListItem, Paper, Typography} from "@mui/mate import dayjs, {Dayjs} from "dayjs"; import DownloadIcon from '@mui/icons-material/Download'; import List from "@mui/material/List"; +import Grid2 from "@mui/material/Unstable_Grid2/Grid2"; + +/** + * Page for viewing a specific submission + * The page should take the necessary data from the backend according to the id present in the url and the logged in user + * The page should display the deadline, the assignment, the filename and the restrictions of the submission + * The page should also allow the user to download the submission and any restriction artifacts that are present + */ interface Submission { - //Dayjs is present in pull request for mainpage deadline: Dayjs; projectName: string; assignment: string; @@ -56,7 +62,7 @@ export function SubmissionPage() { }); } - //TODO: fetch submission data from backend + //TODO: fetch submission data from backend and remove the mock data useEffect(() => { // fetch(`/api/submissions/${project}`) // .then(res => res.json()) From 5311ab5ec603f5cebd131eebef6ced4f94e5cc30 Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Sat, 16 Mar 2024 23:50:41 +0100 Subject: [PATCH 005/268] uploadfunctionaliteit werkt, halve pagina nog te maken --- frontend/frontend/index.html | 20 +++--- frontend/frontend/public/assets/favicon.svg | Bin 0 -> 60898 bytes .../src/components/FileUploadButton.tsx | 61 ++++++++++++------ frontend/frontend/src/i18n/en.ts | 2 + frontend/frontend/src/i18n/nl.ts | 2 + frontend/frontend/src/main.tsx | 21 ++++++ .../AddChangeAssignmentPage.tsx | 22 +++++-- 7 files changed, 94 insertions(+), 34 deletions(-) create mode 100644 frontend/frontend/public/assets/favicon.svg diff --git a/frontend/frontend/index.html b/frontend/frontend/index.html index 5c4dbc2a8..857a35440 100644 --- a/frontend/frontend/index.html +++ b/frontend/frontend/index.html @@ -1,13 +1,13 @@ - - - - - Vite + React + TS - - -
- - + + + + + Pigeonhole + + +
+ + diff --git a/frontend/frontend/public/assets/favicon.svg b/frontend/frontend/public/assets/favicon.svg new file mode 100644 index 0000000000000000000000000000000000000000..56e2f20f9c9e7891ca9c9023e5695b26bca8a2e3 GIT binary patch literal 60898 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 => void; fileTypes: string[]; - path: string; - multiple?: boolean; + path: File | undefined; } -export default function InputFileUpload({name, fileTypes, path, multiple}: InputFileUploadProps) { +export default function InputFileUpload({name, fileTypes, path, onFileChange}: InputFileUploadProps) { + const clearFile = () => { + const dt = new DataTransfer(); + onFileChange({target: {files: dt.files}} as unknown as ChangeEvent); + } + return ( - + <> + + + + + {path ? path.name : t('noFile')} + + + + + ); } \ No newline at end of file diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index fa27f6b51..e5ddb69c9 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -17,6 +17,8 @@ const english = { assignmentName: "Name assignment:", upload: "Upload assignment", description: "Description assignment:", + uploadToolTip: 'upload a .zip or .pdf file', + noFile: "No file chosen", }; export default english; \ No newline at end of file diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts index f8c5e20d4..de3464f59 100644 --- a/frontend/frontend/src/i18n/nl.ts +++ b/frontend/frontend/src/i18n/nl.ts @@ -17,6 +17,8 @@ const dutch = { assignmentName: "Naam opdracht:", upload: "Upload opgavebestand", description: "Beschrijving opgave:", + uploadToolTip: 'upload een .zip of .pdf bestand', + noFile: "Geen bestand gekozen", }; export default dutch; \ No newline at end of file diff --git a/frontend/frontend/src/main.tsx b/frontend/frontend/src/main.tsx index 897a88895..c23b29514 100644 --- a/frontend/frontend/src/main.tsx +++ b/frontend/frontend/src/main.tsx @@ -28,47 +28,68 @@ const router = createBrowserRouter([ path: "/", element: , errorElement: , + }, { path: "/subjects_student/:courseId", element: , + errorElement: , + }, { path: "/subjects_teacher/:courseId", element: , + errorElement: , + }, { path: "/scores", element: , + errorElement: , + }, { path: "/assignment_student", element: , + errorElement: , + }, { path: "/assignment_teacher", element: , + errorElement: , + }, { path: "/groups", element: , + errorElement: , + }, { path: "/subjects_student", element: , + errorElement: , + }, { path: "/add_change_assignment", element: , + errorElement: , + }, { path: "/submission/:project", element: , + errorElement: , + }, { path: "/test_requests", element: , + errorElement: , + } ]); diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx index 022750eb2..72ed40793 100644 --- a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx +++ b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx @@ -49,15 +49,18 @@ export function AddChangeAssignmentPage() { const [restrictions, setRestrictions] = useState([]); const [groups, setGroups] = useState(false); const [visible, setVisible] = useState(false); - const [assignmentFile, setAssignmentFile] = useState(""); + const [assignmentFile, setAssignmentFile] = useState(); /** * Function to upload the details of the assignment through a text file * @param {ChangeEvent} event - The event object */ - const uploadDescription = (event: ChangeEvent) => { - setDescription(event.target.value); - } + const handleFileChange = (event: ChangeEvent) => { + if (event.target.files) { + setAssignmentFile(event.target.files[0]); + console.log(assignmentFile?.name); + } + }; return ( <> @@ -77,8 +80,12 @@ export function AddChangeAssignmentPage() { setTitle(event.target.value)}/> - + + + @@ -105,6 +112,9 @@ export function AddChangeAssignmentPage() { sx={{overflowY: 'auto', maxHeight: '25svh'}}/> + + + From 11e5e4ea05cfc1666940b4f5ee83b0a3beb65e18 Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Mon, 18 Mar 2024 11:04:27 +0100 Subject: [PATCH 006/268] commentaar uploadknop --- .../pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx index 72ed40793..3b0d107e5 100644 --- a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx +++ b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx @@ -84,7 +84,9 @@ export function AddChangeAssignmentPage() { alignItems={"flex-start"}> + fileTypes={['.pdf', '.zip']} + tooltip={t('uploadToolTip')} + /> Date: Mon, 18 Mar 2024 18:16:08 +0100 Subject: [PATCH 007/268] begin coverage omhoog krijgen --- api/serializers/groep.py | 8 ++-- api/serializers/score.py | 1 + api/tests/serializers/test_groep.py | 54 +++++++++++++++++++++++++++ api/tests/serializers/test_project.py | 16 ++++++++ api/tests/serializers/test_score.py | 37 +++++++++++++++++- 5 files changed, 110 insertions(+), 6 deletions(-) diff --git a/api/serializers/groep.py b/api/serializers/groep.py index 7a99be60f..ec9db1cc5 100644 --- a/api/serializers/groep.py +++ b/api/serializers/groep.py @@ -72,6 +72,8 @@ def validate_students(students_data, project, current_group=None): serializers.ValidationError: Als een gebruiker geen student is of al in een andere groep voor dit project zit. """ groepen = Groep.objects.filter(project=project) + if current_group is not None: + groepen = groepen.exclude(groep_id=current_group.groep_id) student_counts = Counter(students_data) for student, count in student_counts.items(): @@ -87,11 +89,7 @@ def validate_students(students_data, project, current_group=None): ) for groep in groepen: - if ( - current_group - and groep.groep_id != current_group.groep_id - and student in groep.studenten.all() - ): + if student in groep.studenten.all(): raise serializers.ValidationError( f"Gebruiker {student} zit al in een andere groep voor dit project!" ) diff --git a/api/serializers/score.py b/api/serializers/score.py index 72eff488e..7c06ad5e6 100644 --- a/api/serializers/score.py +++ b/api/serializers/score.py @@ -28,6 +28,7 @@ def create(self, validated_data): Returns: Score: De aangemaakte score. """ + if Score.objects.filter(indiening=validated_data.get("indiening")).exists(): raise serializers.ValidationError( "Deze indiening heeft al een bestaande score" diff --git a/api/tests/serializers/test_groep.py b/api/tests/serializers/test_groep.py index 4b91dcfae..9f9b45b20 100644 --- a/api/tests/serializers/test_groep.py +++ b/api/tests/serializers/test_groep.py @@ -38,6 +38,43 @@ def test_create(self): set([student.user.id for student in groep.studenten.all()]), set(data["studenten"]), ) + + def test_create_invalid_user_already_in_a_group(self): + student = GebruikerFactory.create(is_lesgever=False).user.id + data = { + "project": self.groep.project.project_id, + "studenten": [ + student + ], + } + serializer = GroepSerializer(data=data) + self.assertTrue(serializer.is_valid()) + groep = serializer.save() + self.assertEqual(groep.project.project_id, data["project"]) + self.assertEqual( + set([student.user.id for student in groep.studenten.all()]), + set(data["studenten"]), + ) + new_data = { + "project": self.groep.project.project_id, + "studenten": [ + student + ], + } + newserializer = GroepSerializer(data=new_data) + self.assertTrue(newserializer.is_valid()) + self.assertRaises(ValidationError, newserializer.save, raise_exception=True) + + def test_create_invalid_user_is_teacher(self): + data = { + "project": self.groep.project.project_id, + "studenten": [ + GebruikerFactory.create(is_lesgever=True).user.id for _ in range(3) + ], + } + serializer = GroepSerializer(data=data) + self.assertTrue(serializer.is_valid()) + self.assertRaises(ValidationError, serializer.save, raise_exception=True) def test_update(self): data = self.serializer.data @@ -50,6 +87,23 @@ def test_update(self): [student.user.id for student in groep.studenten.all()], data["studenten"] ) + def test_update_invalid_user_already_in_this_group(self): + data = self.serializer.data + self.assertEqual(len(data["studenten"]), 1) + student = GebruikerFactory.create(is_lesgever=False).user.id + data["studenten"].append(student) + serializer = GroepSerializer(instance=self.groep, data=data, partial=True) + self.assertTrue(serializer.is_valid()) + groep = serializer.save() + self.assertEqual( + [student.user.id for student in groep.studenten.all()], data["studenten"] + ) + new_data = self.serializer.data + new_data["studenten"].append(student) + serializer = GroepSerializer(instance=groep, data=new_data, partial=True) + self.assertTrue(serializer.is_valid()) + self.assertRaises(ValidationError, serializer.save, raise_exception=True) + def test_validation_for_blank_items(self): serializer = GroepSerializer(data={"project": "", "studenten": []}) self.assertRaises(ValidationError, serializer.is_valid, raise_exception=True) diff --git a/api/tests/serializers/test_project.py b/api/tests/serializers/test_project.py index 2f482167e..c0d7d003c 100644 --- a/api/tests/serializers/test_project.py +++ b/api/tests/serializers/test_project.py @@ -5,6 +5,7 @@ from dateutil.parser import parse from api.tests.factories.vak import VakFactory from django.core.files.uploadedfile import SimpleUploadedFile +from datetime import datetime, timedelta class ProjectSerializerTest(APITestCase): @@ -82,6 +83,21 @@ def test_create(self): project = serializer.save() self.assertEqual(project.deadline, parse(data["deadline"])) + def test_create_invalid_deadline(self): + vak = VakFactory.create().vak_id + data = { + "titel": "test project", + "beschrijving": "Dit is een test project.", + "opgave_bestand": SimpleUploadedFile("file.txt", b"file_content"), + "vak": vak, + "deadline": datetime.now() - timedelta(days=1), + "max_score": 20, + } + serializer = ProjectSerializer(data=data) + self.assertTrue(serializer.is_valid()) + self.assertRaises(ValidationError, serializer.save, raise_exception=True) + + def test_update(self): data = { "titel": "test project", diff --git a/api/tests/serializers/test_score.py b/api/tests/serializers/test_score.py index 40b89b57f..50637df5a 100644 --- a/api/tests/serializers/test_score.py +++ b/api/tests/serializers/test_score.py @@ -1,4 +1,4 @@ -# test_score.py +from rest_framework.exceptions import ValidationError from django.test import TestCase from api.serializers.score import ScoreSerializer from api.tests.factories.score import ScoreFactory @@ -37,6 +37,26 @@ def test_score_serializer_create(self): score = serializer.save() self.assertEqual(score.indiening, indiening) self.assertEqual(score.score, data["score"]) + + def test_score_serializer_create_invalid(self): + indiening = IndieningFactory.create() + max_score = indiening.groep.project.max_score + data = {"indiening": indiening.indiening_id, "score": max_score} + serializer = ScoreSerializer(data=data) + self.assertTrue(serializer.is_valid()) + score = serializer.save() + new_data = {"indiening": indiening.indiening_id, "score": max_score} + new_serializer = ScoreSerializer(data=new_data) + self.assertTrue(new_serializer.is_valid()) + self.assertRaises(ValidationError, new_serializer.save, raise_exception=True) + + def test_score_serializer_create_invalid_high_score(self): + indiening = IndieningFactory.create() + max_score = indiening.groep.project.max_score + data = {"indiening": indiening.indiening_id, "score": max_score + 1} + serializer = ScoreSerializer(data=data) + self.assertTrue(serializer.is_valid()) + self.assertRaises(ValidationError, serializer.save, raise_exception=True) def test_score_serializer_update(self): score = self.score.score @@ -49,3 +69,18 @@ def test_score_serializer_update(self): self.assertTrue(serializer.is_valid()) score = serializer.save() self.assertEqual(score.score, new_data["score"]) + + def test_score_serializer_update_invalid(self): + indiening = IndieningFactory.create() + score = self.score.score + new_data = { + "score": score, + "indiening": indiening.indiening_id, + "score_id": self.score.score_id, + } + print(new_data) + serializer = ScoreSerializer(instance=self.score, data=new_data, partial=True) + self.assertTrue(serializer.is_valid()) + self.assertRaises(ValidationError, serializer.save, raise_exception=True) + + From 17b6157d9e350bdfdf86d0e458c0947e8e274def Mon Sep 17 00:00:00 2001 From: arallaer Date: Mon, 18 Mar 2024 23:57:01 +0100 Subject: [PATCH 008/268] alle serializers 100% en start views --- api/tests/factories/gebruiker.py | 11 +++- api/tests/serializers/test_score.py | 4 +- api/tests/serializers/test_vak.py | 33 +++++++++++ api/tests/views/test_gebruiker.py | 58 +++++++++++++++++- api/tests/views/test_groep.py | 91 ++++++++++++++++++++++++++++- api/views/gebruiker.py | 2 +- 6 files changed, 188 insertions(+), 11 deletions(-) diff --git a/api/tests/factories/gebruiker.py b/api/tests/factories/gebruiker.py index 54bb2f132..6b6b11c62 100644 --- a/api/tests/factories/gebruiker.py +++ b/api/tests/factories/gebruiker.py @@ -1,7 +1,7 @@ from django.contrib.auth.models import User from api.models.gebruiker import Gebruiker from factory.django import DjangoModelFactory -from factory import SubFactory, Faker +from factory import SubFactory, Faker, PostGeneration class UserFactory(DjangoModelFactory): @@ -12,7 +12,7 @@ class Meta: first_name = Faker("first_name") last_name = Faker("last_name") email = Faker("email") - is_superuser = Faker("boolean") + is_superuser = False class GebruikerFactory(DjangoModelFactory): @@ -21,3 +21,10 @@ class Meta: user = SubFactory(UserFactory) is_lesgever = Faker("boolean") + + @PostGeneration # Use the PostGeneration decorator + def make_superuser(self, create, extracted, **kwargs): + if not create: + return + self.user.is_superuser = self.is_lesgever + self.user.save() diff --git a/api/tests/serializers/test_score.py b/api/tests/serializers/test_score.py index 50637df5a..bae2f8626 100644 --- a/api/tests/serializers/test_score.py +++ b/api/tests/serializers/test_score.py @@ -72,13 +72,11 @@ def test_score_serializer_update(self): def test_score_serializer_update_invalid(self): indiening = IndieningFactory.create() - score = self.score.score new_data = { - "score": score, + "score": indiening.groep.project.max_score, "indiening": indiening.indiening_id, "score_id": self.score.score_id, } - print(new_data) serializer = ScoreSerializer(instance=self.score, data=new_data, partial=True) self.assertTrue(serializer.is_valid()) self.assertRaises(ValidationError, serializer.save, raise_exception=True) diff --git a/api/tests/serializers/test_vak.py b/api/tests/serializers/test_vak.py index 25f7601e2..aed8c376f 100644 --- a/api/tests/serializers/test_vak.py +++ b/api/tests/serializers/test_vak.py @@ -72,6 +72,39 @@ def test_create(self): ) self.assertEqual(vak.naam, "test vak") + def test_create_invalid_students(self): + students_data = [ + GebruikerFactory.create(is_lesgever=True).user.id for _ in range(3) + ] + teachers_data = [ + GebruikerFactory.create(is_lesgever=True).user.id for _ in range(3) + ] + data = { + "naam": "test vak", + "studenten": students_data, + "lesgevers": teachers_data, + } + serializer = VakSerializer(data=data) + self.assertTrue(serializer.is_valid()) + self.assertRaises(ValidationError, serializer.save, raise_exception=True) + + def test_create_invalid_teachers(self): + students_data = [ + GebruikerFactory.create(is_lesgever=False).user.id for _ in range(3) + ] + teachers_data = [ + GebruikerFactory.create(is_lesgever=False).user.id for _ in range(3) + ] + data = { + "naam": "test vak", + "studenten": students_data, + "lesgevers": teachers_data, + } + serializer = VakSerializer(data=data) + self.assertTrue(serializer.is_valid()) + self.assertRaises(ValidationError, serializer.save, raise_exception=True) + + def test_update(self): students_data = [ GebruikerFactory.create(is_lesgever=False).user.id for _ in range(3) diff --git a/api/tests/views/test_gebruiker.py b/api/tests/views/test_gebruiker.py index c21c6dc1e..344999720 100644 --- a/api/tests/views/test_gebruiker.py +++ b/api/tests/views/test_gebruiker.py @@ -6,14 +6,40 @@ class GebruikerListViewTest(APITestCase): def setUp(self): - self.gebruiker = GebruikerFactory.create() + self.student = GebruikerFactory.create(is_lesgever=False) + self.student.user.is_superuser = False + self.student.user.save() + self.teacher = GebruikerFactory.create(is_lesgever=True) self.url = reverse("gebruiker_list") self.client = APIClient() - self.client.force_login(self.gebruiker.user) + + def test_gebruiker_list_get_as_student(self): + self.client.force_login(self.student.user) + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + + def test_gebruiker_list_get_lesgevers(self): + self.client.force_login(self.teacher.user) + response = self.client.get(self.url, {'is_lesgever': 'true'}, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) def test_gebruiker_list_get(self): + self.client.force_login(self.teacher.user) response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 2) + + def test_gebruiker_list_post(self): + self.client.force_login(self.student.user) + new_data = { + "user": 69, + "is_lesgever": True, + } + response = self.client.post(self.url, new_data, format="json") + # TODO: 403 wordt nooit gegeven, want automatisch al 405 + # self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) class GebruikerDetailViewTest(APITestCase): @@ -38,3 +64,31 @@ def test_gebruiker_detail_put(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.gebruiker.refresh_from_db() self.assertEqual(self.gebruiker.is_lesgever, new_data["is_lesgever"]) + + def test_gebruiker_detail_get_non_existing_user(self): + response = self.client.get(reverse("gebruiker_detail", kwargs={"id": 69})) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_gebruiker_detail_get_unauthorized(self): + student = GebruikerFactory.create(is_lesgever=False) + self.client.force_login(student.user) + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_gebruiker_detail_put_unauthorized(self): + student = GebruikerFactory.create(is_lesgever=False) + self.client.force_login(student.user) + new_data = { + "user": self.gebruiker.user.id, + "is_lesgever": not self.gebruiker.is_lesgever, + } + response = self.client.put(self.url, new_data, format="json") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_gebruiker_detail_put_bad_request(self): + new_data = { + "user": 69, + "is_lesgever": True, + } + response = self.client.put(self.url, new_data, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/api/tests/views/test_groep.py b/api/tests/views/test_groep.py index 22c02cb09..545be3045 100644 --- a/api/tests/views/test_groep.py +++ b/api/tests/views/test_groep.py @@ -7,14 +7,47 @@ class GroepListViewTest(APITestCase): def setUp(self): - self.gebruiker = GebruikerFactory.create(is_lesgever=True) + self.student = GebruikerFactory.create(is_lesgever=False) + self.teacher = GebruikerFactory.create(is_lesgever=True) + self.groep1 = GroepFactory.create() + self.groep2 = GroepFactory.create() + self.groep1.studenten.add(self.student) self.url = reverse("groep_list") self.client = APIClient() - self.client.force_login(self.gebruiker.user) + self.client.force_login(self.teacher.user) + + def test_groep_list_get_as_teacher(self): + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 2) - def test_groep_list_get(self): + + def test_groep_list_get_as_student(self): + self.client.force_login(self.student.user) response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]["groep_id"], self.groep1.groep_id) + + def test_groep_list_get_project(self): + response = self.client.get(self.url, {"project": self.groep1.project.project_id}, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]["groep_id"], self.groep1.groep_id) + + def test_groep_list_get_invalid_project(self): + response = self.client.get(self.url, {"project": 'project'}, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_groep_list_get_student(self): + response = self.client.get(self.url, {"student": self.student.user.id}, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]["groep_id"], self.groep1.groep_id) + + def test_groep_list_get_invalid_student(self): + response = self.client.get(self.url, {"student": 'student'}, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_groep_list_post(self): groep = GroepFactory.create() @@ -25,6 +58,26 @@ def test_groep_list_post(self): } response = self.client.post(self.url, data, format="json") self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + def test_groep_list_post_unauthorized(self): + self.client.force_login(self.student.user) + groep = GroepFactory.create() + data = { + "groep_id": groep.groep_id, + "project": groep.project.project_id, + "studenten": [], + } + response = self.client.post(self.url, data, format="json") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_groep_list_post_invalid(self): + data = { + "groep_id": self.groep1.groep_id, + "project": 69, + "studenten": [], + } + response = self.client.post(self.url, data, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) class GroepDetailViewTest(APITestCase): @@ -38,6 +91,12 @@ def setUp(self): def test_groep_detail_get(self): response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_groep_detail_get_unauthorized(self): + student = GebruikerFactory.create(is_lesgever=False) + self.client.force_login(student.user) + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_groep_detail_put(self): new_data = { @@ -49,10 +108,36 @@ def test_groep_detail_put(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.groep.refresh_from_db() self.assertEqual(set(self.groep.studenten.all()), set(new_data["studenten"])) + + def test_groep_detail_put_unauthorized(self): + student = GebruikerFactory.create(is_lesgever=False) + self.client.force_login(student.user) + new_data = { + "groep_id": self.groep.groep_id, + "project": self.groep.project.project_id, + "studenten": [], + } + response = self.client.put(self.url, new_data, format="json") + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_groep_detail_put_invalid(self): + new_data = { + "groep_id": self.groep.groep_id, + "project": 69, + "studenten": [], + } + response = self.client.put(self.url, new_data, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_groep_detail_delete(self): response = self.client.delete(self.url) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + def test_groep_detail_delete_unauthorized(self): + student = GebruikerFactory.create(is_lesgever=False) + self.client.force_login(student.user) + response = self.client.delete(self.url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) def test_get_invalid_groep(self): url = reverse("groep_detail", kwargs={"id": 10101}) diff --git a/api/views/gebruiker.py b/api/views/gebruiker.py index 80d495e95..eaa38d12a 100644 --- a/api/views/gebruiker.py +++ b/api/views/gebruiker.py @@ -60,7 +60,7 @@ def gebruiker_detail(request, id): return Response(status=status.HTTP_404_NOT_FOUND) if request.method == "GET": - if is_lesgever(request.user) or id == request.user.id: + if is_lesgever(request.user) or int(id) == request.user.id: serializer = GebruikerSerializer(gebruiker) return Response(serializer.data) return Response(status=status.HTTP_403_FORBIDDEN) From 921a1ab856135a00c29422ea3a36eabd5effb6bf Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke Date: Tue, 19 Mar 2024 12:07:37 +0100 Subject: [PATCH 009/268] icons for subject teacher edit --- .../AssignmentListItemSubjectsPage.tsx | 26 ++++++++++++++++--- .../src/pages/SubjectsPage/ProjectsView.tsx | 6 +++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx index 6ad833a0f..d8418b3ef 100644 --- a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx +++ b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx @@ -1,6 +1,11 @@ -import {ListItem, ListItemButton, ListItemText, Divider} from "@mui/material"; +import {ListItem, ListItemButton, ListItemText, Divider, IconButton} from "@mui/material"; import {useNavigate} from "react-router-dom"; import {t} from "i18next"; +import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined'; +import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined'; +import ArchiveOutlinedIcon from '@mui/icons-material/ArchiveOutlined'; +import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; + interface AssignmentListItemSubjectsPageProps { key: string; projectName: string; @@ -8,6 +13,7 @@ interface AssignmentListItemSubjectsPageProps { submissions: number; score: number; isStudent: boolean; + visible: boolean; } /* @@ -20,7 +26,7 @@ interface AssignmentListItemSubjectsPageProps { * @param isStudent: boolean - if the user is a student or a teacher */ -export function AssignmentListItemSubjectsPage({key,projectName, dueDate, submissions, score, isStudent}:AssignmentListItemSubjectsPageProps) { +export function AssignmentListItemSubjectsPage({key,projectName, dueDate, submissions, score, isStudent, visible}:AssignmentListItemSubjectsPageProps) { const navigate = useNavigate(); const handleProjectClick = () => { console.log("Project clicked"); @@ -51,7 +57,21 @@ export function AssignmentListItemSubjectsPage({key,projectName, dueDate, submis <> - + {visible? + + + + : + + + + } + + + + + + } diff --git a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx index 70266f325..f0a6cf982 100644 --- a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx @@ -23,6 +23,7 @@ interface Assignment { deadline?: Date; submissions: number; score: number; + visible: boolean; } export function ProjectsView({courseId, isStudent}: ProjectsViewProps) { @@ -68,7 +69,7 @@ export function ProjectsView({courseId, isStudent}: ProjectsViewProps) { {assignments.map((assignment) => ( - + ))} @@ -96,6 +97,7 @@ function getAssignment(assignmentId: string): Assignment { name: "assignmentName", deadline: new Date(2022, 11, 17), submissions: 2, - score: 10 + score: 10, + visible: false, } } \ No newline at end of file From 966168b6f0694942bdf4807d05673598c9465586 Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke Date: Tue, 19 Mar 2024 12:45:06 +0100 Subject: [PATCH 010/268] icons alignment --- frontend/frontend/package-lock.json | 6 ++-- .../AssignmentListItemSubjectsPage.tsx | 28 ++++++++++--------- .../src/pages/SubjectsPage/ProjectsView.tsx | 2 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/frontend/frontend/package-lock.json b/frontend/frontend/package-lock.json index fdc877af6..034b0289c 100644 --- a/frontend/frontend/package-lock.json +++ b/frontend/frontend/package-lock.json @@ -2838,9 +2838,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", diff --git a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx index d8418b3ef..09060a9b1 100644 --- a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx +++ b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx @@ -57,21 +57,23 @@ export function AssignmentListItemSubjectsPage({key,projectName, dueDate, submis <> - {visible? - - + + {visible? + + + + : + + + + } + + - : - - + + - } - - - - - - + } diff --git a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx index f0a6cf982..b9fa2a98b 100644 --- a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx @@ -98,6 +98,6 @@ function getAssignment(assignmentId: string): Assignment { deadline: new Date(2022, 11, 17), submissions: 2, score: 10, - visible: false, + visible: true, } } \ No newline at end of file From 4a08412ee6c208014b562bedb886774a2d3b34fb Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke Date: Tue, 19 Mar 2024 14:05:34 +0100 Subject: [PATCH 011/268] fixed archive views --- .../src/pages/SubjectsPage/ArchivedProjectsView.tsx | 7 ------- frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx | 8 ++++++-- .../src/pages/SubjectsPage/SubjectsStudentPage.tsx | 4 ++-- .../src/pages/SubjectsPage/SubjectsTeacherPage.tsx | 4 ++-- 4 files changed, 10 insertions(+), 13 deletions(-) delete mode 100644 frontend/frontend/src/pages/SubjectsPage/ArchivedProjectsView.tsx diff --git a/frontend/frontend/src/pages/SubjectsPage/ArchivedProjectsView.tsx b/frontend/frontend/src/pages/SubjectsPage/ArchivedProjectsView.tsx deleted file mode 100644 index 0f4cded88..000000000 --- a/frontend/frontend/src/pages/SubjectsPage/ArchivedProjectsView.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export function ArchivedProjectsView() { - return ( - <> - - - ); -} \ No newline at end of file diff --git a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx index b9fa2a98b..08dda69c2 100644 --- a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx @@ -5,6 +5,7 @@ import {AssignmentListItemSubjectsPage} from "../../components/AssignmentListIte interface ProjectsViewProps { courseId: string; isStudent: boolean; + archived: boolean; } interface Course { @@ -24,9 +25,10 @@ interface Assignment { submissions: number; score: number; visible: boolean; + archived: boolean; } -export function ProjectsView({courseId, isStudent}: ProjectsViewProps) { +export function ProjectsView({courseId, isStudent, archived}: ProjectsViewProps) { const course = getCourse(courseId); const assignments = course.assignments.map((assignmentId) => getAssignment(assignmentId)); @@ -68,7 +70,8 @@ export function ProjectsView({courseId, isStudent}: ProjectsViewProps) { - {assignments.map((assignment) => ( + {assignments.filter((assignment) => assignment.archived == archived) + .map((assignment) => ( ))} @@ -99,5 +102,6 @@ function getAssignment(assignmentId: string): Assignment { submissions: 2, score: 10, visible: true, + archived: false, } } \ No newline at end of file diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx index 512c33a70..64b78987b 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx @@ -1,7 +1,6 @@ import { Header } from "../../components/Header"; import { Box, Stack } from "@mui/material"; import TabSwitcher from "../../components/TabSwitcher.tsx"; -import {ArchivedProjectsView} from "./ArchivedProjectsView.tsx"; import {ProjectsView} from "./ProjectsView.tsx"; import { useParams } from "react-router-dom"; @@ -14,7 +13,8 @@ export function SubjectsStudentPage() {
,]}/> + nodes={[, + ]}/> diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx index 1858afe4f..76c49b3fe 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx @@ -1,7 +1,6 @@ import { Header } from "../../components/Header"; import { Box, Stack } from "@mui/material"; import TabSwitcher from "../../components/TabSwitcher.tsx"; -import {ArchivedProjectsView} from "./ArchivedProjectsView.tsx"; import {ProjectsView} from "./ProjectsView.tsx"; import { useParams } from "react-router-dom"; @@ -14,7 +13,8 @@ export function SubjectsTeacherPage() {
,]}/> + nodes={[, + ]}/> From 9052192417e8c076a85942642f5585d54e676ce1 Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke Date: Tue, 19 Mar 2024 14:19:25 +0100 Subject: [PATCH 012/268] internationalisation on pages subjects teacher/student --- .../frontend/src/components/AssignmentListItemSubjectsPage.tsx | 2 +- frontend/frontend/src/i18n/en.ts | 1 + frontend/frontend/src/i18n/nl.ts | 1 + frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx | 3 ++- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx index 09060a9b1..9a2944f21 100644 --- a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx +++ b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx @@ -50,7 +50,7 @@ export function AssignmentListItemSubjectsPage({key,projectName, dueDate, submis <> - + : diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index e5ddb69c9..3aa6e0803 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -9,6 +9,7 @@ const english = { no_deadline: "No deadline", add_admin: "Add admin", submission: "Submission", + submissions: "Submissions", error: "Something went wrong.", assignment: "Assignment:", filename: "Submitted file:", diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts index de3464f59..110787725 100644 --- a/frontend/frontend/src/i18n/nl.ts +++ b/frontend/frontend/src/i18n/nl.ts @@ -9,6 +9,7 @@ const dutch = { no_deadline: "Geen deadline", add_admin: "Voeg admin toe", submission: "Indiening", + submissions: "Indieningen", error: "Er is iets misgegaan.", assignment: "Opgave:", filename: "Ingediend bestand:", diff --git a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx index 08dda69c2..dc31c2504 100644 --- a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx @@ -1,5 +1,6 @@ import {Box, Typography} from "@mui/material"; import List from '@mui/material/List'; +import {t} from "i18next"; import {AssignmentListItemSubjectsPage} from "../../components/AssignmentListItemSubjectsPage.tsx"; interface ProjectsViewProps { @@ -47,7 +48,7 @@ export function ProjectsView({courseId, isStudent, archived}: ProjectsViewProps) <> Project Deadline - Submissions + {t("submissions")} Score : From 9e8174d13f97a1e79a2ce9894187c15ad3227b40 Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke Date: Tue, 19 Mar 2024 14:45:08 +0100 Subject: [PATCH 013/268] internationalisation scores page --- frontend/frontend/src/components/StudentScoreListItem.tsx | 5 ++--- frontend/frontend/src/i18n/en.ts | 3 +++ frontend/frontend/src/i18n/nl.ts | 3 +++ frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx | 3 ++- frontend/frontend/src/pages/scoresPage/StudentsView.tsx | 3 ++- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/frontend/src/components/StudentScoreListItem.tsx b/frontend/frontend/src/components/StudentScoreListItem.tsx index fd586d323..0ba4466ce 100644 --- a/frontend/frontend/src/components/StudentScoreListItem.tsx +++ b/frontend/frontend/src/components/StudentScoreListItem.tsx @@ -1,7 +1,6 @@ import {Divider, IconButton, ListItem, ListItemText, TextField} from "@mui/material"; import DownloadIcon from '@mui/icons-material/Download'; - -//TODO: take care of internationalization +import { t } from "i18next"; interface StudentScoreListItemProps { key: string; @@ -33,7 +32,7 @@ export function StudentScoreListItem({key, studentName, submissionFiles}: Studen <> + primary={submissionFiles.length ? submissionFiles.length + " " + t("submissions") : t("no_submissions")}/> diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index 3aa6e0803..a1b54be2b 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -20,6 +20,9 @@ const english = { description: "Description assignment:", uploadToolTip: 'upload a .zip or .pdf file', noFile: "No file chosen", + export_submissions: "Export Submissions", + time: "Time", + no_submissions: "No submissions", }; export default english; \ No newline at end of file diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts index 110787725..b8c5bbfde 100644 --- a/frontend/frontend/src/i18n/nl.ts +++ b/frontend/frontend/src/i18n/nl.ts @@ -20,6 +20,9 @@ const dutch = { description: "Beschrijving opgave:", uploadToolTip: 'upload een .zip of .pdf bestand', noFile: "Geen bestand gekozen", + export_submissions: "Exporteer Indieningen", + time: "Tijdstip", + no_submissions: "Geen indieningen", }; export default dutch; \ No newline at end of file diff --git a/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx b/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx index d8ba97fee..e154eaf2a 100644 --- a/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx +++ b/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx @@ -2,6 +2,7 @@ import { Header } from "../../components/Header"; import { Box, Button, Stack } from "@mui/material"; import { useParams } from "react-router-dom"; import { StudentsView } from "./StudentsView.tsx"; +import {t} from "i18next"; import SaveIcon from '@mui/icons-material/Save'; import CloseIcon from '@mui/icons-material/Close'; @@ -17,7 +18,7 @@ export function ProjectScoresPage() { - + + - - + + + Deadline {deadline} +
+ + + {/*Opgave*/} - Opgave + Opgave @@ -93,8 +100,8 @@ export function AssignmentStudentPage() { > - Indiening - Datum + Indiening + Datum {assignments.map((assignment) => ( @@ -110,15 +117,21 @@ export function AssignmentStudentPage() { {/*Upload knop*/} - - -
- - + + + +
+ + + diff --git a/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx b/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx index 660605aeb..8b828f387 100644 --- a/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx +++ b/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx @@ -1,5 +1,5 @@ import {Header} from "../../components/Header.tsx"; -import {Button, Card, Divider, Grid, List, ListItem, ListItemText, Stack, TextField, Typography} from "@mui/material"; +import { Box, Button, Card, Divider, Grid, List, ListItem, ListItemText, Stack, TextField, Typography} from "@mui/material"; import UploadIcon from '@mui/icons-material/Upload'; import SaveIcon from '@mui/icons-material/Save'; import AddIcon from '@mui/icons-material/Add'; @@ -49,37 +49,49 @@ export function AssignmentTeacherPage() { sx={{width: "100%", height: "100%", backgroundColor: "background.default"}}> {/*opdracht and upload button*/} - + + + + + + Naam Opdracht + + + + + + +
+ + + + + {/*Deadline*/} + - - Naam Opdracht: + + Deadline - + + + -
- - - - - - - - Deadline: - - - - - - - - + {/*Opgave*/} - Opgave - {text} + Opgave + @@ -119,18 +131,23 @@ export function AssignmentTeacherPage() { - - - - -
- - + + + + +
+ + + ); diff --git a/frontend/frontend/src/pages/groupsPage/groupsPage.tsx b/frontend/frontend/src/pages/groupsPage/groupsPage.tsx index b74944857..1a63cd132 100644 --- a/frontend/frontend/src/pages/groupsPage/groupsPage.tsx +++ b/frontend/frontend/src/pages/groupsPage/groupsPage.tsx @@ -68,37 +68,49 @@ export function GroupsPage() {
- - Groepen: + + + Groepen - - - - - Leden per groep: - + + + + + Leden per groep + + + + + - - - - - - - - Willekeurige groepen: - - - - Studenten kunnen kiezen: - - - + + + + Willekeurige groepen + + + + Studenten kunnen kiezen + + + + - - :not(style)': {marginBottom: '8px', width: "100vh"}}}> + + :not(style)': {marginBottom: '8px', width: "150vh"}}}> - Naam Student - Ingeschreven groep + Naam Student + Ingeschreven groep From 24ef8a118079e72928e25d7e9a404c529df68e71 Mon Sep 17 00:00:00 2001 From: Alexandre Paice Date: Wed, 20 Mar 2024 16:02:20 +0100 Subject: [PATCH 026/268] integratie met frontend --- api/settings.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/settings.py b/api/settings.py index 5960c2f85..d67f21c51 100644 --- a/api/settings.py +++ b/api/settings.py @@ -18,7 +18,7 @@ # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent - +REAL_BASE_DIR = Path(__file__).resolve().parent.parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ @@ -62,7 +62,7 @@ TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], + 'DIRS': [os.path.join(REAL_BASE_DIR, 'frontend', 'build')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -128,6 +128,7 @@ # https://docs.djangoproject.com/en/5.0/howto/static-files/ STATIC_URL = 'static/' +STATICFILES_DIRS = [os.path.join(REAL_BASE_DIR, 'frontend', 'build', 'static')] # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field From e338e23a8f9983d713b26e14f5251a091ae7349b Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Wed, 20 Mar 2024 22:46:43 +0100 Subject: [PATCH 027/268] add/change assignment pagina volledig afgewerkt, enkel nog logica en api implementeren, GROTE ERROR OPGElOST, VITE CONFIG IN ORDE! --- frontend/frontend/package-lock.json | 6 +- frontend/frontend/src/Theme.ts | 3 + .../src/components/DeadlineCalendar.tsx | 2 +- .../src/components/FileUploadButton.tsx | 73 ++++++---- frontend/frontend/src/components/Header.tsx | 2 +- frontend/frontend/src/i18n/en.ts | 5 + frontend/frontend/src/i18n/nl.ts | 5 + .../AddChangeAssignmentPage.tsx | 128 ++++++++++++++++-- frontend/frontend/src/pages/ErrorPage.tsx | 2 +- .../src/pages/mainPage/ArchivedView.tsx | 21 +-- .../src/pages/mainPage/CoursesView.tsx | 9 +- .../frontend/src/pages/mainPage/MainPage.tsx | 3 +- .../pages/submissionPage/SubmissionPage.tsx | 10 +- frontend/frontend/vite.config.ts | 20 ++- 14 files changed, 222 insertions(+), 67 deletions(-) diff --git a/frontend/frontend/package-lock.json b/frontend/frontend/package-lock.json index fdc877af6..034b0289c 100644 --- a/frontend/frontend/package-lock.json +++ b/frontend/frontend/package-lock.json @@ -2838,9 +2838,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", diff --git a/frontend/frontend/src/Theme.ts b/frontend/frontend/src/Theme.ts index d0656f058..667d88ae6 100644 --- a/frontend/frontend/src/Theme.ts +++ b/frontend/frontend/src/Theme.ts @@ -25,6 +25,9 @@ const theme = createTheme({ success: { main: '#81A476' }, + info: { + main: '#47464A' + }, }, }); diff --git a/frontend/frontend/src/components/DeadlineCalendar.tsx b/frontend/frontend/src/components/DeadlineCalendar.tsx index 8611562c7..6473bbe3d 100644 --- a/frontend/frontend/src/components/DeadlineCalendar.tsx +++ b/frontend/frontend/src/components/DeadlineCalendar.tsx @@ -99,7 +99,7 @@ export function DeadlineCalendar({deadlines}: DeadlineCalendarProps) { <> setValue(newValue)} - onMonthChange={handleMonthChange} + onMonthChange={(newValue) => handleMonthChange(newValue)} renderLoading={() => } loading={isLoading} sx={dateStyle} diff --git a/frontend/frontend/src/components/FileUploadButton.tsx b/frontend/frontend/src/components/FileUploadButton.tsx index bfaf362b5..731e37d43 100644 --- a/frontend/frontend/src/components/FileUploadButton.tsx +++ b/frontend/frontend/src/components/FileUploadButton.tsx @@ -6,6 +6,16 @@ import {Box, IconButton, Tooltip, Typography} from "@mui/material"; import {t} from "i18next"; import ClearIcon from '@mui/icons-material/Clear'; +/* + * This component is used to create a button to upload a file. + * @parram {string} name - The name of the button + * @parram {string} tooltip - The tooltip of the button + * @parram {function} onFileChange - The function to call when the file is changed + * @parram {string[]} fileTypes - The allowed file types + * @parram {File | undefined} path - The path of the file + * @return {JSX.Element} - The button to upload a file + */ + const VisuallyHiddenInput = styled('input')({ clipPath: 'inset(50%)', height: 1, @@ -19,12 +29,13 @@ const VisuallyHiddenInput = styled('input')({ interface InputFileUploadProps { name: string; + tooltip: string; onFileChange: (event: ChangeEvent) => void; fileTypes: string[]; path: File | undefined; } -export default function InputFileUpload({name, fileTypes, path, onFileChange}: InputFileUploadProps) { +export default function InputFileUpload({name, fileTypes, path, onFileChange, tooltip}: InputFileUploadProps) { const clearFile = () => { const dt = new DataTransfer(); onFileChange({target: {files: dt.files}} as unknown as ChangeEvent); @@ -32,34 +43,40 @@ export default function InputFileUpload({name, fileTypes, path, onFileChange}: I return ( <> - - + + - {name} - - - - - {path ? path.name : t('noFile')} - - - + {path ? path.name : t('noFile')} + {path && + + + } + ); diff --git a/frontend/frontend/src/components/Header.tsx b/frontend/frontend/src/components/Header.tsx index 50142b5b2..3329a29cf 100644 --- a/frontend/frontend/src/components/Header.tsx +++ b/frontend/frontend/src/components/Header.tsx @@ -68,7 +68,7 @@ export const Header = ({variant, title}: Props) => { return ( <> diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index e5ddb69c9..2589d03da 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -19,6 +19,11 @@ const english = { description: "Description assignment:", uploadToolTip: 'upload a .zip or .pdf file', noFile: "No file chosen", + groups: "Groups", + remove: "Remove", + submit: "Submit", + cancel: "Cancel", + add_restriction: "Add restriction", }; export default english; \ No newline at end of file diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts index de3464f59..2fb24ab32 100644 --- a/frontend/frontend/src/i18n/nl.ts +++ b/frontend/frontend/src/i18n/nl.ts @@ -19,6 +19,11 @@ const dutch = { description: "Beschrijving opgave:", uploadToolTip: 'upload een .zip of .pdf bestand', noFile: "Geen bestand gekozen", + groups: "Groepen", + remove: "Verwijder", + submit: "Bevestig", + cancel: "Annuleer", + add_restriction: "Voeg restrictie toe", }; export default dutch; \ No newline at end of file diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx index 3b0d107e5..f1c18c31d 100644 --- a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx +++ b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx @@ -1,15 +1,23 @@ -import {Box, Card, Stack, TextField, Typography} from "@mui/material"; +import {Box, Button, Card, Divider, IconButton, ListItem, Stack, TextField, Tooltip, Typography} from "@mui/material"; import {Header} from "../../components/Header.tsx"; -import {ChangeEvent, useState} from "react"; +import {ChangeEvent, useEffect, useState} from "react"; import {Dayjs} from "dayjs"; import {t} from "i18next"; import {DateTimePicker, LocalizationProvider, renderTimeViewClock} from "@mui/x-date-pickers"; import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs/AdapterDayjs"; import 'dayjs/locale/nl'; import FileUploadButton from "../../components/FileUploadButton"; - +import List from "@mui/material/List"; +import ClearIcon from '@mui/icons-material/Clear'; +import AddIcon from "@mui/icons-material/Add"; +import Switch from "@mui/material/Switch"; +import VisibilityIcon from "@mui/icons-material/Visibility"; +import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; +import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; +import SaveIcon from '@mui/icons-material/Save'; //TODO: fix api integration +//TODO: add logic for all form state manipulations /** * This page is used to add or change an assignment. @@ -38,7 +46,6 @@ interface assignment { interface restriction { type: string, value: string, - artifact: string, } export function AddChangeAssignmentPage() { @@ -62,17 +69,31 @@ export function AddChangeAssignmentPage() { } }; + const handleAddRestriction = () => { + + } + + useEffect(() => { + //TODO: fetch data from api + console.log(assignmentFile); + setRestrictions([{type: "test", value: "test"}, {type: "test2", value: "test2"}, { + type: "test3", + value: "test3" + }, {type: "test4", value: "test4"}]) + }, [assignmentFile]); + return ( <> - +
- + setDueDate(newValue)}/> - - + + {t('description')} - - + + + {t("restrictions")} + + + { + restrictions.map((restriction, index) => { + return ( + + + {restriction.type} + + {restriction.value} + + + + + + + + ); + }) + } + + + + + + + + + + + + {groups ? + : + {t('groups')}} + setGroups(!groups)} color={'primary'}/> + {visible ? + setVisible(!visible)}> : + setVisible(!visible)}>} + + + + + + + + + + + + + + diff --git a/frontend/frontend/src/pages/ErrorPage.tsx b/frontend/frontend/src/pages/ErrorPage.tsx index 0efc778b9..1dcc32f30 100644 --- a/frontend/frontend/src/pages/ErrorPage.tsx +++ b/frontend/frontend/src/pages/ErrorPage.tsx @@ -10,7 +10,7 @@ export default function ErrorPage() { if (isRouteErrorResponse(error)) { // error is type `ErrorResponse` - errorMessage = error.error?.message || error.statusText; + errorMessage = error.data.message || error.statusText; } else if (error instanceof Error) { errorMessage = error.message; } else if (typeof error === 'string') { diff --git a/frontend/frontend/src/pages/mainPage/ArchivedView.tsx b/frontend/frontend/src/pages/mainPage/ArchivedView.tsx index 10ef62727..0c8787547 100644 --- a/frontend/frontend/src/pages/mainPage/ArchivedView.tsx +++ b/frontend/frontend/src/pages/mainPage/ArchivedView.tsx @@ -11,15 +11,18 @@ export function ArchivedView({isStudent}: CourseCardProps) { return ( <> - - - - - + + + + + + + diff --git a/frontend/frontend/src/pages/mainPage/CoursesView.tsx b/frontend/frontend/src/pages/mainPage/CoursesView.tsx index 96fbb7f78..3680428a8 100644 --- a/frontend/frontend/src/pages/mainPage/CoursesView.tsx +++ b/frontend/frontend/src/pages/mainPage/CoursesView.tsx @@ -12,11 +12,12 @@ export function CoursesView({isStudent}: CourseCardProps) { return ( <> - - + diff --git a/frontend/frontend/src/pages/mainPage/MainPage.tsx b/frontend/frontend/src/pages/mainPage/MainPage.tsx index 296a7ccb2..0eaaa9b21 100644 --- a/frontend/frontend/src/pages/mainPage/MainPage.tsx +++ b/frontend/frontend/src/pages/mainPage/MainPage.tsx @@ -39,7 +39,8 @@ export function MainPage() { gap: 5, }}> , ]}/> + nodes={[, + ]}/> diff --git a/frontend/frontend/src/pages/submissionPage/SubmissionPage.tsx b/frontend/frontend/src/pages/submissionPage/SubmissionPage.tsx index c5c41a553..4568a3271 100644 --- a/frontend/frontend/src/pages/submissionPage/SubmissionPage.tsx +++ b/frontend/frontend/src/pages/submissionPage/SubmissionPage.tsx @@ -6,7 +6,7 @@ import {Box, Button, Card, Divider, ListItem, Paper, Typography} from "@mui/mate import dayjs, {Dayjs} from "dayjs"; import DownloadIcon from '@mui/icons-material/Download'; import List from "@mui/material/List"; -import Grid2 from "@mui/material/Unstable_Grid2/Grid2"; +import Grid2 from "@mui/material/Unstable_Grid2"; /** * Page for viewing a specific submission @@ -172,7 +172,7 @@ export function SubmissionPage() { sx={{ padding: 1, backgroundColor: "background.default", - width: "99 %", + maxWidth: "60%", height: "20vh", }}> {t("restrictions")} @@ -181,8 +181,8 @@ export function SubmissionPage() { { submission.restrictions.map((restriction, index) => { return ( - <> - + + {restriction.name} {restriction.artifact && @@ -192,7 +192,7 @@ export function SubmissionPage() { {restriction.value} - + ); }) } diff --git a/frontend/frontend/vite.config.ts b/frontend/frontend/vite.config.ts index 5a33944a9..4bdf359d1 100644 --- a/frontend/frontend/vite.config.ts +++ b/frontend/frontend/vite.config.ts @@ -1,7 +1,21 @@ -import { defineConfig } from 'vite' +import {defineConfig} from 'vite' import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], -}) + optimizeDeps: { + include: [ + '@emotion/react', + '@emotion/styled', + '@mui/material/Tooltip', // Include other MUI components as needed + ], + }, + plugins: [ + react({ + jsxImportSource: '@emotion/react', + babel: { + plugins: ['@emotion/babel-plugin'], + }, + }), + ], +}); From edb85dcbf371e38f2750a6f8e6d62d7b0a182424 Mon Sep 17 00:00:00 2001 From: lgdtimtou Date: Thu, 21 Mar 2024 10:11:24 +0100 Subject: [PATCH 028/268] Indiening bestanden worden nu vanzelf opgevraagd via get queries naar een indiening. Aparte indiening_bestanden api call is dus verwijderd --- api/models/indiening.py | 2 +- api/serializers/gebruiker.py | 3 +- api/serializers/indiening.py | 20 +++++------ api/urls.py | 17 +--------- api/utils.py | 1 - api/views/indiening.py | 66 +----------------------------------- 6 files changed, 14 insertions(+), 95 deletions(-) diff --git a/api/models/indiening.py b/api/models/indiening.py index b3b67298d..5d428a78e 100644 --- a/api/models/indiening.py +++ b/api/models/indiening.py @@ -59,7 +59,7 @@ class IndieningBestand(models.Model): """ indiening_bestand_id = models.AutoField(primary_key=True) - indiening = models.ForeignKey("Indiening", on_delete=models.CASCADE) + indiening = models.ForeignKey(Indiening, related_name='indiening_bestanden', on_delete=models.CASCADE) bestand = models.FileField(upload_to=upload_to) def __str__(self): diff --git a/api/serializers/gebruiker.py b/api/serializers/gebruiker.py index f8aacf444..ac21686fe 100644 --- a/api/serializers/gebruiker.py +++ b/api/serializers/gebruiker.py @@ -1,7 +1,6 @@ from rest_framework import serializers from api.models.gebruiker import Gebruiker - class GebruikerSerializer(serializers.ModelSerializer): """ Serializer voor het serialiseren en deserialiseren van Gebruiker objecten. @@ -18,7 +17,7 @@ class GebruikerSerializer(serializers.ModelSerializer): class Meta: model = Gebruiker - fields = "__all__" + fields = ['user', 'user.first_name', 'is_lesgever'] def create(self, validated_data): """ diff --git a/api/serializers/indiening.py b/api/serializers/indiening.py index aa96fbb01..cd6825723 100644 --- a/api/serializers/indiening.py +++ b/api/serializers/indiening.py @@ -2,31 +2,31 @@ from api.models.indiening import Indiening, IndieningBestand -class IndieningSerializer(serializers.ModelSerializer): +class IndieningBestandSerializer(serializers.ModelSerializer): """ - Serializer voor het serialiseren en deserialiseren van Indiening objecten. + Serializer voor het serialiseren en deserialiseren van IndieningBestand objecten. Fields: - Meta.model (Indiening): Het model waarop de serializer is gebaseerd. + Meta.model (IndieningBestand): Het model waarop de serializer is gebaseerd. Meta.fields (tuple): De velden die moeten worden opgenomen in de serializer. Hier worden alle velden opgenomen. """ class Meta: - model = Indiening + model = IndieningBestand fields = "__all__" - -class IndieningBestandSerializer(serializers.ModelSerializer): +class IndieningSerializer(serializers.ModelSerializer): """ - Serializer voor het serialiseren en deserialiseren van IndieningBestand objecten. + Serializer voor het serialiseren en deserialiseren van Indiening objecten. Fields: - Meta.model (IndieningBestand): Het model waarop de serializer is gebaseerd. + Meta.model (Indiening): Het model waarop de serializer is gebaseerd. Meta.fields (tuple): De velden die moeten worden opgenomen in de serializer. Hier worden alle velden opgenomen. """ + indiening_bestanden = IndieningBestandSerializer(many=True, read_only=True) class Meta: - model = IndieningBestand - fields = "__all__" + model = Indiening + fields = ['indiening_id', 'groep', 'tijdstip', 'indiening_bestanden'] diff --git a/api/urls.py b/api/urls.py index 16d7321bf..9173c6230 100644 --- a/api/urls.py +++ b/api/urls.py @@ -23,12 +23,7 @@ from .views.gebruiker import gebruiker_list, gebruiker_detail from .views.vak import vak_list, vak_detail from .views.project import project_list, project_detail -from .views.indiening import ( - indiening_list, - indiening_detail, - indiening_bestand_list, - indiening_bestand_detail, -) +from .views.indiening import indiening_list, indiening_detail from .views.score import score_list, score_detail from .views.groep import groep_list, groep_detail @@ -46,16 +41,6 @@ path("api/projecten//", project_detail, name="project_detail"), path("api/indieningen/", indiening_list, name="indiening_list"), path("api/indieningen//", indiening_detail, name="indiening_detail"), - path( - "api/indiening_bestanden/", - indiening_bestand_list, - name="indiening_bestand_list", - ), - path( - "api/indiening_bestanden//", - indiening_bestand_detail, - name="indiening_bestand_detail", - ), path("api/scores/", score_list, name="score_list"), path("api/scores//", score_detail, name="score_detail"), path("api/groepen/", groep_list, name="groep_list"), diff --git a/api/utils.py b/api/utils.py index 6a25d2f6f..29bfec313 100644 --- a/api/utils.py +++ b/api/utils.py @@ -8,7 +8,6 @@ "vakken": "/api/vakken", "groepen": "/api/groepen", "indieningen": "/api/indieningen", - "indiening_bestanden": "/api/indiening_bestanden", "scores": "api/scores", "projecten": "api/projecten", } diff --git a/api/views/indiening.py b/api/views/indiening.py index 097cd2659..9b76c1380 100644 --- a/api/views/indiening.py +++ b/api/views/indiening.py @@ -4,7 +4,7 @@ from api.models.indiening import Indiening, IndieningBestand from api.models.groep import Groep -from api.serializers.indiening import IndieningSerializer, IndieningBestandSerializer +from api.serializers.indiening import IndieningSerializer from api.utils import is_lesgever, contains @@ -95,67 +95,3 @@ def indiening_detail(request, id, format=None): indiening.delete() return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_403_FORBIDDEN) - - -@api_view(["GET"]) -def indiening_bestand_list(request, format=None): - """ - Een view om een lijst van indieningbestanden op te halen (GET). - GET: - Als de gebruiker een lesgever is, worden alle indieningbestanden opgehaald. - Als de gebruiker geen lesgever is, worden alleen de indieningbestanden opgehaald - van de ingelogde gebruiker. - - Optionele query parameters: - indiening (int): Filtert indieningbestanden op basis van indiening-ID. - - Returns: - Response: Een lijst van indieningbestandgegevens. - """ - if request.method == "GET": - if is_lesgever(request.user): - indieningen_bestanden = IndieningBestand.objects.all() - else: - groepen = Groep.objects.filter(studenten=request.user.id) - indieningen = Indiening.objects.filter(groep__in=groepen) - indieningen_bestanden = IndieningBestand.objects.filter( - indiening__in=indieningen - ) - - if "indiening" in request.GET: - try: - indiening = eval(request.GET.get("indiening")) - indieningen_bestanden = indieningen_bestanden.filter( - indiening=indiening - ) - except NameError: - return Response(status=status.HTTP_400_BAD_REQUEST) - - serializer = IndieningBestandSerializer(indieningen_bestanden, many=True) - return Response(serializer.data) - - -@api_view(["GET"]) -def indiening_bestand_detail(request, id, format=None): - """ - Een view om de gegevens van een specifiek indieningbestand op te halen (GET). - - Args: - id (int): De primaire sleutel van het indieningbestand. - - Returns: - Response: Gegevens van het indieningbestand of een foutmelding als - het indieningbestand niet bestaat of als er een ongeautoriseerde toegang is. - """ - try: - indiening_bestand = IndieningBestand.objects.get(pk=id) - except IndieningBestand.DoesNotExist: - return Response(status=status.HTTP_404_NOT_FOUND) - - if request.method == "GET": - if is_lesgever(request.user) or contains( - indiening_bestand.indiening.groep.studenten, request.user - ): - serializer = IndieningBestandSerializer(indiening_bestand) - return Response(serializer.data) - return Response(status=status.HTTP_403_FORBIDDEN) From 178a5da96e7b9904c6f54821d89b369406982698 Mon Sep 17 00:00:00 2001 From: lgdtimtou Date: Thu, 21 Mar 2024 10:15:37 +0100 Subject: [PATCH 029/268] Zorgen dat extra gebruiker data wordt getoond zonder deze expleciet op te slaan in het gebruiker model --- api/serializers/gebruiker.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/api/serializers/gebruiker.py b/api/serializers/gebruiker.py index ac21686fe..3e4fd26de 100644 --- a/api/serializers/gebruiker.py +++ b/api/serializers/gebruiker.py @@ -15,9 +15,13 @@ class GebruikerSerializer(serializers.ModelSerializer): update(self, instance, validated_data): Werkt een bestaande gebruiker bij in de database. """ + first_name = serializers.CharField(source='user.first_name', read_only=True) + last_name = serializers.CharField(source='user.last_name', read_only=True) + email = serializers.EmailField(source='user.email', read_only=True) + class Meta: model = Gebruiker - fields = ['user', 'user.first_name', 'is_lesgever'] + fields = ['user', 'is_lesgever', 'first_name', 'last_name', 'email'] def create(self, validated_data): """ From d03a5a2ad99962329fabcf799ca3910d8bea1514 Mon Sep 17 00:00:00 2001 From: lgdtimtou Date: Thu, 21 Mar 2024 10:38:20 +0100 Subject: [PATCH 030/268] Veld extra_deadline aan project toegevoegd en ook extra validatiecheck toegevoegd dat extra deadline na de eerste deadline moet liggen --- api/models/project.py | 3 ++- api/serializers/project.py | 13 ++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/api/models/project.py b/api/models/project.py index 8a91e2758..12f2bcd61 100644 --- a/api/models/project.py +++ b/api/models/project.py @@ -46,7 +46,8 @@ class Project(models.Model): beschrijving = models.TextField() opgave_bestand = models.FileField(upload_to=upload_to) vak = models.ForeignKey(Vak, on_delete=models.CASCADE) - deadline = models.DateTimeField(null=True) + deadline = models.DateTimeField(null=True, blank=True) + extra_deadline = models.DateTimeField(null=True, blank=True) max_score = models.IntegerField(default=20) # indiening restricties diff --git a/api/serializers/project.py b/api/serializers/project.py index 90f808efc..746e06eac 100644 --- a/api/serializers/project.py +++ b/api/serializers/project.py @@ -30,10 +30,12 @@ def create(self, validated_data): Project: Het aangemaakte project. """ deadline = validated_data.pop("deadline") - validate_deadline(deadline) + extra_deadline = validated_data.pop("extra_deadline") + validate_deadlines(deadline, extra_deadline) project = Project.objects.create(**validated_data) project.deadline = deadline + project.extra_deadline = extra_deadline project.save() return project @@ -47,15 +49,17 @@ def update(self, instance, validated_data): Project: Het bijgewerkte project. """ deadline = validated_data.pop("deadline") - validate_deadline(deadline) + extra_deadline = validated_data.pop("extra_deadline") + validate_deadlines(deadline, extra_deadline) super().update(instance=instance, validated_data=validated_data) instance.deadline = deadline + instance.extra_deadline = extra_deadline instance.save() return instance -def validate_deadline(deadline): +def validate_deadlines(deadline, extra_deadline): """ Controleert of de opgegeven deadline in de toekomst ligt. @@ -67,3 +71,6 @@ def validate_deadline(deadline): """ if deadline <= timezone.now(): raise serializers.ValidationError("Deadline moet in de toekomst liggen") + + if extra_deadline <= deadline: + raise serializers.ValidationError("Extra deadline moet na de eerste deadline liggen") From 0d4e3dc85fc5380b33e3d2b1a2b0fb29695ac511 Mon Sep 17 00:00:00 2001 From: lgdtimtou Date: Thu, 21 Mar 2024 10:48:48 +0100 Subject: [PATCH 031/268] Status veld toevoegen aan indiening --- api/models/indiening.py | 1 + api/serializers/indiening.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api/models/indiening.py b/api/models/indiening.py index 5d428a78e..6bf265fdf 100644 --- a/api/models/indiening.py +++ b/api/models/indiening.py @@ -36,6 +36,7 @@ class Indiening(models.Model): indiening_id = models.AutoField(primary_key=True) groep = models.ForeignKey("Groep", on_delete=models.CASCADE) tijdstip = models.DateTimeField(auto_now_add=True) + status = models.BooleanField() def __str__(self): return str(self.indiening_id) diff --git a/api/serializers/indiening.py b/api/serializers/indiening.py index cd6825723..65d5446d7 100644 --- a/api/serializers/indiening.py +++ b/api/serializers/indiening.py @@ -29,4 +29,4 @@ class IndieningSerializer(serializers.ModelSerializer): class Meta: model = Indiening - fields = ['indiening_id', 'groep', 'tijdstip', 'indiening_bestanden'] + fields = ['indiening_id', 'groep', 'tijdstip', 'status', 'indiening_bestanden'] From 482eaf09d59fd065b4883ef6a57d73423668926d Mon Sep 17 00:00:00 2001 From: lgdtimtou Date: Thu, 21 Mar 2024 11:13:18 +0100 Subject: [PATCH 032/268] zichtbaar en gearchiveerd velden toegevoegd aan een project. Een student kan enkel zichtbare projecten zien --- api/models/project.py | 2 ++ api/views/project.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/api/models/project.py b/api/models/project.py index 12f2bcd61..7c9255304 100644 --- a/api/models/project.py +++ b/api/models/project.py @@ -49,6 +49,8 @@ class Project(models.Model): deadline = models.DateTimeField(null=True, blank=True) extra_deadline = models.DateTimeField(null=True, blank=True) max_score = models.IntegerField(default=20) + zichtbaar = models.BooleanField(default = True, blank = True) + gearchiveerd = models.BooleanField(default = False, blank = True) # indiening restricties def __str__(self): diff --git a/api/views/project.py b/api/views/project.py index d2fa57a8b..c9ee1ecde 100644 --- a/api/views/project.py +++ b/api/views/project.py @@ -32,7 +32,7 @@ def project_list(request, format=None): projects = Project.objects.all() else: vakken = Vak.objects.filter(studenten=request.user.id) - projects = Project.objects.filter(vak__in=vakken) + projects = Project.objects.filter(vak__in=vakken).filter(zichtbaar=True) if "vak" in request.GET: try: From 3bfba45a5f0a19ba3b17d4f3a78a9624cacb8f00 Mon Sep 17 00:00:00 2001 From: lgdtimtou Date: Thu, 21 Mar 2024 11:28:59 +0100 Subject: [PATCH 033/268] Validatiecheck om te zorgen dat alle studenten van een groep wel degelijk het vak hebben van het project voor die groep --- api/serializers/groep.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/api/serializers/groep.py b/api/serializers/groep.py index 7a99be60f..3c0b2eb75 100644 --- a/api/serializers/groep.py +++ b/api/serializers/groep.py @@ -1,6 +1,5 @@ from rest_framework import serializers from api.models.groep import Groep -from collections import Counter class GroepSerializer(serializers.ModelSerializer): @@ -73,18 +72,17 @@ def validate_students(students_data, project, current_group=None): """ groepen = Groep.objects.filter(project=project) - student_counts = Counter(students_data) - for student, count in student_counts.items(): - if count > 1: - raise serializers.ValidationError( - f"Student {student} zit al in deze groep!" - ) for student in students_data: if student.is_lesgever: raise serializers.ValidationError( "Alle gebruikers in 'studenten' moeten studenten zijn!" ) + + if not project.vak.studenten.all().contains(student): + raise serializers.ValidationError( + f"Student {student} is geen student van het vak {project.vak}" + ) for groep in groepen: if ( @@ -93,5 +91,5 @@ def validate_students(students_data, project, current_group=None): and student in groep.studenten.all() ): raise serializers.ValidationError( - f"Gebruiker {student} zit al in een andere groep voor dit project!" + f"Student {student} zit al in een andere groep voor dit project!" ) From 917276b7b66b08ab01e1cf1c9bc98f10d6b6f3e7 Mon Sep 17 00:00:00 2001 From: lgdtimtou Date: Thu, 21 Mar 2024 12:01:35 +0100 Subject: [PATCH 034/268] Validatiecheck wanneer gebruikers hun lesgever status wordt veranderd --- api/models/indiening.py | 4 +++- api/models/project.py | 4 ++-- api/serializers/gebruiker.py | 13 ++++++++++++- api/serializers/groep.py | 3 +-- api/serializers/indiening.py | 4 +++- api/serializers/project.py | 6 ++++-- 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/api/models/indiening.py b/api/models/indiening.py index 6bf265fdf..5ff293db7 100644 --- a/api/models/indiening.py +++ b/api/models/indiening.py @@ -60,7 +60,9 @@ class IndieningBestand(models.Model): """ indiening_bestand_id = models.AutoField(primary_key=True) - indiening = models.ForeignKey(Indiening, related_name='indiening_bestanden', on_delete=models.CASCADE) + indiening = models.ForeignKey( + Indiening, related_name="indiening_bestanden", on_delete=models.CASCADE + ) bestand = models.FileField(upload_to=upload_to) def __str__(self): diff --git a/api/models/project.py b/api/models/project.py index 7c9255304..1f9e208c6 100644 --- a/api/models/project.py +++ b/api/models/project.py @@ -49,8 +49,8 @@ class Project(models.Model): deadline = models.DateTimeField(null=True, blank=True) extra_deadline = models.DateTimeField(null=True, blank=True) max_score = models.IntegerField(default=20) - zichtbaar = models.BooleanField(default = True, blank = True) - gearchiveerd = models.BooleanField(default = False, blank = True) + zichtbaar = models.BooleanField(default=True, blank=True) + gearchiveerd = models.BooleanField(default=False, blank=True) # indiening restricties def __str__(self): diff --git a/api/serializers/gebruiker.py b/api/serializers/gebruiker.py index 3e4fd26de..7dee6cdf4 100644 --- a/api/serializers/gebruiker.py +++ b/api/serializers/gebruiker.py @@ -1,5 +1,6 @@ from rest_framework import serializers from api.models.gebruiker import Gebruiker +from api.models.vak import Vak class GebruikerSerializer(serializers.ModelSerializer): """ @@ -43,6 +44,16 @@ def update(self, instance, validated_data): Returns: Gebruiker: De bijgewerkte gebruiker. """ - instance.is_lesgever = validated_data.pop("is_lesgever") + is_lesgever = validated_data.pop("is_lesgever") + if instance.is_lesgever != is_lesgever: + validate_lesgever_change(instance) + + instance.is_lesgever = is_lesgever instance.save() return instance + +def validate_lesgever_change(instance): + if instance.is_lesgever and Vak.objects.filter(lesgevers=instance): + raise serializers.ValidationError(f"De lesgever {instance} moet eerst verwijderd worden als lesgever in zijn huidige vakken") + elif not instance.is_lesgever and Vak.objects.filter(studenten=instance): + raise serializers.ValidationError(f"De student {instance} moet eerst verwijderd worden als student in zijn huidige vakken") \ No newline at end of file diff --git a/api/serializers/groep.py b/api/serializers/groep.py index 3c0b2eb75..af8f84cfd 100644 --- a/api/serializers/groep.py +++ b/api/serializers/groep.py @@ -72,13 +72,12 @@ def validate_students(students_data, project, current_group=None): """ groepen = Groep.objects.filter(project=project) - for student in students_data: if student.is_lesgever: raise serializers.ValidationError( "Alle gebruikers in 'studenten' moeten studenten zijn!" ) - + if not project.vak.studenten.all().contains(student): raise serializers.ValidationError( f"Student {student} is geen student van het vak {project.vak}" diff --git a/api/serializers/indiening.py b/api/serializers/indiening.py index 65d5446d7..016a169d3 100644 --- a/api/serializers/indiening.py +++ b/api/serializers/indiening.py @@ -16,6 +16,7 @@ class Meta: model = IndieningBestand fields = "__all__" + class IndieningSerializer(serializers.ModelSerializer): """ Serializer voor het serialiseren en deserialiseren van Indiening objecten. @@ -25,8 +26,9 @@ class IndieningSerializer(serializers.ModelSerializer): Meta.fields (tuple): De velden die moeten worden opgenomen in de serializer. Hier worden alle velden opgenomen. """ + indiening_bestanden = IndieningBestandSerializer(many=True, read_only=True) class Meta: model = Indiening - fields = ['indiening_id', 'groep', 'tijdstip', 'status', 'indiening_bestanden'] + fields = ["indiening_id", "groep", "tijdstip", "status", "indiening_bestanden"] diff --git a/api/serializers/project.py b/api/serializers/project.py index 746e06eac..0243ebc5d 100644 --- a/api/serializers/project.py +++ b/api/serializers/project.py @@ -71,6 +71,8 @@ def validate_deadlines(deadline, extra_deadline): """ if deadline <= timezone.now(): raise serializers.ValidationError("Deadline moet in de toekomst liggen") - + if extra_deadline <= deadline: - raise serializers.ValidationError("Extra deadline moet na de eerste deadline liggen") + raise serializers.ValidationError( + "Extra deadline moet na de eerste deadline liggen" + ) From 7bad67f4fd56c0a3d300ce178a445a65a833d10b Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke Date: Thu, 21 Mar 2024 14:19:32 +0100 Subject: [PATCH 035/268] small layout fixes --- .../src/components/AssignmentListItemSubjectsPage.tsx | 3 +-- frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx | 4 ++-- .../frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx | 4 ++-- .../frontend/src/pages/scoresPage/ProjectScoresPage.tsx | 6 +++--- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx index 621079498..c05aafd80 100644 --- a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx +++ b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx @@ -7,7 +7,6 @@ import ArchiveOutlinedIcon from '@mui/icons-material/ArchiveOutlined'; import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; interface AssignmentListItemSubjectsPageProps { - key: string; projectName: string; dueDate?: Date; submissions: number; @@ -26,7 +25,7 @@ interface AssignmentListItemSubjectsPageProps { * @param isStudent: boolean - if the user is a student or a teacher */ -export function AssignmentListItemSubjectsPage({key,projectName, dueDate, submissions, score, isStudent, visible}:AssignmentListItemSubjectsPageProps) { +export function AssignmentListItemSubjectsPage({projectName, dueDate, submissions, score, isStudent, visible}:AssignmentListItemSubjectsPageProps) { const navigate = useNavigate(); const handleProjectClick = () => { console.log("Project clicked"); diff --git a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx index e47747662..6387168ee 100644 --- a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx @@ -61,7 +61,7 @@ export function ProjectsView({courseId, isStudent, archived}: ProjectsViewProps) - + {assignments.filter((assignment) => assignment.archived == archived) .map((assignment) => ( diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx index e100b35ff..c1f19639a 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx @@ -2,7 +2,7 @@ import { Header } from "../../components/Header"; import { Box, IconButton, Stack } from "@mui/material"; import TabSwitcher from "../../components/TabSwitcher.tsx"; import {ProjectsView} from "./ProjectsView.tsx"; -import { redirect, useNavigate, useParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import AddCircleIcon from '@mui/icons-material/AddCircle'; export function SubjectsTeacherPage() { @@ -18,7 +18,7 @@ export function SubjectsTeacherPage() { return ( <> - +
- +
- + - + From 0ae6fe1601bab84e93d23cebfaffb98a5f98fe61 Mon Sep 17 00:00:00 2001 From: arallaer Date: Thu, 21 Mar 2024 15:43:03 +0100 Subject: [PATCH 036/268] testen model --- api/serializers/gebruiker.py | 18 ++++++++++++------ api/tests/factories/indiening.py | 5 +++++ api/tests/factories/project.py | 7 +++++++ api/tests/models/test_indiening.py | 14 ++++++-------- api/tests/models/test_project.py | 15 +++++++++++++++ 5 files changed, 45 insertions(+), 14 deletions(-) diff --git a/api/serializers/gebruiker.py b/api/serializers/gebruiker.py index 7dee6cdf4..1292e0101 100644 --- a/api/serializers/gebruiker.py +++ b/api/serializers/gebruiker.py @@ -2,6 +2,7 @@ from api.models.gebruiker import Gebruiker from api.models.vak import Vak + class GebruikerSerializer(serializers.ModelSerializer): """ Serializer voor het serialiseren en deserialiseren van Gebruiker objecten. @@ -16,13 +17,13 @@ class GebruikerSerializer(serializers.ModelSerializer): update(self, instance, validated_data): Werkt een bestaande gebruiker bij in de database. """ - first_name = serializers.CharField(source='user.first_name', read_only=True) - last_name = serializers.CharField(source='user.last_name', read_only=True) - email = serializers.EmailField(source='user.email', read_only=True) + first_name = serializers.CharField(source="user.first_name", read_only=True) + last_name = serializers.CharField(source="user.last_name", read_only=True) + email = serializers.EmailField(source="user.email", read_only=True) class Meta: model = Gebruiker - fields = ['user', 'is_lesgever', 'first_name', 'last_name', 'email'] + fields = ["user", "is_lesgever", "first_name", "last_name", "email"] def create(self, validated_data): """ @@ -52,8 +53,13 @@ def update(self, instance, validated_data): instance.save() return instance + def validate_lesgever_change(instance): if instance.is_lesgever and Vak.objects.filter(lesgevers=instance): - raise serializers.ValidationError(f"De lesgever {instance} moet eerst verwijderd worden als lesgever in zijn huidige vakken") + raise serializers.ValidationError( + f"De lesgever {instance} moet eerst verwijderd worden als lesgever in zijn huidige vakken" + ) elif not instance.is_lesgever and Vak.objects.filter(studenten=instance): - raise serializers.ValidationError(f"De student {instance} moet eerst verwijderd worden als student in zijn huidige vakken") \ No newline at end of file + raise serializers.ValidationError( + f"De student {instance} moet eerst verwijderd worden als student in zijn huidige vakken" + ) diff --git a/api/tests/factories/indiening.py b/api/tests/factories/indiening.py index 38d63ece2..457a0a1c9 100644 --- a/api/tests/factories/indiening.py +++ b/api/tests/factories/indiening.py @@ -22,6 +22,11 @@ class Meta: fake.date_time_between(start_date="+1d", end_date="+30d") ) ) + status = factory.Faker("boolean") + + indiening_bestanden = factory.RelatedFactory( + "api.tests.factories.indiening.IndieningBestandFactory", "indiening" + ) class IndieningBestandFactory(DjangoModelFactory): diff --git a/api/tests/factories/project.py b/api/tests/factories/project.py index 216d19980..19669fc55 100644 --- a/api/tests/factories/project.py +++ b/api/tests/factories/project.py @@ -23,4 +23,11 @@ class Meta: fake.date_time_between(start_date="+1d", end_date="+30d") ) ) + extra_deadline = factory.LazyFunction( + lambda: timezone.make_aware( + fake.date_time_between(start_date="+30d", end_date="+40d") + ) + ) max_score = factory.Faker("random_int", min=10, max=100) + zichtbaar = factory.Faker('boolean') + gearchiveerd = factory.Faker('boolean') diff --git a/api/tests/models/test_indiening.py b/api/tests/models/test_indiening.py index b3bd52a65..094c0f631 100644 --- a/api/tests/models/test_indiening.py +++ b/api/tests/models/test_indiening.py @@ -15,14 +15,12 @@ def test_groep(self): def test_tijdstip(self): self.assertIsNotNone(self.indiening.tijdstip) - - def test_indiening_bestand(self): - self.assertEqual(self.indiening.indieningbestand_set.count(), 0) - - def test_indiening_bestand_add(self): - IndieningBestandFactory.create(indiening=self.indiening) - self.assertEqual(self.indiening.indieningbestand_set.count(), 1) - + + def test_status(self): + self.assertIsNotNone(self.indiening.status) + + def test_indiening_bestanden(self): + self.assertEqual(self.indiening.indiening_bestanden.count(), 1) class IndieningBestandModelTest(TestCase): def setUp(self): diff --git a/api/tests/models/test_project.py b/api/tests/models/test_project.py index 971c0eec0..29ec4d61e 100644 --- a/api/tests/models/test_project.py +++ b/api/tests/models/test_project.py @@ -14,3 +14,18 @@ def test_project_vak(self): def test_project_max_score(self): self.assertTrue(10 <= self.project.max_score <= 100) + + def test_project_zichtbaar(self): + self.assertIsNotNone(self.project.zichtbaar) + + def test_project_gearchiveerd(self): + self.assertIsNotNone(self.project.gearchiveerd) + + def test_project_deadline(self): + self.assertIsNotNone(self.project.deadline) + + def test_project_extra_deadline(self): + self.assertIsNotNone(self.project.extra_deadline) + + def test_project_opgave_bestand(self): + self.assertEqual(self.project.opgave_bestand.read(), b"file content") From ae887507fb61de71a83c2731302f63fc1b5688c3 Mon Sep 17 00:00:00 2001 From: arallaer Date: Thu, 21 Mar 2024 16:41:36 +0100 Subject: [PATCH 037/268] serializer testen --- api/models/indiening.py | 2 +- api/serializers/groep.py | 8 +++++ api/tests/factories/groep.py | 4 ++- api/tests/factories/indiening.py | 4 +-- api/tests/factories/project.py | 1 - api/tests/serializers/test_gebruiker.py | 14 +++++++- api/tests/serializers/test_groep.py | 38 ++++++++++++++++++--- api/tests/serializers/test_indiening.py | 6 ++-- api/tests/serializers/test_project.py | 44 +++++++++++++++++++++++++ 9 files changed, 106 insertions(+), 15 deletions(-) diff --git a/api/models/indiening.py b/api/models/indiening.py index 5ff293db7..5a9d48139 100644 --- a/api/models/indiening.py +++ b/api/models/indiening.py @@ -36,7 +36,7 @@ class Indiening(models.Model): indiening_id = models.AutoField(primary_key=True) groep = models.ForeignKey("Groep", on_delete=models.CASCADE) tijdstip = models.DateTimeField(auto_now_add=True) - status = models.BooleanField() + status = models.BooleanField(default=False) def __str__(self): return str(self.indiening_id) diff --git a/api/serializers/groep.py b/api/serializers/groep.py index 355b86858..c2c4715a2 100644 --- a/api/serializers/groep.py +++ b/api/serializers/groep.py @@ -1,5 +1,6 @@ from rest_framework import serializers from api.models.groep import Groep +from collections import Counter class GroepSerializer(serializers.ModelSerializer): @@ -73,6 +74,13 @@ def validate_students(students_data, project, current_group=None): groepen = Groep.objects.filter(project=project) if current_group is not None: groepen = groepen.exclude(groep_id=current_group.groep_id) + + student_counts = Counter(students_data) + for student, count in student_counts.items(): + if count > 1: + raise serializers.ValidationError( + f"Student {student} komt meerdere keren voor in de groep!" + ) for student in students_data: if student.is_lesgever: diff --git a/api/tests/factories/groep.py b/api/tests/factories/groep.py index 1602802ca..40c796004 100644 --- a/api/tests/factories/groep.py +++ b/api/tests/factories/groep.py @@ -21,4 +21,6 @@ def studenten(self, create, extracted, **kwargs): for student in extracted: self.studenten.add(student) else: - self.studenten.add(GebruikerFactory(is_lesgever=False)) + student = GebruikerFactory(is_lesgever=False) + self.project.vak.studenten.add(student) + self.studenten.add(student) diff --git a/api/tests/factories/indiening.py b/api/tests/factories/indiening.py index 457a0a1c9..7210f2b5a 100644 --- a/api/tests/factories/indiening.py +++ b/api/tests/factories/indiening.py @@ -15,7 +15,6 @@ class IndieningFactory(DjangoModelFactory): class Meta: model = Indiening - indiening_id = factory.Sequence(lambda n: n) groep = SubFactory(GroepFactory) tijdstip = factory.LazyFunction( lambda: timezone.make_aware( @@ -23,7 +22,7 @@ class Meta: ) ) status = factory.Faker("boolean") - + indiening_bestanden = factory.RelatedFactory( "api.tests.factories.indiening.IndieningBestandFactory", "indiening" ) @@ -33,6 +32,5 @@ class IndieningBestandFactory(DjangoModelFactory): class Meta: model = IndieningBestand - indiening_bestand_id = factory.Sequence(lambda n: n) indiening = SubFactory(IndieningFactory) bestand = FileField(filename="test.txt", data=b"file content") diff --git a/api/tests/factories/project.py b/api/tests/factories/project.py index 19669fc55..39f49dde1 100644 --- a/api/tests/factories/project.py +++ b/api/tests/factories/project.py @@ -13,7 +13,6 @@ class ProjectFactory(DjangoModelFactory): class Meta: model = Project - project_id = factory.Sequence(lambda n: n) titel = factory.Faker("word") beschrijving = factory.Faker("paragraph") opgave_bestand = factory.django.FileField(data=b"file content") diff --git a/api/tests/serializers/test_gebruiker.py b/api/tests/serializers/test_gebruiker.py index a0279b4cf..fa71f7b8c 100644 --- a/api/tests/serializers/test_gebruiker.py +++ b/api/tests/serializers/test_gebruiker.py @@ -12,7 +12,7 @@ def setUp(self): def test_contains_expected_fields(self): data = self.serializer.data - self.assertCountEqual(data.keys(), ["user", "is_lesgever"]) + self.assertCountEqual(data.keys(), ["user", "is_lesgever", "first_name", "last_name", "email"]) def test_user_field_content(self): data = self.serializer.data @@ -21,6 +21,18 @@ def test_user_field_content(self): def test_is_lesgever_field_content(self): data = self.serializer.data self.assertEqual(data["is_lesgever"], self.gebruiker.is_lesgever) + + def test_first_name_field_content(self): + data = self.serializer.data + self.assertEqual(data["first_name"], self.user.first_name) + + def test_last_name_field_content(self): + data = self.serializer.data + self.assertEqual(data["last_name"], self.user.last_name) + + def test_email_field_content(self): + data = self.serializer.data + self.assertEqual(data["email"], self.user.email) def test_create(self): data = {"user": UserFactory.create().id, "is_lesgever": False} diff --git a/api/tests/serializers/test_groep.py b/api/tests/serializers/test_groep.py index 477960d1a..e56b3cd14 100644 --- a/api/tests/serializers/test_groep.py +++ b/api/tests/serializers/test_groep.py @@ -24,11 +24,12 @@ def test_studenten_field_content(self): self.assertEqual(data["studenten"], students) def test_create(self): + studenten = [GebruikerFactory.create(is_lesgever=False).user.id for _ in range(3)] + for student in studenten: + self.groep.project.vak.studenten.add(student) data = { "project": self.groep.project.project_id, - "studenten": [ - GebruikerFactory.create(is_lesgever=False).user.id for _ in range(3) - ], + "studenten": studenten, } serializer = GroepSerializer(data=data) self.assertTrue(serializer.is_valid()) @@ -41,6 +42,7 @@ def test_create(self): def test_create_invalid_user_already_in_a_group(self): student = GebruikerFactory.create(is_lesgever=False).user.id + self.groep.project.vak.studenten.add(student) data = { "project": self.groep.project.project_id, "studenten": [student], @@ -60,6 +62,16 @@ def test_create_invalid_user_already_in_a_group(self): newserializer = GroepSerializer(data=new_data) self.assertTrue(newserializer.is_valid()) self.assertRaises(ValidationError, newserializer.save, raise_exception=True) + + def test_create_invalid_user_not_in_vak(self): + student = GebruikerFactory.create(is_lesgever=False).user.id + data = { + "project": self.groep.project.project_id, + "studenten": [student], + } + serializer = GroepSerializer(data=data) + self.assertTrue(serializer.is_valid()) + self.assertRaises(ValidationError, serializer.save, raise_exception=True) def test_create_invalid_user_is_teacher(self): data = { @@ -74,8 +86,9 @@ def test_create_invalid_user_is_teacher(self): def test_update(self): data = self.serializer.data - self.assertEqual(len(data["studenten"]), 1) - data["studenten"].append(GebruikerFactory.create(is_lesgever=False).user.id) + student = GebruikerFactory.create(is_lesgever=False).user.id + self.groep.project.vak.studenten.add(student) + data["studenten"].append(student) serializer = GroepSerializer(instance=self.groep, data=data, partial=True) self.assertTrue(serializer.is_valid()) groep = serializer.save() @@ -87,6 +100,7 @@ def test_update_invalid_user_already_in_this_group(self): data = self.serializer.data self.assertEqual(len(data["studenten"]), 1) student = GebruikerFactory.create(is_lesgever=False).user.id + self.groep.project.vak.studenten.add(student) data["studenten"].append(student) serializer = GroepSerializer(instance=self.groep, data=data, partial=True) self.assertTrue(serializer.is_valid()) @@ -99,6 +113,20 @@ def test_update_invalid_user_already_in_this_group(self): serializer = GroepSerializer(instance=groep, data=new_data, partial=True) self.assertTrue(serializer.is_valid()) self.assertRaises(ValidationError, serializer.save, raise_exception=True) + + def test_update_invalid_user_not_in_vak(self): + data = self.serializer.data + serializer = GroepSerializer(instance=self.groep, data=data, partial=True) + self.assertTrue(serializer.is_valid()) + groep = serializer.save() + self.assertEqual( + [student.user.id for student in groep.studenten.all()], data["studenten"] + ) + new_data = self.serializer.data + new_data["studenten"].append(GebruikerFactory.create(is_lesgever=False).user.id) + serializer = GroepSerializer(instance=groep, data=new_data, partial=True) + self.assertTrue(serializer.is_valid()) + self.assertRaises(ValidationError, serializer.save, raise_exception=True) def test_validation_for_blank_items(self): serializer = GroepSerializer(data={"project": "", "studenten": []}) diff --git a/api/tests/serializers/test_indiening.py b/api/tests/serializers/test_indiening.py index 84bdf5622..b4372d70a 100644 --- a/api/tests/serializers/test_indiening.py +++ b/api/tests/serializers/test_indiening.py @@ -12,19 +12,19 @@ def setUp(self): def test_indiening_serializer_fields(self): data = self.serializer.data - self.assertEqual(set(data.keys()), set(["indiening_id", "groep", "tijdstip"])) + self.assertEqual(set(data.keys()), set(["indiening_id", "groep", "tijdstip", "status", "indiening_bestanden"])) def test_indiening_serializer_create(self): - # can't check tijdstip because it's auto_now_add groep = GroepFactory.create() data = {"groep": groep.groep_id} serializer = IndieningSerializer(data=data) + if not serializer.is_valid(): + print(serializer.errors) self.assertTrue(serializer.is_valid()) indiening = serializer.save() self.assertEqual(indiening.groep, groep) def test_indiening_serializer_update(self): - # can't check tijdstip because it's auto_now_add new_data = {"groep": self.indiening.groep.groep_id} serializer = IndieningSerializer( instance=self.indiening, data=new_data, partial=True diff --git a/api/tests/serializers/test_project.py b/api/tests/serializers/test_project.py index 5e3d105c9..92a5f691d 100644 --- a/api/tests/serializers/test_project.py +++ b/api/tests/serializers/test_project.py @@ -24,7 +24,10 @@ def test_contains_expected_fields(self): "opgave_bestand", "vak", "deadline", + "extra_deadline", "max_score", + "zichtbaar", + "gearchiveerd", ], ) @@ -54,6 +57,18 @@ def test_max_score_field_content(self): def test_deadline_field_content(self): data = self.serializer.data self.assertEqual(parse(data["deadline"]), self.project.deadline) + + def test_extra_deadline_field_content(self): + data = self.serializer.data + self.assertEqual(parse(data["extra_deadline"]), self.project.extra_deadline) + + def test_zichtbaar_field_content(self): + data = self.serializer.data + self.assertEqual(data["zichtbaar"], self.project.zichtbaar) + + def test_gearchiveerd_field_content(self): + data = self.serializer.data + self.assertEqual(data["gearchiveerd"], self.project.gearchiveerd) def test_validation_for_blank_items(self): serializer = ProjectSerializer( @@ -63,7 +78,10 @@ def test_validation_for_blank_items(self): "opgave_bestand": "", "vak": "", "deadline": "", + "extra_deadline": "", "max_score": "", + "zichtbaar": "", + "gearchiveerd": "", } ) self.assertRaises(ValidationError, serializer.is_valid, raise_exception=True) @@ -76,7 +94,10 @@ def test_create(self): "opgave_bestand": SimpleUploadedFile("file.txt", b"file_content"), "vak": vak, "deadline": self.serializer.data["deadline"], + "extra_deadline": self.serializer.data["extra_deadline"], "max_score": 20, + "zichtbaar": True, + "gearchiveerd": False, } serializer = ProjectSerializer(data=data) self.assertTrue(serializer.is_valid()) @@ -91,7 +112,27 @@ def test_create_invalid_deadline(self): "opgave_bestand": SimpleUploadedFile("file.txt", b"file_content"), "vak": vak, "deadline": datetime.now() - timedelta(days=1), + "extra_deadline": self.serializer.data["extra_deadline"], + "max_score": 20, + "zichtbaar": True, + "gearchiveerd": False, + } + serializer = ProjectSerializer(data=data) + self.assertTrue(serializer.is_valid()) + self.assertRaises(ValidationError, serializer.save, raise_exception=True) + + def test_create_invalid_extra_deadline(self): + vak = VakFactory.create().vak_id + data = { + "titel": "test project", + "beschrijving": "Dit is een test project.", + "opgave_bestand": SimpleUploadedFile("file.txt", b"file_content"), + "vak": vak, + "deadline": self.serializer.data["deadline"], + "extra_deadline": datetime.now() - timedelta(days=1), "max_score": 20, + "zichtbaar": True, + "gearchiveerd": False, } serializer = ProjectSerializer(data=data) self.assertTrue(serializer.is_valid()) @@ -104,7 +145,10 @@ def test_update(self): "opgave_bestand": SimpleUploadedFile("file.txt", b"file_content"), "vak": self.serializer.data["vak"], "deadline": self.serializer.data["deadline"], + "extra_deadline": self.serializer.data["extra_deadline"], "max_score": 20, + "zichtbaar": True, + "gearchiveerd": False, } serializer = ProjectSerializer(instance=self.project, data=data, partial=True) self.assertTrue(serializer.is_valid()) From 95220768babc30a09d7f500740dbcc8da0d62907 Mon Sep 17 00:00:00 2001 From: arallaer Date: Thu, 21 Mar 2024 17:09:29 +0100 Subject: [PATCH 038/268] views testen --- api/models/project.py | 2 +- api/serializers/project.py | 2 +- api/tests/views/test_indiening.py | 88 ------------------------------- api/tests/views/test_project.py | 20 ++++++- 4 files changed, 21 insertions(+), 91 deletions(-) diff --git a/api/models/project.py b/api/models/project.py index 1f9e208c6..1e52df646 100644 --- a/api/models/project.py +++ b/api/models/project.py @@ -47,7 +47,7 @@ class Project(models.Model): opgave_bestand = models.FileField(upload_to=upload_to) vak = models.ForeignKey(Vak, on_delete=models.CASCADE) deadline = models.DateTimeField(null=True, blank=True) - extra_deadline = models.DateTimeField(null=True, blank=True) + extra_deadline = models.DateTimeField(null=True, blank=True, default=None) max_score = models.IntegerField(default=20) zichtbaar = models.BooleanField(default=True, blank=True) gearchiveerd = models.BooleanField(default=False, blank=True) diff --git a/api/serializers/project.py b/api/serializers/project.py index 0243ebc5d..ae3cc3358 100644 --- a/api/serializers/project.py +++ b/api/serializers/project.py @@ -72,7 +72,7 @@ def validate_deadlines(deadline, extra_deadline): if deadline <= timezone.now(): raise serializers.ValidationError("Deadline moet in de toekomst liggen") - if extra_deadline <= deadline: + if extra_deadline is not None and extra_deadline <= deadline: raise serializers.ValidationError( "Extra deadline moet na de eerste deadline liggen" ) diff --git a/api/tests/views/test_indiening.py b/api/tests/views/test_indiening.py index 3c3d9a5db..32e0b9f34 100644 --- a/api/tests/views/test_indiening.py +++ b/api/tests/views/test_indiening.py @@ -104,91 +104,3 @@ def test_indiening_detail_delete_unauthorized(self): self.client.force_login(gebruiker.user) response = self.client.delete(self.url) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - -class IndieningBestandListViewTest(TestCase): - def setUp(self): - self.client = APIClient() - self.teacher = GebruikerFactory.create(is_lesgever=True) - self.student = GebruikerFactory.create(is_lesgever=False) - self.indiening_bestand1 = IndieningBestandFactory.create() - self.indiening_bestand2 = IndieningBestandFactory.create() - self.indiening_bestand2.indiening.groep.studenten.add(self.student) - self.client.force_login(self.teacher.user) - self.url = reverse("indiening_bestand_list") - - def test_indiening_bestand_list_get_as_teacher(self): - response = self.client.get(self.url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 2) - - def test_indiening_bestand_list_get_as_student(self): - self.client.force_login(self.student.user) - response = self.client.get(self.url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - self.assertEqual( - response.data[0]["indiening_bestand_id"], - self.indiening_bestand2.indiening_bestand_id, - ) - - def test_indiening_bestand_list_get_indiening(self): - response = self.client.get( - self.url, {"indiening": self.indiening_bestand1.indiening.indiening_id} - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - self.assertEqual( - response.data[0]["indiening_bestand_id"], - self.indiening_bestand1.indiening_bestand_id, - ) - - def test_indiening_bestand_list_get_invalid_indiening(self): - response = self.client.get(self.url, {"indiening": "indiening"}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - -class IndieningBestandDetailViewTest(TestCase): - def setUp(self): - self.client = APIClient() - self.teacher = GebruikerFactory.create(is_lesgever=True) - self.student = GebruikerFactory.create(is_lesgever=False) - self.indiening_bestand1 = IndieningBestandFactory.create() - self.indiening_bestand2 = IndieningBestandFactory.create() - self.indiening_bestand2.indiening.groep.studenten.add(self.student) - self.client.force_login(self.teacher.user) - self.url = reverse( - "indiening_bestand_detail", - kwargs={"id": self.indiening_bestand1.indiening_bestand_id}, - ) - - def test_indiening_bestand_detail_get_as_teacher(self): - response = self.client.get(self.url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.data["indiening_bestand_id"], - self.indiening_bestand1.indiening_bestand_id, - ) - - def test_indiening_bestand_detail_get_as_student(self): - self.client.force_login(self.student.user) - url = reverse( - "indiening_bestand_detail", - kwargs={"id": self.indiening_bestand2.indiening_bestand_id}, - ) - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.data["indiening_bestand_id"], - self.indiening_bestand2.indiening_bestand_id, - ) - - def test_indiening_bestand_detail_get_invalid(self): - url = reverse("indiening_bestand_detail", kwargs={"id": 69}) - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - def test_indiening_bestand_detail_get_unauthorized(self): - self.client.force_login(self.student.user) - response = self.client.get(self.url) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/api/tests/views/test_project.py b/api/tests/views/test_project.py index 1ed85342a..db523a2af 100644 --- a/api/tests/views/test_project.py +++ b/api/tests/views/test_project.py @@ -13,7 +13,7 @@ def setUp(self): self.teacher = GebruikerFactory.create(is_lesgever=True) self.student = GebruikerFactory.create(is_lesgever=False) self.project1 = ProjectFactory.create() - self.project2 = ProjectFactory.create() + self.project2 = ProjectFactory.create(zichtbaar=True, gearchiveerd=False) self.project2.vak.studenten.add(self.student) self.url = reverse("project_list") self.client = APIClient() @@ -49,7 +49,10 @@ def test_project_list_post(self): "opgave_bestand": SimpleUploadedFile("file.txt", b"file_content"), "vak": vak, "deadline": "2024-03-31T12:40:05.317980Z", + "extra_deadline": "", "max_score": 20, + "zichtbaar": "true", + "gearchiveerd": "false", } response = self.client.post(self.url, data, format="multipart") self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -63,7 +66,10 @@ def test_project_list_post_unauthorized(self): "opgave_bestand": SimpleUploadedFile("file.txt", b"file_content"), "vak": vak, "deadline": "2024-03-31T12:40:05.317980Z", + "max_score": 20, + "zichtbaar": "true", + "gearchiveerd": "false", } response = self.client.post(self.url, data, format="multipart") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) @@ -75,7 +81,10 @@ def test_project_list_post_invalid(self): "opgave_bestand": "bestand", "vak": "vak", "deadline": "2024-03-31T12:40:05.317980Z", + "extra_deadline": "2024-03-31T12:40:05.317980Z", "max_score": 20, + "zichtbaar": "true", + "gearchiveerd": "false", } response = self.client.post(self.url, data, format="multipart") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -120,7 +129,10 @@ def test_project_detail_put(self): "opgave_bestand": self.project.opgave_bestand, "vak": self.project.vak.vak_id, "deadline": self.project.deadline, + "extra_deadline": self.project.extra_deadline, "max_score": self.project.max_score, + "zichtbaar": self.project.zichtbaar, + "gearchiveerd": self.project.gearchiveerd, } response = self.client.put(self.url, new_data, format="multipart") self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -136,7 +148,10 @@ def test_project_detail_put_invalid(self): "opgave_bestand": "bestand", "vak": self.project.vak.vak_id, "deadline": self.project.deadline, + "extra_deadline": self.project.extra_deadline, "max_score": self.project.max_score, + "zichtbaar": self.project.zichtbaar, + "gearchiveerd": self.project.gearchiveerd, } response = self.client.put(self.url, new_data, format="multipart") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -151,7 +166,10 @@ def test_project_detail_put_unauthorized(self): "opgave_bestand": "bestand", "vak": self.project.vak.vak_id, "deadline": self.project.deadline, + "extra_deadline": self.project.extra_deadline, "max_score": self.project.max_score, + "zichtbaar": "true", + "gearchiveerd": "false", } response = self.client.put(self.url, new_data, format="multipart") self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) From 1b215d9ea06b81cb78495769e5bec98387b2c972 Mon Sep 17 00:00:00 2001 From: arallaer Date: Thu, 21 Mar 2024 17:18:29 +0100 Subject: [PATCH 039/268] linter --- api/serializers/groep.py | 2 +- api/tests/factories/project.py | 4 ++-- api/tests/models/test_indiening.py | 5 +++-- api/tests/models/test_project.py | 12 ++++++------ api/tests/serializers/test_gebruiker.py | 8 +++++--- api/tests/serializers/test_groep.py | 8 +++++--- api/tests/serializers/test_indiening.py | 5 ++++- api/tests/serializers/test_project.py | 8 ++++---- api/tests/views/test_indiening.py | 2 +- api/tests/views/test_project.py | 1 - 10 files changed, 31 insertions(+), 24 deletions(-) diff --git a/api/serializers/groep.py b/api/serializers/groep.py index c2c4715a2..632ac8694 100644 --- a/api/serializers/groep.py +++ b/api/serializers/groep.py @@ -74,7 +74,7 @@ def validate_students(students_data, project, current_group=None): groepen = Groep.objects.filter(project=project) if current_group is not None: groepen = groepen.exclude(groep_id=current_group.groep_id) - + student_counts = Counter(students_data) for student, count in student_counts.items(): if count > 1: diff --git a/api/tests/factories/project.py b/api/tests/factories/project.py index 39f49dde1..c7a4f642b 100644 --- a/api/tests/factories/project.py +++ b/api/tests/factories/project.py @@ -28,5 +28,5 @@ class Meta: ) ) max_score = factory.Faker("random_int", min=10, max=100) - zichtbaar = factory.Faker('boolean') - gearchiveerd = factory.Faker('boolean') + zichtbaar = factory.Faker("boolean") + gearchiveerd = factory.Faker("boolean") diff --git a/api/tests/models/test_indiening.py b/api/tests/models/test_indiening.py index 094c0f631..617488ab5 100644 --- a/api/tests/models/test_indiening.py +++ b/api/tests/models/test_indiening.py @@ -15,13 +15,14 @@ def test_groep(self): def test_tijdstip(self): self.assertIsNotNone(self.indiening.tijdstip) - + def test_status(self): self.assertIsNotNone(self.indiening.status) - + def test_indiening_bestanden(self): self.assertEqual(self.indiening.indiening_bestanden.count(), 1) + class IndieningBestandModelTest(TestCase): def setUp(self): self.indiening_bestand = IndieningBestandFactory.create( diff --git a/api/tests/models/test_project.py b/api/tests/models/test_project.py index 29ec4d61e..b98855373 100644 --- a/api/tests/models/test_project.py +++ b/api/tests/models/test_project.py @@ -14,18 +14,18 @@ def test_project_vak(self): def test_project_max_score(self): self.assertTrue(10 <= self.project.max_score <= 100) - + def test_project_zichtbaar(self): self.assertIsNotNone(self.project.zichtbaar) - + def test_project_gearchiveerd(self): self.assertIsNotNone(self.project.gearchiveerd) - + def test_project_deadline(self): self.assertIsNotNone(self.project.deadline) - + def test_project_extra_deadline(self): self.assertIsNotNone(self.project.extra_deadline) - + def test_project_opgave_bestand(self): - self.assertEqual(self.project.opgave_bestand.read(), b"file content") + self.assertEqual(self.project.opgave_bestand.read(), b"file content") diff --git a/api/tests/serializers/test_gebruiker.py b/api/tests/serializers/test_gebruiker.py index fa71f7b8c..cd0035341 100644 --- a/api/tests/serializers/test_gebruiker.py +++ b/api/tests/serializers/test_gebruiker.py @@ -12,7 +12,9 @@ def setUp(self): def test_contains_expected_fields(self): data = self.serializer.data - self.assertCountEqual(data.keys(), ["user", "is_lesgever", "first_name", "last_name", "email"]) + self.assertCountEqual( + data.keys(), ["user", "is_lesgever", "first_name", "last_name", "email"] + ) def test_user_field_content(self): data = self.serializer.data @@ -21,11 +23,11 @@ def test_user_field_content(self): def test_is_lesgever_field_content(self): data = self.serializer.data self.assertEqual(data["is_lesgever"], self.gebruiker.is_lesgever) - + def test_first_name_field_content(self): data = self.serializer.data self.assertEqual(data["first_name"], self.user.first_name) - + def test_last_name_field_content(self): data = self.serializer.data self.assertEqual(data["last_name"], self.user.last_name) diff --git a/api/tests/serializers/test_groep.py b/api/tests/serializers/test_groep.py index e56b3cd14..c81669af3 100644 --- a/api/tests/serializers/test_groep.py +++ b/api/tests/serializers/test_groep.py @@ -24,7 +24,9 @@ def test_studenten_field_content(self): self.assertEqual(data["studenten"], students) def test_create(self): - studenten = [GebruikerFactory.create(is_lesgever=False).user.id for _ in range(3)] + studenten = [ + GebruikerFactory.create(is_lesgever=False).user.id for _ in range(3) + ] for student in studenten: self.groep.project.vak.studenten.add(student) data = { @@ -62,7 +64,7 @@ def test_create_invalid_user_already_in_a_group(self): newserializer = GroepSerializer(data=new_data) self.assertTrue(newserializer.is_valid()) self.assertRaises(ValidationError, newserializer.save, raise_exception=True) - + def test_create_invalid_user_not_in_vak(self): student = GebruikerFactory.create(is_lesgever=False).user.id data = { @@ -113,7 +115,7 @@ def test_update_invalid_user_already_in_this_group(self): serializer = GroepSerializer(instance=groep, data=new_data, partial=True) self.assertTrue(serializer.is_valid()) self.assertRaises(ValidationError, serializer.save, raise_exception=True) - + def test_update_invalid_user_not_in_vak(self): data = self.serializer.data serializer = GroepSerializer(instance=self.groep, data=data, partial=True) diff --git a/api/tests/serializers/test_indiening.py b/api/tests/serializers/test_indiening.py index b4372d70a..a8a3dc42c 100644 --- a/api/tests/serializers/test_indiening.py +++ b/api/tests/serializers/test_indiening.py @@ -12,7 +12,10 @@ def setUp(self): def test_indiening_serializer_fields(self): data = self.serializer.data - self.assertEqual(set(data.keys()), set(["indiening_id", "groep", "tijdstip", "status", "indiening_bestanden"])) + self.assertEqual( + set(data.keys()), + set(["indiening_id", "groep", "tijdstip", "status", "indiening_bestanden"]), + ) def test_indiening_serializer_create(self): groep = GroepFactory.create() diff --git a/api/tests/serializers/test_project.py b/api/tests/serializers/test_project.py index 92a5f691d..940013095 100644 --- a/api/tests/serializers/test_project.py +++ b/api/tests/serializers/test_project.py @@ -57,7 +57,7 @@ def test_max_score_field_content(self): def test_deadline_field_content(self): data = self.serializer.data self.assertEqual(parse(data["deadline"]), self.project.deadline) - + def test_extra_deadline_field_content(self): data = self.serializer.data self.assertEqual(parse(data["extra_deadline"]), self.project.extra_deadline) @@ -65,7 +65,7 @@ def test_extra_deadline_field_content(self): def test_zichtbaar_field_content(self): data = self.serializer.data self.assertEqual(data["zichtbaar"], self.project.zichtbaar) - + def test_gearchiveerd_field_content(self): data = self.serializer.data self.assertEqual(data["gearchiveerd"], self.project.gearchiveerd) @@ -78,7 +78,7 @@ def test_validation_for_blank_items(self): "opgave_bestand": "", "vak": "", "deadline": "", - "extra_deadline": "", + "extra_deadline": "", "max_score": "", "zichtbaar": "", "gearchiveerd": "", @@ -120,7 +120,7 @@ def test_create_invalid_deadline(self): serializer = ProjectSerializer(data=data) self.assertTrue(serializer.is_valid()) self.assertRaises(ValidationError, serializer.save, raise_exception=True) - + def test_create_invalid_extra_deadline(self): vak = VakFactory.create().vak_id data = { diff --git a/api/tests/views/test_indiening.py b/api/tests/views/test_indiening.py index 32e0b9f34..a036b63f6 100644 --- a/api/tests/views/test_indiening.py +++ b/api/tests/views/test_indiening.py @@ -1,5 +1,5 @@ from django.test import TestCase -from api.tests.factories.indiening import IndieningFactory, IndieningBestandFactory +from api.tests.factories.indiening import IndieningFactory from api.tests.factories.gebruiker import GebruikerFactory from rest_framework.test import APIClient from django.urls import reverse diff --git a/api/tests/views/test_project.py b/api/tests/views/test_project.py index db523a2af..840e07247 100644 --- a/api/tests/views/test_project.py +++ b/api/tests/views/test_project.py @@ -66,7 +66,6 @@ def test_project_list_post_unauthorized(self): "opgave_bestand": SimpleUploadedFile("file.txt", b"file_content"), "vak": vak, "deadline": "2024-03-31T12:40:05.317980Z", - "max_score": 20, "zichtbaar": "true", "gearchiveerd": "false", From 0e13244c96c884f25f7852b8ea66fa68c7143c2b Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Fri, 22 Mar 2024 16:34:30 +0100 Subject: [PATCH 040/268] logica allemaal in orde, alle state kan veranderd worden buiten het toevoegen van restricties --- frontend/frontend/src/i18n/en.ts | 3 + frontend/frontend/src/i18n/nl.ts | 3 + .../AddChangeAssignmentPage.tsx | 90 ++++++++++++++----- 3 files changed, 76 insertions(+), 20 deletions(-) diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index 2589d03da..901b053f5 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -24,6 +24,9 @@ const english = { submit: "Submit", cancel: "Cancel", add_restriction: "Add restriction", + is_required: "is required", + name: "Name", + descriptionName: "Description", }; export default english; \ No newline at end of file diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts index 2fb24ab32..2e4ed37ec 100644 --- a/frontend/frontend/src/i18n/nl.ts +++ b/frontend/frontend/src/i18n/nl.ts @@ -24,6 +24,9 @@ const dutch = { submit: "Bevestig", cancel: "Annuleer", add_restriction: "Voeg restrictie toe", + is_required: "is verplicht", + name: "Naam", + descriptionName: "Beschrijving", }; export default dutch; \ No newline at end of file diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx index f1c18c31d..eebc9796c 100644 --- a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx +++ b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx @@ -1,6 +1,6 @@ import {Box, Button, Card, Divider, IconButton, ListItem, Stack, TextField, Tooltip, Typography} from "@mui/material"; import {Header} from "../../components/Header.tsx"; -import {ChangeEvent, useEffect, useState} from "react"; +import {ChangeEvent, FormEvent, useEffect, useState} from "react"; import {Dayjs} from "dayjs"; import {t} from "i18next"; import {DateTimePicker, LocalizationProvider, renderTimeViewClock} from "@mui/x-date-pickers"; @@ -17,8 +17,7 @@ import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; import SaveIcon from '@mui/icons-material/Save'; //TODO: fix api integration -//TODO: add logic for all form state manipulations - +//TODO: add restriction functionality /** * This page is used to add or change an assignment. * The page should only be accessible to teachers of the course. @@ -36,7 +35,7 @@ import SaveIcon from '@mui/icons-material/Save'; interface assignment { title: string, description: string, - assignmentFile: File, + assignmentFile: File | null, dueDate: Dayjs, restrictions: restriction[], groups: boolean, @@ -48,6 +47,12 @@ interface restriction { value: string, } +interface errorChecks { + title: boolean, + description: boolean, + dueDate: boolean, +} + export function AddChangeAssignmentPage() { // State for the different fields of the assignment const [title, setTitle] = useState(""); @@ -57,6 +62,11 @@ export function AddChangeAssignmentPage() { const [groups, setGroups] = useState(false); const [visible, setVisible] = useState(false); const [assignmentFile, setAssignmentFile] = useState(); + const [assignmentErrors, setAssignmentErrors] = useState({ + title: false, + description: false, + dueDate: false + }); /** * Function to upload the details of the assignment through a text file @@ -73,6 +83,34 @@ export function AddChangeAssignmentPage() { } + const removeRestriction = (index: number) => { + setRestrictions(restrictions.filter((_, i) => i !== index)); + } + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + setAssignmentErrors({title: title === "", description: description === "", dueDate: dueDate === null}); + if (title === "" || description === "" || dueDate === null) { + return; + } + let optionalFile: File | null = null; + if (assignmentFile !== undefined) { + optionalFile = assignmentFile; + } + const newAssignment: assignment = { + title: title, + description: description, + assignmentFile: optionalFile, + dueDate: dueDate, + restrictions: restrictions, + groups: groups, + visible: visible, + } + //TODO: send data to api + alert("Form Submitted"); + console.info('Form submitted', title, description, dueDate, restrictions, groups, visible, assignmentFile) + } + useEffect(() => { //TODO: fetch data from api console.log(assignmentFile); @@ -86,7 +124,7 @@ export function AddChangeAssignmentPage() { <>
- + - + {t('assignmentName')} - setTitle(event.target.value)}/> Deadline: - setDueDate(newValue)}/> @@ -133,6 +179,8 @@ export function AddChangeAssignmentPage() { setDescription(event.target.value)} fullWidth + error={assignmentErrors.description} + helperText={assignmentErrors.description ? t("descriptionName") + " " + t('is_required') : ""} sx={{overflowY: 'auto', maxHeight: '25svh'}}/> @@ -159,7 +207,8 @@ export function AddChangeAssignmentPage() { alignItems={'center'} gap={1}> {restriction.value} - + removeRestriction(index)}> @@ -174,12 +223,13 @@ export function AddChangeAssignmentPage() { - + - - + From 6e54c6120452ada2efd0fa9f4e17497f98d85e05 Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Fri, 22 Mar 2024 23:02:59 +0100 Subject: [PATCH 041/268] toevoegen van restricties wordt geregeld via popup, vrij complexe state maar zou moeten werken --- frontend/frontend/src/i18n/en.ts | 9 + frontend/frontend/src/i18n/nl.ts | 8 + .../AddChangeAssignmentPage.tsx | 34 +++- .../RestrictionPopup.tsx | 162 ++++++++++++++++++ 4 files changed, 205 insertions(+), 8 deletions(-) create mode 100644 frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index 901b053f5..fc434ddaf 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -27,6 +27,15 @@ const english = { is_required: "is required", name: "Name", descriptionName: "Description", + restrictionType: "Restriction type", + upload_container: "Upload a .tar file", + dockerfile: "Dockerfile", + restrictiontype: "Restriction type", + maxSize: "Max file size", + fileType: "Allowed file types", + fileSize: "Max file size", + allowed_file_types: "Allowed file types", + }; export default english; \ No newline at end of file diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts index 2e4ed37ec..9f457cb6c 100644 --- a/frontend/frontend/src/i18n/nl.ts +++ b/frontend/frontend/src/i18n/nl.ts @@ -27,6 +27,14 @@ const dutch = { is_required: "is verplicht", name: "Naam", descriptionName: "Beschrijving", + restrictionType: "Restrictietype", + upload_container: "Upload een .tar bestand", + dockerfile: "Docker bestand (Dockerfile)", + restrictiontype: "Restrictietype", + maxSize: "Maximale bestandsgrootte", + fileType: "Toegestane bestandstypes", + fileSize: "Maximale bestandsgrootte", + allowed_file_types: "Toegestane bestandstypes", }; export default dutch; \ No newline at end of file diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx index eebc9796c..f23631334 100644 --- a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx +++ b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx @@ -15,6 +15,7 @@ import VisibilityIcon from "@mui/icons-material/Visibility"; import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; import SaveIcon from '@mui/icons-material/Save'; +import RestrictionPopup, {restrictionType} from "./RestrictionPopup.tsx"; //TODO: fix api integration //TODO: add restriction functionality @@ -42,9 +43,9 @@ interface assignment { visible: boolean, } -interface restriction { +export interface restriction { type: string, - value: string, + value: string[] | number | File | undefined, } interface errorChecks { @@ -67,6 +68,13 @@ export function AddChangeAssignmentPage() { description: false, dueDate: false }); + const [open, setOpen] = useState(false); + const [type, setType] = useState('dockerTest'); + const [dockerfile, setDockerFile] = useState(); + const [allowedFileTypes, setAllowedFileTypes] = useState([]); + const [maxSize, setMaxSize] = useState(); + const [restriction, setRestriction] = useState({type: '', value: undefined}); + /** * Function to upload the details of the assignment through a text file @@ -80,7 +88,7 @@ export function AddChangeAssignmentPage() { }; const handleAddRestriction = () => { - + setOpen(true); } const removeRestriction = (index: number) => { @@ -114,12 +122,15 @@ export function AddChangeAssignmentPage() { useEffect(() => { //TODO: fetch data from api console.log(assignmentFile); - setRestrictions([{type: "test", value: "test"}, {type: "test2", value: "test2"}, { - type: "test3", - value: "test3" - }, {type: "test4", value: "test4"}]) }, [assignmentFile]); + useEffect(() => { + if (restriction.type === '' || restriction.value === undefined) { + return; + } + setRestrictions(prevRestrictions => [...prevRestrictions, restriction]); + }, [restriction]); + return ( <> @@ -206,7 +217,8 @@ export function AddChangeAssignmentPage() { {restriction.value} + variant={"body1"}>{restriction.value instanceof File ? restriction.value.name : restriction.value instanceof Array ? restriction.value.join(', ') : typeof restriction.value === 'number' ? restriction.value.toString() + 'mb' : '' + } removeRestriction(index)}> + ); diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx b/frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx new file mode 100644 index 000000000..36b6b5383 --- /dev/null +++ b/frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx @@ -0,0 +1,162 @@ +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import {ChangeEvent, FormEvent} from "react"; +import {t} from "i18next"; +import {Autocomplete, Box, IconButton, InputAdornment, MenuItem, Select, TextField, Typography} from "@mui/material"; +import CancelIcon from '@mui/icons-material/Cancel'; +import CheckIcon from '@mui/icons-material/Check'; +import FileUploadButton from "../../components/FileUploadButton"; +import {restriction} from './AddChangeAssignmentPage.tsx' + +/** + * Component for adding restrictions to an assignment + * @param open - boolean for opening the dialog + * @param setOpen - function for setting the open state + * @param type - type of restriction + * @param setType - function for setting the type of restriction + * @param dockerfile - dockerfile for dockerTest restriction + * @param setDockerFile - function for setting the dockerfile + * @param allowedFileTypes - allowed file types for fileType restriction + * @param setAllowedFileTypes - function for setting the allowed file types + * @param maxSize - maximum file size for fileSize restriction + * @param setMaxSize - function for setting the maximum file size + */ + +export type restrictionType = 'dockerTest' | 'fileSize' | 'fileType'; + +interface RestrictionPopupProps { + open: boolean; + setOpen: (open: boolean) => void; + type: restrictionType; + setType: (type: restrictionType) => void; + dockerfile?: File; + setDockerFile?: (dockerfile: File) => void; + allowedFileTypes?: string[]; + setAllowedFileTypes?: (allowedFileTypes: string[]) => void; + maxSize?: number; + setMaxSize?: (maxSize: number) => void; + setRestriction: (restriction: restriction) => void; + +} + +export default function RestrictionPopup({ + open, + setOpen, + type, + setType, + dockerfile, + setDockerFile, + allowedFileTypes, + setAllowedFileTypes, + maxSize, + setMaxSize, + setRestriction + }: RestrictionPopupProps) { + + const handleClose = () => { + setOpen(false); + }; + + const setFile = (event: ChangeEvent) => { + if (event.target.files) { + if (setDockerFile) { + setDockerFile(event.target.files[0]); + } + console.log(dockerfile?.name); + } + }; + + return ( + <> + ) => { + event.preventDefault() + let restriction: restriction + if (type === 'dockerTest') { + restriction = {type, value: dockerfile} + } else if (type === 'fileSize') { + restriction = {type: type, value: maxSize} + } else { + restriction = {type: type, value: allowedFileTypes} + } + setRestriction(restriction) + handleClose(); + }, + }} + > + {t('add_restriction')} + + + + + {t('restrictiontype')} + + + {type === 'dockerTest' && (<> + {t('dockerfile')} + + )} + {type === 'fileSize' && (<> + {t('maxSize')} + mb, + }} + type={'number'} + value={maxSize} + onChange={(event) => setMaxSize ? setMaxSize(parseInt(event.target.value)) : undefined} + /> + )} + {type === 'fileType' && (<> + {t('allowed_file_types')} + option} + defaultValue={allowedFileTypes} + freeSolo + filterSelectedOptions + limitTags={2} + onChange={(_, value) => setAllowedFileTypes ? setAllowedFileTypes(value) : undefined} + renderInput={(params) => ( + + )} + /> + )} + + + + + + + + + + ); +} \ No newline at end of file From ba2104cb21144f91e5254cb10d6b392c53be6a45 Mon Sep 17 00:00:00 2001 From: lgdtimtou Date: Sat, 23 Mar 2024 14:55:06 +0100 Subject: [PATCH 042/268] Restrictie model aangemaakt + extra validaties bij andere serializers zodat belangerijke attributen niet worden aangepast --- api/models/restrictie.py | 30 ++++++++++++++++++++++++++++ api/serializers/groep.py | 10 ++++++++++ api/serializers/project.py | 11 +++++++++++ api/serializers/restrictie.py | 37 +++++++++++++++++++++++++++++++++++ api/serializers/score.py | 17 +++++----------- 5 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 api/models/restrictie.py create mode 100644 api/serializers/restrictie.py diff --git a/api/models/restrictie.py b/api/models/restrictie.py new file mode 100644 index 000000000..8e79289eb --- /dev/null +++ b/api/models/restrictie.py @@ -0,0 +1,30 @@ +from django.db import models +from api.models.project import Project + +def upload_to(instance, filename): + """ + Functie om het pad te genereren waar het opgavebestand wordt opgeslagen. + + Args: + instance: De huidige instantie van het model. + filename (str): De oorspronkelijke bestandsnaam. + + Returns: + str: Het pad waar het opgavebestand moet worden opgeslagen. + """ + project_id = instance.project.project_id + return f"data/restricties/project_{project_id}/{filename}" + + +class Restrictie(models.Model): + """ + TODO + """ + + restrictie_id = models.AutoField(primary_key=True) + project = models.ForeignKey(Project, on_delete=models.CASCADE) + restrictie_script = models.FileField(upload_to=upload_to) + moet_slagen = models.BooleanField(default=False, blank=True) + + def __str__(self): + return self.project.naam + ', restrictie: ' + self.restrictie_script \ No newline at end of file diff --git a/api/serializers/groep.py b/api/serializers/groep.py index af8f84cfd..d09caa66c 100644 --- a/api/serializers/groep.py +++ b/api/serializers/groep.py @@ -50,12 +50,22 @@ def update(self, instance, validated_data): validate_students( students_data, validated_data["project"], current_group=instance ) + new_project = validated_data.get('project') + validate_project(instance, new_project) + super().update(instance=instance, validated_data=validated_data) instance.studenten.set(students_data) instance.save() return instance +def validate_project(instance, new_project): + """ + TODO + """ + + if instance.project != new_project: + raise serializers.ValidationError('Het project van een groep kan niet aangepast worden') def validate_students(students_data, project, current_group=None): """ diff --git a/api/serializers/project.py b/api/serializers/project.py index 0243ebc5d..475e2a542 100644 --- a/api/serializers/project.py +++ b/api/serializers/project.py @@ -52,6 +52,9 @@ def update(self, instance, validated_data): extra_deadline = validated_data.pop("extra_deadline") validate_deadlines(deadline, extra_deadline) + new_vak = validated_data.get('vak') + validate_vak(instance, new_vak) + super().update(instance=instance, validated_data=validated_data) instance.deadline = deadline instance.extra_deadline = extra_deadline @@ -76,3 +79,11 @@ def validate_deadlines(deadline, extra_deadline): raise serializers.ValidationError( "Extra deadline moet na de eerste deadline liggen" ) + +def validate_vak(instance, new_vak): + """ + TODO + """ + + if instance.vak != new_vak: + raise serializers.ValidationError('Het vak van een project kan niet aangepast worden') \ No newline at end of file diff --git a/api/serializers/restrictie.py b/api/serializers/restrictie.py new file mode 100644 index 000000000..35a9e5a0d --- /dev/null +++ b/api/serializers/restrictie.py @@ -0,0 +1,37 @@ +from rest_framework import serializers +from api.models.restrictie import Restrictie + + +class ProjectSerializer(serializers.ModelSerializer): + """ + TODO + """ + + class Meta: + model = Restrictie + fields = "__all__" + + def update(self, instance, validated_data): + """ + Args: + instance (Project): Het project dat moet worden bijgewerkt. + validated_data (dict): Gevalideerde gegevens over het project. + + Returns: + Project: Het bijgewerkte project. + """ + project = validated_data.get('project') + validate_project(instance, project) + + super().update(instance=instance, validated_data=validated_data) + instance.save() + return instance + + +def validate_project(instance, new_project): + """ + TODO + """ + + if instance.project != new_project: + raise serializers.ValidationError('Het project van een restrictie kan niet aangepast worden') diff --git a/api/serializers/score.py b/api/serializers/score.py index 72eff488e..55f23ed4c 100644 --- a/api/serializers/score.py +++ b/api/serializers/score.py @@ -45,7 +45,7 @@ def update(self, instance, validated_data): Score: De bijgewerkte score. """ validate_score(validated_data) - validate_indiening(instance, validated_data) + validate_indiening(instance, validated_data.get('indiening')) super().update(instance=instance, validated_data=validated_data) instance.save() return instance @@ -68,16 +68,9 @@ def validate_score(data): ) -def validate_indiening(instance, data): +def validate_indiening(instance, new_indiening): """ - Controleert of de indiening_id niet wordt aangepast. - - Args: - instance (Score): De score die moet worden bijgewerkt. - data (dict): Gevalideerde gegevens over de score. - - Raises: - serializers.ValidationError: Als de indiening_id wordt aangepast. + TODO """ - if instance.indiening != data.get("indiening"): - raise serializers.ValidationError("indiening_id kan niet aangepast worden") + if instance.indiening != new_indiening: + raise serializers.ValidationError("De indiening van een score kan niet aangepast worden") From b00214079554bd5d33b91088528b5dd5b4978c10 Mon Sep 17 00:00:00 2001 From: lgdtimtou Date: Sat, 23 Mar 2024 15:40:12 +0100 Subject: [PATCH 043/268] Restrictie serializer en view afgewerkt + een validatiecheck toegevoegd die checkt dat de restrictie scripts een shell of pythons script zijn --- api/admin.py | 2 + api/models/project.py | 1 - api/models/restrictie.py | 4 +- api/serializers/project.py | 8 +++- api/serializers/restrictie.py | 30 ++++++++++----- api/urls.py | 3 ++ api/views/restrictie.py | 72 +++++++++++++++++++++++++++++++++++ 7 files changed, 107 insertions(+), 13 deletions(-) create mode 100644 api/views/restrictie.py diff --git a/api/admin.py b/api/admin.py index d59334795..e326c7e85 100644 --- a/api/admin.py +++ b/api/admin.py @@ -5,6 +5,7 @@ from api.models.project import Project from api.models.indiening import Indiening, IndieningBestand from api.models.score import Score +from api.models.restrictie import Restrictie admin.site.register(Gebruiker) admin.site.register(Vak) @@ -13,3 +14,4 @@ admin.site.register(Indiening) admin.site.register(Score) admin.site.register(IndieningBestand) +admin.site.register(Restrictie) diff --git a/api/models/project.py b/api/models/project.py index 1f9e208c6..ac7edbb0b 100644 --- a/api/models/project.py +++ b/api/models/project.py @@ -51,7 +51,6 @@ class Project(models.Model): max_score = models.IntegerField(default=20) zichtbaar = models.BooleanField(default=True, blank=True) gearchiveerd = models.BooleanField(default=False, blank=True) - # indiening restricties def __str__(self): return self.titel diff --git a/api/models/restrictie.py b/api/models/restrictie.py index 8e79289eb..3bc66b12c 100644 --- a/api/models/restrictie.py +++ b/api/models/restrictie.py @@ -23,8 +23,8 @@ class Restrictie(models.Model): restrictie_id = models.AutoField(primary_key=True) project = models.ForeignKey(Project, on_delete=models.CASCADE) - restrictie_script = models.FileField(upload_to=upload_to) + script = models.FileField(upload_to=upload_to) moet_slagen = models.BooleanField(default=False, blank=True) def __str__(self): - return self.project.naam + ', restrictie: ' + self.restrictie_script \ No newline at end of file + return self.project.titel + ', restrictie: ' + str(self.script) \ No newline at end of file diff --git a/api/serializers/project.py b/api/serializers/project.py index 475e2a542..2026560a2 100644 --- a/api/serializers/project.py +++ b/api/serializers/project.py @@ -1,6 +1,7 @@ from rest_framework import serializers from api.models.project import Project from django.utils import timezone +from api.serializers.restrictie import RestrictieSerializer class ProjectSerializer(serializers.ModelSerializer): @@ -17,9 +18,14 @@ class ProjectSerializer(serializers.ModelSerializer): update(self, instance, validated_data): Werkt een bestaand project bij in de database. """ + restricties = RestrictieSerializer(many=True, read_only=True) + class Meta: model = Project - fields = "__all__" + fields = [ + 'project_id', 'titel', 'beschrijving', 'opgave_bestand', 'vak', 'deadline', + 'extra_deadline', 'max_score', 'zichtbaar', 'gearchiveerd', 'restricties' + ] def create(self, validated_data): """ diff --git a/api/serializers/restrictie.py b/api/serializers/restrictie.py index 35a9e5a0d..368ff631c 100644 --- a/api/serializers/restrictie.py +++ b/api/serializers/restrictie.py @@ -2,7 +2,7 @@ from api.models.restrictie import Restrictie -class ProjectSerializer(serializers.ModelSerializer): +class RestrictieSerializer(serializers.ModelSerializer): """ TODO """ @@ -11,22 +11,34 @@ class Meta: model = Restrictie fields = "__all__" - def update(self, instance, validated_data): + + def create(self, validated_data): + """ + TODO """ - Args: - instance (Project): Het project dat moet worden bijgewerkt. - validated_data (dict): Gevalideerde gegevens over het project. - Returns: - Project: Het bijgewerkte project. + validate_script(validated_data.get('script')) + return Restrictie.objects.create(**validated_data) + + + def update(self, instance, validated_data): + """ + TODO """ - project = validated_data.get('project') - validate_project(instance, project) + validate_project(instance, validated_data.get('project')) + validate_script(instance, validated_data.get('script')) + super().update(instance=instance, validated_data=validated_data) instance.save() return instance +def validate_script(new_script): + + if not str(new_script).endswith('.sh') or not str(new_script).endswith('.py'): + raise serializers.ValidationError('Het restrictie script moet een Python of Shell script zijn') + + def validate_project(instance, new_project): """ diff --git a/api/urls.py b/api/urls.py index 9173c6230..087ebe03e 100644 --- a/api/urls.py +++ b/api/urls.py @@ -26,6 +26,7 @@ from .views.indiening import indiening_list, indiening_detail from .views.score import score_list, score_detail from .views.groep import groep_list, groep_detail +from .views.restrictie import restrictie_list, restrictie_detail urlpatterns = [ path(".well-known/microsoft-identity-association.json", microsoft_association), @@ -45,6 +46,8 @@ path("api/scores//", score_detail, name="score_detail"), path("api/groepen/", groep_list, name="groep_list"), path("api/groepen//", groep_detail, name="groep_detail"), + path("api/restricties/", restrictie_list, name="restrictie_list"), + path("api/restricties//", restrictie_detail, name="restrictie_detail"), ] urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/api/views/restrictie.py b/api/views/restrictie.py new file mode 100644 index 000000000..eb91d77b2 --- /dev/null +++ b/api/views/restrictie.py @@ -0,0 +1,72 @@ +from rest_framework.decorators import api_view +from rest_framework.response import Response +from rest_framework import status + +from api.models.restrictie import Restrictie +from api.serializers.restrictie import RestrictieSerializer +from api.utils import is_lesgever + + +@api_view(["GET", "POST"]) +def restrictie_list(request, format=None): + """ + TODO + """ + if is_lesgever(request.user): + if request.method == "GET": + restricties = Restrictie.objects.all() + + if "project" in request.GET: + try: + project = eval(request.GET.get("project")) + restricties = restricties.filter(project = project) + except NameError: + return Response(status=status.HTTP_400_BAD_REQUEST) + + if "moet_slagen" in request.GET and request.GET.get("moet_slagen").lower() in [ + "true", + "false", + ]: + restricties = restricties.filter( + moet_slagen=(request.GET.get("moet_slagen").lower() == "true") + ) + + serializer = RestrictieSerializer(restricties, many=True) + return Response(serializer.data) + + elif request.method == "POST": + serializer = RestrictieSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + return Response(status=status.HTTP_403_FORBIDDEN) + + +@api_view(["GET", "PUT", "DELETE"]) +def restrictie_detail(request, id, format=None): + """ + TODO + """ + try: + restrictie = Restrictie.objects.get(pk=id) + except Restrictie.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + + if is_lesgever(request.user): + if request.method == "GET": + serializer = RestrictieSerializer(restrictie) + return Response(serializer.data) + + if request.method == "PUT": + serializer = RestrictieSerializer(restrictie, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + elif request.method == "DELETE": + restrictie.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + return Response(status=status.HTTP_403_FORBIDDEN) \ No newline at end of file From 3b2920a867148ee2eaa2903dc29ef46430755e1c Mon Sep 17 00:00:00 2001 From: Mathis De Witte Date: Sat, 23 Mar 2024 16:11:11 +0100 Subject: [PATCH 044/268] stijlregels --- api/models/restrictie.py | 3 ++- api/serializers/gebruiker.py | 12 ++++++++++-- api/serializers/groep.py | 2 ++ api/serializers/project.py | 5 +++-- api/serializers/restrictie.py | 6 +----- api/views/restrictie.py | 5 ++--- 6 files changed, 20 insertions(+), 13 deletions(-) diff --git a/api/models/restrictie.py b/api/models/restrictie.py index 3bc66b12c..1a37d33de 100644 --- a/api/models/restrictie.py +++ b/api/models/restrictie.py @@ -1,6 +1,7 @@ from django.db import models from api.models.project import Project + def upload_to(instance, filename): """ Functie om het pad te genereren waar het opgavebestand wordt opgeslagen. @@ -27,4 +28,4 @@ class Restrictie(models.Model): moet_slagen = models.BooleanField(default=False, blank=True) def __str__(self): - return self.project.titel + ', restrictie: ' + str(self.script) \ No newline at end of file + return self.project.titel + ', restrictie: ' + str(self.script) diff --git a/api/serializers/gebruiker.py b/api/serializers/gebruiker.py index 7dee6cdf4..161c68011 100644 --- a/api/serializers/gebruiker.py +++ b/api/serializers/gebruiker.py @@ -2,6 +2,7 @@ from api.models.gebruiker import Gebruiker from api.models.vak import Vak + class GebruikerSerializer(serializers.ModelSerializer): """ Serializer voor het serialiseren en deserialiseren van Gebruiker objecten. @@ -52,8 +53,15 @@ def update(self, instance, validated_data): instance.save() return instance + def validate_lesgever_change(instance): if instance.is_lesgever and Vak.objects.filter(lesgevers=instance): - raise serializers.ValidationError(f"De lesgever {instance} moet eerst verwijderd worden als lesgever in zijn huidige vakken") + raise serializers.ValidationError( + f"De lesgever {instance} moet eerst verwijderd worden \ + als lesgever in zijn huidige vakken" + ) elif not instance.is_lesgever and Vak.objects.filter(studenten=instance): - raise serializers.ValidationError(f"De student {instance} moet eerst verwijderd worden als student in zijn huidige vakken") \ No newline at end of file + raise serializers.ValidationError( + f"De student {instance} moet eerst verwijderd worden \ + als student in zijn huidige vakken" + ) diff --git a/api/serializers/groep.py b/api/serializers/groep.py index d09caa66c..e76953bf8 100644 --- a/api/serializers/groep.py +++ b/api/serializers/groep.py @@ -59,6 +59,7 @@ def update(self, instance, validated_data): return instance + def validate_project(instance, new_project): """ TODO @@ -67,6 +68,7 @@ def validate_project(instance, new_project): if instance.project != new_project: raise serializers.ValidationError('Het project van een groep kan niet aangepast worden') + def validate_students(students_data, project, current_group=None): """ Controleert of de opgegeven gebruikers studenten zijn en of ze al in een andere groep voor dit project zitten. diff --git a/api/serializers/project.py b/api/serializers/project.py index 2026560a2..ff5dfe9a7 100644 --- a/api/serializers/project.py +++ b/api/serializers/project.py @@ -23,7 +23,7 @@ class ProjectSerializer(serializers.ModelSerializer): class Meta: model = Project fields = [ - 'project_id', 'titel', 'beschrijving', 'opgave_bestand', 'vak', 'deadline', + 'project_id', 'titel', 'beschrijving', 'opgave_bestand', 'vak', 'deadline', 'extra_deadline', 'max_score', 'zichtbaar', 'gearchiveerd', 'restricties' ] @@ -86,10 +86,11 @@ def validate_deadlines(deadline, extra_deadline): "Extra deadline moet na de eerste deadline liggen" ) + def validate_vak(instance, new_vak): """ TODO """ if instance.vak != new_vak: - raise serializers.ValidationError('Het vak van een project kan niet aangepast worden') \ No newline at end of file + raise serializers.ValidationError('Het vak van een project kan niet aangepast worden') diff --git a/api/serializers/restrictie.py b/api/serializers/restrictie.py index 368ff631c..644c74f9c 100644 --- a/api/serializers/restrictie.py +++ b/api/serializers/restrictie.py @@ -11,7 +11,6 @@ class Meta: model = Restrictie fields = "__all__" - def create(self, validated_data): """ TODO @@ -20,7 +19,6 @@ def create(self, validated_data): validate_script(validated_data.get('script')) return Restrictie.objects.create(**validated_data) - def update(self, instance, validated_data): """ TODO @@ -28,18 +26,16 @@ def update(self, instance, validated_data): validate_project(instance, validated_data.get('project')) validate_script(instance, validated_data.get('script')) - super().update(instance=instance, validated_data=validated_data) instance.save() return instance + def validate_script(new_script): - if not str(new_script).endswith('.sh') or not str(new_script).endswith('.py'): raise serializers.ValidationError('Het restrictie script moet een Python of Shell script zijn') - def validate_project(instance, new_project): """ TODO diff --git a/api/views/restrictie.py b/api/views/restrictie.py index eb91d77b2..59a3bbb02 100644 --- a/api/views/restrictie.py +++ b/api/views/restrictie.py @@ -19,7 +19,7 @@ def restrictie_list(request, format=None): if "project" in request.GET: try: project = eval(request.GET.get("project")) - restricties = restricties.filter(project = project) + restricties = restricties.filter(project=project) except NameError: return Response(status=status.HTTP_400_BAD_REQUEST) @@ -40,7 +40,6 @@ def restrictie_list(request, format=None): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - return Response(status=status.HTTP_403_FORBIDDEN) @@ -69,4 +68,4 @@ def restrictie_detail(request, id, format=None): elif request.method == "DELETE": restrictie.delete() return Response(status=status.HTTP_204_NO_CONTENT) - return Response(status=status.HTTP_403_FORBIDDEN) \ No newline at end of file + return Response(status=status.HTTP_403_FORBIDDEN) From 70cc39fcb60fb3795c8df3f8ca4d41cae9a0c62e Mon Sep 17 00:00:00 2001 From: Mathis De Witte <49711425+mathis2003@users.noreply.github.com> Date: Sat, 23 Mar 2024 16:12:16 +0100 Subject: [PATCH 045/268] Update django.yml --- .github/workflows/django.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index ce56c1856..2226443ca 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -2,9 +2,9 @@ name: Django CI on: push: - branches: [ "develop", "tests" ] + branches: [ "develop", "tests", "restricties" ] pull_request: - branches: [ "develop", "tests" ] + branches: [ "develop", "tests", "restricties" ] jobs: build: From d997644c4bc01d7dcb88fd4e24d855d3d9190214 Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke Date: Sat, 23 Mar 2024 17:31:02 +0100 Subject: [PATCH 046/268] logica toevoegen aan subjects teacher page --- .../AssignmentListItemSubjectsPage.tsx | 75 ++++++++++++++----- .../src/pages/SubjectsPage/ProjectsView.tsx | 44 +++++++++-- 2 files changed, 94 insertions(+), 25 deletions(-) diff --git a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx index c05aafd80..c738fa8f4 100644 --- a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx +++ b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx @@ -5,6 +5,7 @@ import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined'; import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined'; import ArchiveOutlinedIcon from '@mui/icons-material/ArchiveOutlined'; import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; +import { useState } from "react"; interface AssignmentListItemSubjectsPageProps { projectName: string; @@ -12,7 +13,10 @@ interface AssignmentListItemSubjectsPageProps { submissions: number; score: number; isStudent: boolean; + archived: boolean; visible: boolean; + deleteEvent: () => void; + archiveEvent: () => void; } /* @@ -25,7 +29,7 @@ interface AssignmentListItemSubjectsPageProps { * @param isStudent: boolean - if the user is a student or a teacher */ -export function AssignmentListItemSubjectsPage({projectName, dueDate, submissions, score, isStudent, visible}:AssignmentListItemSubjectsPageProps) { +export function AssignmentListItemSubjectsPage({projectName, dueDate, submissions, score, isStudent, archived, visible, deleteEvent, archiveEvent}:AssignmentListItemSubjectsPageProps) { const navigate = useNavigate(); const handleProjectClick = () => { console.log("Project clicked"); @@ -60,23 +64,7 @@ export function AssignmentListItemSubjectsPage({projectName, dueDate, submission <> - - {visible? - - - - : - - - - } - - - - - - - + } @@ -84,4 +72,55 @@ export function AssignmentListItemSubjectsPage({projectName, dueDate, submission ); +} + +interface ButtonActionsProps { + archived: boolean; + startVisible: boolean; + deleteEvent: () => void; + archiveEvent: () => void; +} + +function ButtonActions({archived, startVisible, deleteEvent, archiveEvent}: ButtonActionsProps){ + const [visible, setVisible] = useState(startVisible); + + const handleIconClick = (e, icon: string) => { + e.stopPropagation(); + console.log(icon + " clicked"); + switch (icon) { + case 'visible': + setVisible(!visible); + break; + case 'archive': + if(!archived){ + archiveEvent(); + } + break; + case 'delete': + deleteEvent(); + break; + default: + break; + } + } + + return( + + {visible? + handleIconClick(e, "visible")} edge="end" aria-label="visible"> + + + : + handleIconClick(e, "visible")} edge="end" aria-label="not-visible"> + + + } + handleIconClick(e, "archive")} edge="end" aria-label="archive"> + + + handleIconClick(e, "delete")} edge="end" aria-label="delete"> + + + + ) } \ No newline at end of file diff --git a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx index 6387168ee..ed8f1ff76 100644 --- a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx @@ -2,6 +2,7 @@ import {Box, Typography} from "@mui/material"; import List from '@mui/material/List'; import {t} from "i18next"; import {AssignmentListItemSubjectsPage} from "../../components/AssignmentListItemSubjectsPage.tsx"; +import { useState } from "react"; interface ProjectsViewProps { courseId: string; @@ -31,7 +32,21 @@ interface Assignment { export function ProjectsView({courseId, isStudent, archived}: ProjectsViewProps) { const course = getCourse(courseId); - const assignments = course.assignments.map((assignmentId) => getAssignment(assignmentId)); + const assignmentsTemp = course.assignments.map((assignmentId) => getAssignment(assignmentId)); + + const [assignments, setAssignments] = useState(assignmentsTemp.filter((assignment) => !assignment.archived)); + const [archivedAssignments, setArchivedAssignments] = useState(assignmentsTemp.filter((assignment) => assignment.archived)); + + const deleteAssignment = (index: number) => { + setAssignments(assignments.filter((_, i) => i !== index)); + } + const deleteArchivedAssignment = (index: number) => { + setArchivedAssignments(archivedAssignments.filter((_, i) => i !== index)); + } + const archiveAssignment = (index: number) => { + setArchivedAssignments([...archivedAssignments, assignments[index]]); + deleteAssignment(index); + } return ( <> @@ -71,10 +86,25 @@ export function ProjectsView({courseId, isStudent, archived}: ProjectsViewProps) - {assignments.filter((assignment) => assignment.archived == archived) - .map((assignment) => ( - - ))} + {!archived? + assignments + .map((assignment, index) => ( + deleteAssignment(index)} + archiveEvent={() => archiveAssignment(index)}/> + )) + : + archivedAssignments + .map((assignment, index) => ( + deleteArchivedAssignment(index)} + archiveEvent={() => archiveAssignment(index)}/> + )) + } @@ -98,11 +128,11 @@ function getCourse(courseId: string): Course { function getAssignment(assignmentId: string): Assignment { return { id: assignmentId, - name: "assignmentName", + name: assignmentId, deadline: new Date(2022, 11, 17), submissions: 2, score: 10, visible: true, - archived: false, + archived: Number(assignmentId.slice(-1))%2==0, } } \ No newline at end of file From 5b376156df07c47103ed7302ade42fb8995214eb Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke Date: Sat, 23 Mar 2024 20:12:26 +0100 Subject: [PATCH 047/268] archive en delete state ge-lift van projectsview naar subjectsstudentpage en subjectsteacherpage --- .../AssignmentListItemSubjectsPage.tsx | 12 ++-- .../src/pages/SubjectsPage/ProjectsView.tsx | 64 +++-------------- .../SubjectsPage/SubjectsStudentPage.tsx | 71 ++++++++++++++++++- .../SubjectsPage/SubjectsTeacherPage.tsx | 70 +++++++++++++++++- 4 files changed, 151 insertions(+), 66 deletions(-) diff --git a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx index c738fa8f4..08f578929 100644 --- a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx +++ b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx @@ -92,9 +92,7 @@ function ButtonActions({archived, startVisible, deleteEvent, archiveEvent}: Butt setVisible(!visible); break; case 'archive': - if(!archived){ - archiveEvent(); - } + archiveEvent(); break; case 'delete': deleteEvent(); @@ -115,9 +113,11 @@ function ButtonActions({archived, startVisible, deleteEvent, archiveEvent}: Butt } - handleIconClick(e, "archive")} edge="end" aria-label="archive"> - - + {!archived && + handleIconClick(e, "archive")} edge="end" aria-label="archive"> + + + } handleIconClick(e, "delete")} edge="end" aria-label="delete"> diff --git a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx index ed8f1ff76..c25a0b610 100644 --- a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx @@ -2,22 +2,15 @@ import {Box, Typography} from "@mui/material"; import List from '@mui/material/List'; import {t} from "i18next"; import {AssignmentListItemSubjectsPage} from "../../components/AssignmentListItemSubjectsPage.tsx"; -import { useState } from "react"; interface ProjectsViewProps { - courseId: string; isStudent: boolean; archived: boolean; -} - -interface Course { - id: string; - name: string; - teacher: string; - students: string[]; - //list of assignment ids - assignments: string[]; - archived: boolean; + assignments: Assignment[]; + deleteAssignment: (index: number) => void; + archiveAssignment: (index: number) => void; + archivedAssignments: Assignment[]; + deleteArchivedAssignment: (index: number) => void; } interface Assignment { @@ -30,24 +23,7 @@ interface Assignment { archived: boolean; } -export function ProjectsView({courseId, isStudent, archived}: ProjectsViewProps) { - const course = getCourse(courseId); - const assignmentsTemp = course.assignments.map((assignmentId) => getAssignment(assignmentId)); - - const [assignments, setAssignments] = useState(assignmentsTemp.filter((assignment) => !assignment.archived)); - const [archivedAssignments, setArchivedAssignments] = useState(assignmentsTemp.filter((assignment) => assignment.archived)); - - const deleteAssignment = (index: number) => { - setAssignments(assignments.filter((_, i) => i !== index)); - } - const deleteArchivedAssignment = (index: number) => { - setArchivedAssignments(archivedAssignments.filter((_, i) => i !== index)); - } - const archiveAssignment = (index: number) => { - setArchivedAssignments([...archivedAssignments, assignments[index]]); - deleteAssignment(index); - } - +export function ProjectsView({isStudent, archived, assignments, deleteAssignment, archiveAssignment, archivedAssignments, deleteArchivedAssignment}: ProjectsViewProps) { return ( <> ( deleteAssignment(index)} archiveEvent={() => archiveAssignment(index)}/> )) @@ -100,7 +76,7 @@ export function ProjectsView({courseId, isStudent, archived}: ProjectsViewProps) .map((assignment, index) => ( deleteArchivedAssignment(index)} archiveEvent={() => archiveAssignment(index)}/> )) @@ -111,28 +87,4 @@ export function ProjectsView({courseId, isStudent, archived}: ProjectsViewProps) ); -} - -//TODO: use api to get data, for now use mock data -function getCourse(courseId: string): Course { - return { - id: courseId, - name: "courseName", - teacher: "teacher", - students: ["student1", "student2"], - archived: false, - assignments: ["assignment1", "assignment2", "assignment3", "assignment4", "assignment5", "assignment6", "assignment7", "assignment8", "assignment9"] - } -} - -function getAssignment(assignmentId: string): Assignment { - return { - id: assignmentId, - name: assignmentId, - deadline: new Date(2022, 11, 17), - submissions: 2, - score: 10, - visible: true, - archived: Number(assignmentId.slice(-1))%2==0, - } } \ No newline at end of file diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx index 64b78987b..55b87da4b 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx @@ -3,20 +3,87 @@ import { Box, Stack } from "@mui/material"; import TabSwitcher from "../../components/TabSwitcher.tsx"; import {ProjectsView} from "./ProjectsView.tsx"; import { useParams } from "react-router-dom"; +import { useState } from "react"; + +interface Course { + id: string; + name: string; + teacher: string; + students: string[]; + //list of assignment ids + assignments: string[]; + archived: boolean; +} + +interface Assignment { + id: string; + name: string; + deadline?: Date; + submissions: number; + score: number; + visible: boolean; + archived: boolean; +} export function SubjectsStudentPage() { let { courseId } = useParams(); courseId = String(courseId); + + const course = getCourse(courseId); + const assignmentsTemp = course.assignments.map((assignmentId) => getAssignment(assignmentId)); + + const [assignments, setAssignments] = useState(assignmentsTemp.filter((assignment) => !assignment.archived)); + const [archivedAssignments, setArchivedAssignments] = useState(assignmentsTemp.filter((assignment) => assignment.archived)); + + const deleteAssignment = (index: number) => { + setAssignments(assignments.filter((_, i) => i !== index)); + } + const deleteArchivedAssignment = (index: number) => { + setArchivedAssignments(archivedAssignments.filter((_, i) => i !== index)); + } + const archiveAssignment = (index: number) => { + setArchivedAssignments([...archivedAssignments, assignments[index]]); + deleteAssignment(index); + } + return ( <>
, - ]}/> + nodes={[, + ]}/> ); +} + +//TODO: use api to get data, for now use mock data +function getCourse(courseId: string): Course { + return { + id: courseId, + name: "courseName", + teacher: "teacher", + students: ["student1", "student2"], + archived: false, + assignments: ["assignment1", "assignment2", "assignment3", "assignment4", "assignment5", "assignment6", "assignment7", "assignment8", "assignment9"] + } +} + +function getAssignment(assignmentId: string): Assignment { + return { + id: assignmentId, + name: assignmentId, + deadline: new Date(2022, 11, 17), + submissions: 2, + score: 10, + visible: true, + archived: Number(assignmentId.slice(-1))%2==0, + } } \ No newline at end of file diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx index c1f19639a..a4dac6995 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx @@ -4,6 +4,27 @@ import TabSwitcher from "../../components/TabSwitcher.tsx"; import {ProjectsView} from "./ProjectsView.tsx"; import { useNavigate, useParams } from "react-router-dom"; import AddCircleIcon from '@mui/icons-material/AddCircle'; +import { useState } from "react"; + +interface Course { + id: string; + name: string; + teacher: string; + students: string[]; + //list of assignment ids + assignments: string[]; + archived: boolean; +} + +interface Assignment { + id: string; + name: string; + deadline?: Date; + submissions: number; + score: number; + visible: boolean; + archived: boolean; +} export function SubjectsTeacherPage() { let { courseId } = useParams(); @@ -16,14 +37,35 @@ export function SubjectsTeacherPage() { navigate("/add_change_assignment"); } + const course = getCourse(courseId); + const assignmentsTemp = course.assignments.map((assignmentId) => getAssignment(assignmentId)); + + const [assignments, setAssignments] = useState(assignmentsTemp.filter((assignment) => !assignment.archived)); + const [archivedAssignments, setArchivedAssignments] = useState(assignmentsTemp.filter((assignment) => assignment.archived)); + + const deleteAssignment = (index: number) => { + setAssignments(assignments.filter((_, i) => i !== index)); + } + const deleteArchivedAssignment = (index: number) => { + setArchivedAssignments(archivedAssignments.filter((_, i) => i !== index)); + } + const archiveAssignment = (index: number) => { + setArchivedAssignments([...archivedAssignments, assignments[index]]); + deleteAssignment(index); + } + return ( <>
, - ]}/> + nodes={[, + ]}/> @@ -33,4 +75,28 @@ export function SubjectsTeacherPage() { ); +} + +//TODO: use api to get data, for now use mock data +function getCourse(courseId: string): Course { + return { + id: courseId, + name: "courseName", + teacher: "teacher", + students: ["student1", "student2"], + archived: false, + assignments: ["assignment1", "assignment2", "assignment3", "assignment4", "assignment5", "assignment6", "assignment7", "assignment8", "assignment9"] + } +} + +function getAssignment(assignmentId: string): Assignment { + return { + id: assignmentId, + name: assignmentId, + deadline: new Date(2022, 11, 17), + submissions: 2, + score: 10, + visible: true, + archived: Number(assignmentId.slice(-1))%2==0, + } } \ No newline at end of file From 3613f308cd205ad29e3b0c1f55a916889df83ab3 Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke Date: Sat, 23 Mar 2024 20:43:02 +0100 Subject: [PATCH 048/268] states voor archive en delete opgeschoond op subjectsteacherpage --- .../src/pages/SubjectsPage/ProjectsView.tsx | 29 ++++++----------- .../SubjectsPage/SubjectsStudentPage.tsx | 29 ++++++++++------- .../SubjectsPage/SubjectsTeacherPage.tsx | 31 +++++++++++-------- 3 files changed, 44 insertions(+), 45 deletions(-) diff --git a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx index c25a0b610..b2d48aeec 100644 --- a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx @@ -9,8 +9,6 @@ interface ProjectsViewProps { assignments: Assignment[]; deleteAssignment: (index: number) => void; archiveAssignment: (index: number) => void; - archivedAssignments: Assignment[]; - deleteArchivedAssignment: (index: number) => void; } interface Assignment { @@ -23,7 +21,7 @@ interface Assignment { archived: boolean; } -export function ProjectsView({isStudent, archived, assignments, deleteAssignment, archiveAssignment, archivedAssignments, deleteArchivedAssignment}: ProjectsViewProps) { +export function ProjectsView({isStudent, archived, assignments, deleteAssignment, archiveAssignment}: ProjectsViewProps) { return ( <> - {!archived? - assignments - .map((assignment, index) => ( - ({...assignment, index})) + .filter((assignment) => assignment.archived == archived) + .map((assignment) => ( + deleteAssignment(index)} - archiveEvent={() => archiveAssignment(index)}/> - )) - : - archivedAssignments - .map((assignment, index) => ( - deleteArchivedAssignment(index)} - archiveEvent={() => archiveAssignment(index)}/> - )) - } + deleteEvent={() => deleteAssignment(assignment.index)} + archiveEvent={() => archiveAssignment(assignment.index)}/> + ))} diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx index 55b87da4b..ba96786df 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx @@ -30,20 +30,15 @@ export function SubjectsStudentPage() { courseId = String(courseId); const course = getCourse(courseId); - const assignmentsTemp = course.assignments.map((assignmentId) => getAssignment(assignmentId)); - const [assignments, setAssignments] = useState(assignmentsTemp.filter((assignment) => !assignment.archived)); - const [archivedAssignments, setArchivedAssignments] = useState(assignmentsTemp.filter((assignment) => assignment.archived)); + const [assignments, setAssignments] = useState(course.assignments.map((assignmentId) => getAssignment(assignmentId))); const deleteAssignment = (index: number) => { setAssignments(assignments.filter((_, i) => i !== index)); } - const deleteArchivedAssignment = (index: number) => { - setArchivedAssignments(archivedAssignments.filter((_, i) => i !== index)); - } const archiveAssignment = (index: number) => { - setArchivedAssignments([...archivedAssignments, assignments[index]]); - deleteAssignment(index); + const newAssignments = assignments.map((a, i) => i==index? archiveSingleAssignment(a): a); + setAssignments(newAssignments); } return ( @@ -53,11 +48,9 @@ export function SubjectsStudentPage() { , + deleteAssignment={deleteAssignment} archiveAssignment={archiveAssignment}/>, ]}/> + deleteAssignment={deleteAssignment} archiveAssignment={archiveAssignment}/>]}/> @@ -86,4 +79,16 @@ function getAssignment(assignmentId: string): Assignment { visible: true, archived: Number(assignmentId.slice(-1))%2==0, } +} + +function archiveSingleAssignment(assignment: Assignment): Assignment { + return { + id: assignment.id, + name: assignment.name, + deadline: assignment.deadline, + submissions: assignment.submissions, + score: assignment.score, + visible: assignment.visible, + archived: true, + } } \ No newline at end of file diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx index a4dac6995..2cf82c688 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx @@ -38,22 +38,17 @@ export function SubjectsTeacherPage() { } const course = getCourse(courseId); - const assignmentsTemp = course.assignments.map((assignmentId) => getAssignment(assignmentId)); - const [assignments, setAssignments] = useState(assignmentsTemp.filter((assignment) => !assignment.archived)); - const [archivedAssignments, setArchivedAssignments] = useState(assignmentsTemp.filter((assignment) => assignment.archived)); + const [assignments, setAssignments] = useState(course.assignments.map((assignmentId) => getAssignment(assignmentId))); const deleteAssignment = (index: number) => { setAssignments(assignments.filter((_, i) => i !== index)); } - const deleteArchivedAssignment = (index: number) => { - setArchivedAssignments(archivedAssignments.filter((_, i) => i !== index)); - } const archiveAssignment = (index: number) => { - setArchivedAssignments([...archivedAssignments, assignments[index]]); - deleteAssignment(index); + const newAssignments = assignments.map((a, i) => i==index? archiveSingleAssignment(a): a); + setAssignments(newAssignments); } - + return ( <> @@ -61,11 +56,9 @@ export function SubjectsTeacherPage() { , + deleteAssignment={deleteAssignment} archiveAssignment={archiveAssignment}/>, ]}/> + deleteAssignment={deleteAssignment} archiveAssignment={archiveAssignment}/>]}/> @@ -99,4 +92,16 @@ function getAssignment(assignmentId: string): Assignment { visible: true, archived: Number(assignmentId.slice(-1))%2==0, } +} + +function archiveSingleAssignment(assignment: Assignment): Assignment { + return { + id: assignment.id, + name: assignment.name, + deadline: assignment.deadline, + submissions: assignment.submissions, + score: assignment.score, + visible: assignment.visible, + archived: true, + } } \ No newline at end of file From ac81bed3ccaa76ad118a141bcf94833eae3a728b Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke Date: Sat, 23 Mar 2024 21:07:27 +0100 Subject: [PATCH 049/268] visibility ook ge-lift zodat het blijft tussen tab switches --- .../AssignmentListItemSubjectsPage.tsx | 11 +++++-- .../src/pages/SubjectsPage/ProjectsView.tsx | 6 ++-- .../SubjectsPage/SubjectsStudentPage.tsx | 29 ++++--------------- .../SubjectsPage/SubjectsTeacherPage.tsx | 22 ++++++++++++-- 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx index 08f578929..5e2206975 100644 --- a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx +++ b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx @@ -17,6 +17,7 @@ interface AssignmentListItemSubjectsPageProps { visible: boolean; deleteEvent: () => void; archiveEvent: () => void; + visibilityEvent: () => void; } /* @@ -29,7 +30,8 @@ interface AssignmentListItemSubjectsPageProps { * @param isStudent: boolean - if the user is a student or a teacher */ -export function AssignmentListItemSubjectsPage({projectName, dueDate, submissions, score, isStudent, archived, visible, deleteEvent, archiveEvent}:AssignmentListItemSubjectsPageProps) { +export function AssignmentListItemSubjectsPage({projectName, dueDate, submissions, score, isStudent, archived, visible, + deleteEvent, archiveEvent, visibilityEvent}:AssignmentListItemSubjectsPageProps) { const navigate = useNavigate(); const handleProjectClick = () => { console.log("Project clicked"); @@ -64,7 +66,8 @@ export function AssignmentListItemSubjectsPage({projectName, dueDate, submission <> - + } @@ -79,9 +82,10 @@ interface ButtonActionsProps { startVisible: boolean; deleteEvent: () => void; archiveEvent: () => void; + visibilityEvent: () => void; } -function ButtonActions({archived, startVisible, deleteEvent, archiveEvent}: ButtonActionsProps){ +function ButtonActions({archived, startVisible, deleteEvent, archiveEvent, visibilityEvent}: ButtonActionsProps){ const [visible, setVisible] = useState(startVisible); const handleIconClick = (e, icon: string) => { @@ -90,6 +94,7 @@ function ButtonActions({archived, startVisible, deleteEvent, archiveEvent}: Butt switch (icon) { case 'visible': setVisible(!visible); + visibilityEvent(); break; case 'archive': archiveEvent(); diff --git a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx index b2d48aeec..035b5f34c 100644 --- a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx @@ -9,6 +9,7 @@ interface ProjectsViewProps { assignments: Assignment[]; deleteAssignment: (index: number) => void; archiveAssignment: (index: number) => void; + changeVisibilityAssignment: (index: number) => void; } interface Assignment { @@ -21,7 +22,7 @@ interface Assignment { archived: boolean; } -export function ProjectsView({isStudent, archived, assignments, deleteAssignment, archiveAssignment}: ProjectsViewProps) { +export function ProjectsView({isStudent, archived, assignments, deleteAssignment, archiveAssignment, changeVisibilityAssignment}: ProjectsViewProps) { return ( <> deleteAssignment(assignment.index)} - archiveEvent={() => archiveAssignment(assignment.index)}/> + archiveEvent={() => archiveAssignment(assignment.index)} + visibilityEvent={() => changeVisibilityAssignment(assignment.index)}/> ))} diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx index ba96786df..f7bdf97e1 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx @@ -3,7 +3,6 @@ import { Box, Stack } from "@mui/material"; import TabSwitcher from "../../components/TabSwitcher.tsx"; import {ProjectsView} from "./ProjectsView.tsx"; import { useParams } from "react-router-dom"; -import { useState } from "react"; interface Course { id: string; @@ -31,15 +30,7 @@ export function SubjectsStudentPage() { const course = getCourse(courseId); - const [assignments, setAssignments] = useState(course.assignments.map((assignmentId) => getAssignment(assignmentId))); - - const deleteAssignment = (index: number) => { - setAssignments(assignments.filter((_, i) => i !== index)); - } - const archiveAssignment = (index: number) => { - const newAssignments = assignments.map((a, i) => i==index? archiveSingleAssignment(a): a); - setAssignments(newAssignments); - } + const assignments = course.assignments.map((assignmentId) => getAssignment(assignmentId)); return ( <> @@ -48,9 +39,11 @@ export function SubjectsStudentPage() { , + deleteAssignment={() => undefined} archiveAssignment={() => undefined} + changeVisibilityAssignment={() => undefined}/>, ]}/> + deleteAssignment={() => undefined} archiveAssignment={() => undefined} + changeVisibilityAssignment={() => undefined}/>]}/> @@ -79,16 +72,4 @@ function getAssignment(assignmentId: string): Assignment { visible: true, archived: Number(assignmentId.slice(-1))%2==0, } -} - -function archiveSingleAssignment(assignment: Assignment): Assignment { - return { - id: assignment.id, - name: assignment.name, - deadline: assignment.deadline, - submissions: assignment.submissions, - score: assignment.score, - visible: assignment.visible, - archived: true, - } } \ No newline at end of file diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx index 2cf82c688..15f6fcb7a 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx @@ -48,6 +48,10 @@ export function SubjectsTeacherPage() { const newAssignments = assignments.map((a, i) => i==index? archiveSingleAssignment(a): a); setAssignments(newAssignments); } + const changeVisibilityAssignment = (index: number) => { + const newAssignments = assignments.map((a, i) => i==index? changeVisibilitySingleAssignment(a): a); + setAssignments(newAssignments); + } return ( <> @@ -56,9 +60,11 @@ export function SubjectsTeacherPage() { , + deleteAssignment={deleteAssignment} archiveAssignment={archiveAssignment} + changeVisibilityAssignment={changeVisibilityAssignment}/>, ]}/> + deleteAssignment={deleteAssignment} archiveAssignment={archiveAssignment} + changeVisibilityAssignment={changeVisibilityAssignment}/>]}/> @@ -104,4 +110,16 @@ function archiveSingleAssignment(assignment: Assignment): Assignment { visible: assignment.visible, archived: true, } +} + +function changeVisibilitySingleAssignment(assignment: Assignment): Assignment { + return { + id: assignment.id, + name: assignment.name, + deadline: assignment.deadline, + submissions: assignment.submissions, + score: assignment.score, + visible: !assignment.visible, + archived: assignment.archived, + } } \ No newline at end of file From 9557a7d97c9c0817a0be39fe9a0a29aa7ae80dab Mon Sep 17 00:00:00 2001 From: elias Date: Sun, 24 Mar 2024 13:02:02 +0100 Subject: [PATCH 050/268] opdracht student pagina lijst scrollable gemaakt --- .../assignmentPage/assignmentStudentPage.tsx | 43 +++++---- .../assignmentPage/assignmentTeacherPage.tsx | 89 ++----------------- 2 files changed, 34 insertions(+), 98 deletions(-) diff --git a/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx b/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx index b0c9ad816..0c18c2c83 100644 --- a/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx +++ b/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx @@ -1,6 +1,6 @@ import {Header} from "../../components/Header.tsx"; import {AssignmentListItem} from "../../components/AssignmentListItem.tsx"; -import {TextField, Box, Button, Card, Divider, List, Stack, Typography} from "@mui/material"; +import {TextField,Paper, Box, Button, Card, Divider, List, Stack, Typography} from "@mui/material"; const text = "Lorem ipsum dolor sit amet consectetur. Nisi magna dolor et nisi nibh et velit phasellus. Aliquam semper justo posuere suspendisse amet amet nam nec. Tellus magna in proin tempor hac sit. Faucibus laoreet nulla commodo quis. Porttitor sit facilisis sit dignissim quis. Malesuada etiam tempor donec et ante. Aliquam massa donec augue aliquam semper amet blandit sed faucibus. Et elementum duis adipiscing turpis mi. Senectus eu rutrum accumsan convallis metus mattis risus. Quam eget sapien tellus aliquam facilisi sit volutpat. Scelerisque auctor purus nam sit lacus amet ullamcorper amet. Turpis nulla quis in pretium. Maecenas aliquam ac ullamcorper suspendisse morbi cras. Mi nibh aliquet massa sit eget tristique a. Posuere pretium auctor tellus massa et eu egestas. Sit lorem proin aenean tortor morbi condimentum. Leo eu enim cursus tempus sed viverra laoreet. Nisl ornare velit molestie suspendisse. Hendrerit nibh mauris vulputate sit vitae. Tellus quisque non nibh proin nunc lacus scelerisque dui. Aliquam fermentum libero aliquet volutpat at. Vestibulum ultrices nec felis leo nibh viverra. Hendrerit ut nunc porta egestas sit velit dictumst dis porta. Donec quam aliquam commodo mattis purus. Tellus nulla lectus fusce in fames scelerisque at." @@ -86,35 +86,48 @@ export function AssignmentStudentPage() { > Opgave - + {text} {/*Indieningen*/} + - + Indiening Datum - - {assignments.map((assignment) => ( - - - - - ))} - + + + + {assignments.map((assignment) => ( + + + + + + + ))} + + + {/*Upload knop*/} - {/*opdracht and upload button*/} + {/*deadline and groep button */} - - - - - Naam Opdracht - - - - - - -
- - + Deadline {deadline} - {/*Deadline*/} - - - - - Deadline - - - - - - - - - - - {/*Opgave*/} - Opgave - + {text} - - {/*Restricties*/} - - - - Type restrictie - Details - - - - {restrictions.map((res) => - - )} - - - - - - - - - -
- - - ); From 2d608a9df29c9f4cfbf6ffeb0eb1b798c8a00353 Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Sun, 24 Mar 2024 15:11:51 +0100 Subject: [PATCH 051/268] bugs opgelost in change/add asignment pagina. - Validatie op de restricties toegevoegd. - Gezorgd dat er maar 1 restrictie van elk van de ondersteunde types kan aangemaakt worden. --- frontend/frontend/src/i18n/en.ts | 3 + frontend/frontend/src/i18n/nl.ts | 3 + .../AddChangeAssignmentPage.tsx | 33 +++-- .../RestrictionPopup.tsx | 122 ++++++++++++++---- 4 files changed, 122 insertions(+), 39 deletions(-) diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index fc434ddaf..8f467db6b 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -31,10 +31,13 @@ const english = { upload_container: "Upload a .tar file", dockerfile: "Dockerfile", restrictiontype: "Restriction type", + dockerTest: "Docker test", maxSize: "Max file size", fileType: "Allowed file types", fileSize: "Max file size", allowed_file_types: "Allowed file types", + dockerfile_error: "Please upload a Dockerfile.", + filetype_error: "Please select at least one file type.", }; diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts index 9f457cb6c..09f1fe050 100644 --- a/frontend/frontend/src/i18n/nl.ts +++ b/frontend/frontend/src/i18n/nl.ts @@ -31,10 +31,13 @@ const dutch = { upload_container: "Upload een .tar bestand", dockerfile: "Docker bestand (Dockerfile)", restrictiontype: "Restrictietype", + dockerTest: "Docker test", maxSize: "Maximale bestandsgrootte", fileType: "Toegestane bestandstypes", fileSize: "Maximale bestandsgrootte", allowed_file_types: "Toegestane bestandstypes", + dockerfile_error: "Gelieve een Docker bestand te uploaden.", + filetype_error: "Gelieve minstens één bestandstype te selecteren.", }; export default dutch; \ No newline at end of file diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx index f23631334..30ec2d393 100644 --- a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx +++ b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx @@ -63,19 +63,21 @@ export function AddChangeAssignmentPage() { const [groups, setGroups] = useState(false); const [visible, setVisible] = useState(false); const [assignmentFile, setAssignmentFile] = useState(); + + // State for the error checks of the assignment const [assignmentErrors, setAssignmentErrors] = useState({ title: false, description: false, dueDate: false }); + + // State for the restriction popup const [open, setOpen] = useState(false); const [type, setType] = useState('dockerTest'); const [dockerfile, setDockerFile] = useState(); const [allowedFileTypes, setAllowedFileTypes] = useState([]); const [maxSize, setMaxSize] = useState(); - const [restriction, setRestriction] = useState({type: '', value: undefined}); - - + const [allowedTypes, setAllowedTypes] = useState(['dockerTest', 'fileSize', 'fileType']); /** * Function to upload the details of the assignment through a text file * @param {ChangeEvent} event - The event object @@ -87,14 +89,22 @@ export function AddChangeAssignmentPage() { } }; + // Limit the types of restrictions that can be added by one for each type and set the type to the first allowed type, + // then open the restriction popup. const handleAddRestriction = () => { + //found at https://upmostly.com/typescript/typescripts-array-filter-method-explained + const currentRestrictionTypes = restrictions.map((restriction) => restriction.type as restrictionType); + setAllowedTypes(allowedTypes.filter((type) => !currentRestrictionTypes.includes(type))); + setType(allowedTypes[0]) setOpen(true); } + // Remove the restriction at the given index, tied to the remove button in the restriction list. const removeRestriction = (index: number) => { setRestrictions(restrictions.filter((_, i) => i !== index)); } +// Handle the submission of the form, check if all required fields are filled in, and send the data to the API. const handleSubmit = (event: FormEvent) => { event.preventDefault(); setAssignmentErrors({title: title === "", description: description === "", dueDate: dueDate === null}); @@ -119,17 +129,10 @@ export function AddChangeAssignmentPage() { console.info('Form submitted', title, description, dueDate, restrictions, groups, visible, assignmentFile) } + // Fetch data from the API on load useEffect(() => { //TODO: fetch data from api - console.log(assignmentFile); - }, [assignmentFile]); - - useEffect(() => { - if (restriction.type === '' || restriction.value === undefined) { - return; - } - setRestrictions(prevRestrictions => [...prevRestrictions, restriction]); - }, [restriction]); + }, []); return ( <> @@ -288,10 +291,12 @@ export function AddChangeAssignmentPage() { + maxSize={maxSize} setMaxSize={setMaxSize} + allowedTypes={allowedTypes} + /> ); diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx b/frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx index 36b6b5383..a6de20631 100644 --- a/frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx +++ b/frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx @@ -2,16 +2,30 @@ import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; -import {ChangeEvent, FormEvent} from "react"; +import {ChangeEvent, Dispatch, SetStateAction, useEffect, useState} from "react"; import {t} from "i18next"; -import {Autocomplete, Box, IconButton, InputAdornment, MenuItem, Select, TextField, Typography} from "@mui/material"; +import { + Autocomplete, + Box, + FormHelperText, + IconButton, + InputAdornment, + MenuItem, + Select, + TextField, + Typography +} from "@mui/material"; import CancelIcon from '@mui/icons-material/Cancel'; import CheckIcon from '@mui/icons-material/Check'; import FileUploadButton from "../../components/FileUploadButton"; import {restriction} from './AddChangeAssignmentPage.tsx' /** - * Component for adding restrictions to an assignment + * Component for adding restrictions to an assignment. + * The component is a dialog that opens when the user wants to add a restriction to an assignment. + * Only one restriction can be added at a time. + * The user can choose between three types of restrictions: dockerTest, fileSize and fileType. + * The dialog only allows one restriction of each type to be added. * @param open - boolean for opening the dialog * @param setOpen - function for setting the open state * @param type - type of restriction @@ -22,6 +36,9 @@ import {restriction} from './AddChangeAssignmentPage.tsx' * @param setAllowedFileTypes - function for setting the allowed file types * @param maxSize - maximum file size for fileSize restriction * @param setMaxSize - function for setting the maximum file size + * @param restrictions - list of restrictions + * @param setRestrictions - function for setting the restrictions + * @param allowedTypes - list of allowed restriction types */ export type restrictionType = 'dockerTest' | 'fileSize' | 'fileType'; @@ -32,15 +49,17 @@ interface RestrictionPopupProps { type: restrictionType; setType: (type: restrictionType) => void; dockerfile?: File; - setDockerFile?: (dockerfile: File) => void; + setDockerFile?: (dockerfile: File | undefined) => void; allowedFileTypes?: string[]; setAllowedFileTypes?: (allowedFileTypes: string[]) => void; maxSize?: number; setMaxSize?: (maxSize: number) => void; - setRestriction: (restriction: restriction) => void; - + restrictions: restriction[]; + setRestrictions: Dispatch> + allowedTypes: restrictionType[]; } + export default function RestrictionPopup({ open, setOpen, @@ -52,13 +71,19 @@ export default function RestrictionPopup({ setAllowedFileTypes, maxSize, setMaxSize, - setRestriction + restrictions, + setRestrictions, + allowedTypes }: RestrictionPopupProps) { + //state for submitError, used to check if the required fields are filled + const [submitError, setSubmitError] = useState(false); + // Function for closing the dialog const handleClose = () => { setOpen(false); }; + // Function for setting the dockerfile, required by the uploadbutton component const setFile = (event: ChangeEvent) => { if (event.target.files) { if (setDockerFile) { @@ -68,6 +93,52 @@ export default function RestrictionPopup({ } }; + // Function for submitting the restriction + // Checks if the restriction is valid by checking if the required fields are filled, if not it sets the submitError + // to true, and it does not submit the restriction. + const submitRestriction = (event: formEvent) => { + event.preventDefault(); + let restriction: restriction; + if (type === 'dockerTest') { + if (!dockerfile) { + setSubmitError(true) + return; + } + restriction = {type, value: dockerfile} + } else if (type === 'fileSize') { + if (!maxSize) { + setSubmitError(true) + return; + } + restriction = {type: type, value: maxSize} + } else { + if (allowedFileTypes?.length === 0) { + setSubmitError(true) + return; + } + restriction = {type: type, value: allowedFileTypes} + } + + setRestrictions([...restrictions, restriction]); + setMaxSize ? setMaxSize(0) : undefined; + setDockerFile ? setDockerFile(undefined) : undefined; + setAllowedFileTypes ? setAllowedFileTypes([]) : undefined; + handleClose(); + } + + // Checks if the required fields are filled, if they are it sets the submitError to false + // This is used to remove the error message when the user has filled the required fields to prevent annoying the user. + // Executes when the type, dockerfile, maxSize or allowedFileTypes changes + useEffect(() => { + if (type === 'dockerTest' && dockerfile) { + setSubmitError(false); + } else if (type === 'fileSize' && maxSize) { + setSubmitError(false); + } else if (type === 'fileType' && allowedFileTypes?.length !== 0) { + setSubmitError(false); + } + }, [type, dockerfile, maxSize, allowedFileTypes]) + return ( <> ) => { - event.preventDefault() - let restriction: restriction - if (type === 'dockerTest') { - restriction = {type, value: dockerfile} - } else if (type === 'fileSize') { - restriction = {type: type, value: maxSize} - } else { - restriction = {type: type, value: allowedFileTypes} - } - setRestriction(restriction) - handleClose(); - }, + onSubmit: submitRestriction, }} > {t('add_restriction')} @@ -102,7 +161,7 @@ export default function RestrictionPopup({ name={'restrictionType'} value={type} > - {['dockerTest', 'fileSize', 'fileType'].map((type) => ( + {allowedTypes.map((type) => ( {t(type)} ))} @@ -116,16 +175,28 @@ export default function RestrictionPopup({ path={dockerfile ? dockerfile : undefined} /> + {submitError && {t('dockerfile_error')}} )} {type === 'fileSize' && (<> {t('maxSize')} mb, }} type={'number'} - value={maxSize} - onChange={(event) => setMaxSize ? setMaxSize(parseInt(event.target.value)) : undefined} + value={maxSize ? maxSize : 0} + onChange={(event) => { + if (event.target.value !== '') { + const newSize = Math.max(parseInt(event.target.value), 0); + setMaxSize ? setMaxSize(newSize) : undefined; + } else { + setMaxSize ? setMaxSize(parseInt(event.target.value)) : undefined; + } + }} /> )} {type === 'fileType' && (<> @@ -135,7 +206,7 @@ export default function RestrictionPopup({ id="tags-outlined" options={['.zip', '.pdf', '.tar', '.java', '.py', '.c', '.cpp', '.h', '.hpp', '.txt', '.md', '.csv', '.json', '.xml', '.html', '.css', '.js', '.ts', '.jsx', '.tsx']} getOptionLabel={(option) => option} - defaultValue={allowedFileTypes} + value={allowedFileTypes} freeSolo filterSelectedOptions limitTags={2} @@ -148,13 +219,14 @@ export default function RestrictionPopup({ /> )} /> + {submitError && {t('filetype_error')}} )} - + From 02a462cdaa81df004a5db7e32aeb44a8c0c045c7 Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Sun, 24 Mar 2024 15:29:49 +0100 Subject: [PATCH 052/268] Geen restricties toelaten als er geen toegelaten types meer zijn. Ook restricties terug toevoegen als deze worden verwijderd. --- .../AddChangeAssignmentPage.tsx | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx index 30ec2d393..f27da8ae7 100644 --- a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx +++ b/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx @@ -33,6 +33,8 @@ import RestrictionPopup, {restrictionType} from "./RestrictionPopup.tsx"; * The form should also contain a button to upload the assignment file for ease of use. */ +const initialAllowedTypes: restrictionType[] = ['dockerTest', 'fileSize', 'fileType']; + interface assignment { title: string, description: string, @@ -77,7 +79,8 @@ export function AddChangeAssignmentPage() { const [dockerfile, setDockerFile] = useState(); const [allowedFileTypes, setAllowedFileTypes] = useState([]); const [maxSize, setMaxSize] = useState(); - const [allowedTypes, setAllowedTypes] = useState(['dockerTest', 'fileSize', 'fileType']); + const [allowedTypes, setAllowedTypes] = useState(initialAllowedTypes); + /** * Function to upload the details of the assignment through a text file * @param {ChangeEvent} event - The event object @@ -94,9 +97,11 @@ export function AddChangeAssignmentPage() { const handleAddRestriction = () => { //found at https://upmostly.com/typescript/typescripts-array-filter-method-explained const currentRestrictionTypes = restrictions.map((restriction) => restriction.type as restrictionType); - setAllowedTypes(allowedTypes.filter((type) => !currentRestrictionTypes.includes(type))); - setType(allowedTypes[0]) - setOpen(true); + setAllowedTypes(initialAllowedTypes.filter((type) => !currentRestrictionTypes.includes(type))); + if (allowedTypes.length !== 0) { + setType(allowedTypes[0]) + setOpen(true); + } } // Remove the restriction at the given index, tied to the remove button in the restriction list. @@ -129,10 +134,13 @@ export function AddChangeAssignmentPage() { console.info('Form submitted', title, description, dueDate, restrictions, groups, visible, assignmentFile) } - // Fetch data from the API on load + // Remove the types of restrictions that are already added to the assignment from the allowed types. useEffect(() => { - //TODO: fetch data from api - }, []); + const currentRestrictionTypes = restrictions.map((restriction) => restriction.type as restrictionType); + setAllowedTypes(initialAllowedTypes.filter((type) => !currentRestrictionTypes.includes(type))); + setType(allowedTypes[0]); + console.log(allowedTypes) + }, [restrictions]); return ( <> @@ -239,6 +247,7 @@ export function AddChangeAssignmentPage() { From 842adbd8c80eb3f55303af9687db0ef79652fc4c Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Sun, 24 Mar 2024 15:32:23 +0100 Subject: [PATCH 053/268] kleine aanpassing filetype --- .../src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx b/frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx index a6de20631..bc9ce07b4 100644 --- a/frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx +++ b/frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx @@ -169,7 +169,7 @@ export default function RestrictionPopup({ {type === 'dockerTest' && (<> {t('dockerfile')} Date: Sun, 24 Mar 2024 22:38:01 +0100 Subject: [PATCH 054/268] Bestandsnamen veranderd voor consistentie en routering frontend in orde proberen brengen. --- frontend/frontend/src/main.tsx | 112 +++++++++++++----- .../AddChangeAssignmentPage.tsx | 0 .../RestrictionPopup.tsx | 0 ...dentPage.tsx => AssignmentStudentPage.tsx} | 0 ...cherPage.tsx => AssignmentTeacherPage.tsx} | 0 .../{groupsPage.tsx => GroupsPage.tsx} | 0 .../ArchivedProjectsView.tsx | 0 .../ProjectsView.tsx | 0 .../SubjectsStudentPage.tsx | 0 .../SubjectsTeacherPage.tsx | 0 10 files changed, 80 insertions(+), 32 deletions(-) rename frontend/frontend/src/pages/{AddChangeAssignmentPage => addChangeAssignmentPage}/AddChangeAssignmentPage.tsx (100%) rename frontend/frontend/src/pages/{AddChangeAssignmentPage => addChangeAssignmentPage}/RestrictionPopup.tsx (100%) rename frontend/frontend/src/pages/assignmentPage/{assignmentStudentPage.tsx => AssignmentStudentPage.tsx} (100%) rename frontend/frontend/src/pages/assignmentPage/{assignmentTeacherPage.tsx => AssignmentTeacherPage.tsx} (100%) rename frontend/frontend/src/pages/groupsPage/{groupsPage.tsx => GroupsPage.tsx} (100%) rename frontend/frontend/src/pages/{SubjectsPage => subjectsPage}/ArchivedProjectsView.tsx (100%) rename frontend/frontend/src/pages/{SubjectsPage => subjectsPage}/ProjectsView.tsx (100%) rename frontend/frontend/src/pages/{SubjectsPage => subjectsPage}/SubjectsStudentPage.tsx (100%) rename frontend/frontend/src/pages/{SubjectsPage => subjectsPage}/SubjectsTeacherPage.tsx (100%) diff --git a/frontend/frontend/src/main.tsx b/frontend/frontend/src/main.tsx index c23b29514..76567b3b5 100644 --- a/frontend/frontend/src/main.tsx +++ b/frontend/frontend/src/main.tsx @@ -8,23 +8,90 @@ import ErrorPage from "./pages/ErrorPage.tsx"; import {MainPage} from "./pages/mainPage/MainPage.tsx"; import {Helmet, HelmetProvider} from "react-helmet-async"; - -import {SubjectsStudentPage} from "./pages/SubjectsPage/SubjectsStudentPage.tsx"; -import {SubjectsTeacherPage} from "./pages/SubjectsPage/SubjectsTeacherPage.tsx"; -import {ProjectScoresPage} from "./pages/scoresPage/ProjectScoresPage.tsx"; -import {AssignmentStudentPage} from "./pages/assignmentPage/assignmentStudentPage"; -import {AssignmentTeacherPage} from "./pages/assignmentPage/assignmentTeacherPage.tsx"; -import {GroupsPage} from "./pages/groupsPage/groupsPage.tsx"; import {LocalizationProvider} from "@mui/x-date-pickers"; import {AdapterDayjs} from '@mui/x-date-pickers/AdapterDayjs/AdapterDayjs'; - +import {LoginPage} from "./pages/loginPage/LoginPage.tsx"; +import {SubjectsStudentPage} from "./pages/subjectsPage/SubjectsStudentPage.tsx"; +import {AssignmentTeacherPage} from "./pages/assignmentPage/AssignmentTeacherPage.tsx"; +import {AssignmentStudentPage} from "./pages/assignmentPage/AssignmentStudentPage.tsx"; import {SubmissionPage} from "./pages/submissionPage/SubmissionPage.tsx"; -import {SimpleRequestsPage} from "./pages/simpleRequestsPage/SimpleRequestsPage.tsx"; -import {AddChangeAssignmentPage} from "./pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx"; - +import {ProjectScoresPage} from "./pages/scoresPage/ProjectScoresPage.tsx"; +import {SubjectsTeacherPage} from "./pages/subjectsPage/SubjectsTeacherPage.tsx"; +import {AddChangeAssignmentPage} from "./pages/addChangeAssignmentPage/AddChangeAssignmentPage.tsx"; +//TODO: add change/add course page when implemented const router = createBrowserRouter([ { + path: '/login', + element: , + errorElement: , + }, + { + path: '/', + element: , + errorElement: , + }, + { + path: '/course_student/:courseId', + element: , + errorElement: , + }, + { + path: '/course_teacher/:courseId', + element: , + errorElement: , + }, + { + path: '/course_teacher/:courseId/assignment/:assignmentId', + element: , + errorElement: , + }, + { + path: '/course_student/:courseId/assignment/:assignmentId', + element: , + errorElement: , + }, + { + path: '/course_student/:courseId/assignment/:assignmentId/submission/:submissionId', + element: , + errorElement: , + }, + { + path: '/course_teacher/:courseId/assignment/:assignmentId/scoring', + element: , + errorElement: , + }, + { + path: '/course_teacher/:courseId/assignment/edit/:assignmentId?', + element: , + errorElement: , + }, + { + path: '*', + element: , + }, +]); + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + + + Loading...
}> + + + + + + + + +); + +/* old routes, to be deleted TODO: fix routes +{ path: "/", element: , errorElement: , @@ -75,7 +142,7 @@ const router = createBrowserRouter([ }, { path: "/add_change_assignment", - element: , + element: , errorElement: , }, @@ -91,23 +158,4 @@ const router = createBrowserRouter([ errorElement: , } - -]); - -ReactDOM.createRoot(document.getElementById("root")!).render( - - - - - - - Loading...
}> - - - - - - - - -); + */ \ No newline at end of file diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx b/frontend/frontend/src/pages/addChangeAssignmentPage/AddChangeAssignmentPage.tsx similarity index 100% rename from frontend/frontend/src/pages/AddChangeAssignmentPage/AddChangeAssignmentPage.tsx rename to frontend/frontend/src/pages/addChangeAssignmentPage/AddChangeAssignmentPage.tsx diff --git a/frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx b/frontend/frontend/src/pages/addChangeAssignmentPage/RestrictionPopup.tsx similarity index 100% rename from frontend/frontend/src/pages/AddChangeAssignmentPage/RestrictionPopup.tsx rename to frontend/frontend/src/pages/addChangeAssignmentPage/RestrictionPopup.tsx diff --git a/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx b/frontend/frontend/src/pages/assignmentPage/AssignmentStudentPage.tsx similarity index 100% rename from frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx rename to frontend/frontend/src/pages/assignmentPage/AssignmentStudentPage.tsx diff --git a/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx b/frontend/frontend/src/pages/assignmentPage/AssignmentTeacherPage.tsx similarity index 100% rename from frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx rename to frontend/frontend/src/pages/assignmentPage/AssignmentTeacherPage.tsx diff --git a/frontend/frontend/src/pages/groupsPage/groupsPage.tsx b/frontend/frontend/src/pages/groupsPage/GroupsPage.tsx similarity index 100% rename from frontend/frontend/src/pages/groupsPage/groupsPage.tsx rename to frontend/frontend/src/pages/groupsPage/GroupsPage.tsx diff --git a/frontend/frontend/src/pages/SubjectsPage/ArchivedProjectsView.tsx b/frontend/frontend/src/pages/subjectsPage/ArchivedProjectsView.tsx similarity index 100% rename from frontend/frontend/src/pages/SubjectsPage/ArchivedProjectsView.tsx rename to frontend/frontend/src/pages/subjectsPage/ArchivedProjectsView.tsx diff --git a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/subjectsPage/ProjectsView.tsx similarity index 100% rename from frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx rename to frontend/frontend/src/pages/subjectsPage/ProjectsView.tsx diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx b/frontend/frontend/src/pages/subjectsPage/SubjectsStudentPage.tsx similarity index 100% rename from frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx rename to frontend/frontend/src/pages/subjectsPage/SubjectsStudentPage.tsx diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx b/frontend/frontend/src/pages/subjectsPage/SubjectsTeacherPage.tsx similarity index 100% rename from frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx rename to frontend/frontend/src/pages/subjectsPage/SubjectsTeacherPage.tsx From cf97b7596757e3f15c1f4e91f1c5373d9a7d40a8 Mon Sep 17 00:00:00 2001 From: elias Date: Mon, 25 Mar 2024 01:01:12 +0100 Subject: [PATCH 055/268] download knop toegevoegd aan items van opdracht leerkracht pagina --- .../AssignmentListItemTeacherPage.tsx | 49 +++++++ .../assignmentPage/assignmentStudentPage.tsx | 26 ++-- .../assignmentPage/assignmentTeacherPage.tsx | 127 ++++++++++++++---- 3 files changed, 158 insertions(+), 44 deletions(-) create mode 100644 frontend/frontend/src/components/AssignmentListItemTeacherPage.tsx diff --git a/frontend/frontend/src/components/AssignmentListItemTeacherPage.tsx b/frontend/frontend/src/components/AssignmentListItemTeacherPage.tsx new file mode 100644 index 000000000..ed99c62be --- /dev/null +++ b/frontend/frontend/src/components/AssignmentListItemTeacherPage.tsx @@ -0,0 +1,49 @@ +import {ListItem, ListItemButton, ListItemText, Divider} from "@mui/material"; +import {useNavigate} from "react-router-dom"; +import DownloadIcon from '@mui/icons-material/Download'; +import {t} from "i18next"; +interface AssignmentListItemTeacherPageProps { + id: string; + studentName: string; + submitted?: Date; + score: number; +} + +/* +* This component is used to display a single assignment in the list of assignments +* @param id: string - the id of the student +* @param studentName: string - the name of the student +* @param submitted: Date - the due date of the submission. empty when the student hasn't submitted yet +* @param score: number - assigned score on the project +*/ + +export function AssignmentListItemTeacherPage({id,studentName,submitted, score}:AssignmentListItemTeacherPageProps) { + const navigate = useNavigate(); + const handleProjectClick = () => { + console.log("Project clicked"); + navigate(`/${id}`) + } + + return ( + <> + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx b/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx index 0c18c2c83..aa7402c3e 100644 --- a/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx +++ b/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx @@ -1,6 +1,6 @@ import {Header} from "../../components/Header.tsx"; import {AssignmentListItem} from "../../components/AssignmentListItem.tsx"; -import {TextField,Paper, Box, Button, Card, Divider, List, Stack, Typography} from "@mui/material"; +import {Box, Button, Card, Divider, List, Stack, Typography} from "@mui/material"; const text = "Lorem ipsum dolor sit amet consectetur. Nisi magna dolor et nisi nibh et velit phasellus. Aliquam semper justo posuere suspendisse amet amet nam nec. Tellus magna in proin tempor hac sit. Faucibus laoreet nulla commodo quis. Porttitor sit facilisis sit dignissim quis. Malesuada etiam tempor donec et ante. Aliquam massa donec augue aliquam semper amet blandit sed faucibus. Et elementum duis adipiscing turpis mi. Senectus eu rutrum accumsan convallis metus mattis risus. Quam eget sapien tellus aliquam facilisi sit volutpat. Scelerisque auctor purus nam sit lacus amet ullamcorper amet. Turpis nulla quis in pretium. Maecenas aliquam ac ullamcorper suspendisse morbi cras. Mi nibh aliquet massa sit eget tristique a. Posuere pretium auctor tellus massa et eu egestas. Sit lorem proin aenean tortor morbi condimentum. Leo eu enim cursus tempus sed viverra laoreet. Nisl ornare velit molestie suspendisse. Hendrerit nibh mauris vulputate sit vitae. Tellus quisque non nibh proin nunc lacus scelerisque dui. Aliquam fermentum libero aliquet volutpat at. Vestibulum ultrices nec felis leo nibh viverra. Hendrerit ut nunc porta egestas sit velit dictumst dis porta. Donec quam aliquam commodo mattis purus. Tellus nulla lectus fusce in fames scelerisque at." @@ -57,7 +57,7 @@ const deadline = "02/04/2024"; export function AssignmentStudentPage() { return ( <> -
+
@@ -91,11 +91,6 @@ export function AssignmentStudentPage() { {/*Indieningen*/} - - - Indiening - Datum + + Indiening + Tijdstip + Status @@ -115,19 +110,17 @@ export function AssignmentStudentPage() { {assignments.map((assignment) => ( - + + status={true} + isStudent={true}/> ))} - {/*Upload knop*/}
- ); diff --git a/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx b/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx index 286a02bd9..9c26d1bbf 100644 --- a/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx +++ b/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx @@ -1,6 +1,7 @@ import {Header} from "../../components/Header.tsx"; -import { Box, Button, Card, Divider, Grid, List, ListItem, ListItemText, Stack, TextField, Typography} from "@mui/material"; -import UploadIcon from '@mui/icons-material/Upload'; +import { AssignmentListItemTeacherPage } from "../../components/AssignmentListItemTeacherPage.tsx"; +import { Box, Button, Card, Divider, Grid, List, ListItem, ListItemText, ListSubheader, Stack, TextField, Typography} from "@mui/material"; +import DownloadIcon from '@mui/icons-material/Download'; import SaveIcon from '@mui/icons-material/Save'; import AddIcon from '@mui/icons-material/Add'; import {LocalizationProvider} from '@mui/x-date-pickers'; @@ -9,43 +10,60 @@ import {DatePicker} from '@mui/x-date-pickers/DatePicker'; const text = "Lorem ipsum dolor sit amet consectetur. Nisi magna dolor et nisi nibh et velit phasellus. Aliquam semper justo posuere suspendisse amet amet nam nec. Tellus magna in proin tempor hac sit. Faucibus laoreet nulla commodo quis. Porttitor sit facilisis sit dignissim quis. Malesuada etiam tempor donec et ante. Aliquam massa donec augue aliquam semper amet blandit sed faucibus. Et elementum duis adipiscing turpis mi. Senectus eu rutrum accumsan convallis metus mattis risus. Quam eget sapien tellus aliquam facilisi sit volutpat. Scelerisque auctor purus nam sit lacus amet ullamcorper amet. Turpis nulla quis in pretium. Maecenas aliquam ac ullamcorper suspendisse morbi cras. Mi nibh aliquet massa sit eget tristique a. Posuere pretium auctor tellus massa et eu egestas. Sit lorem proin aenean tortor morbi condimentum. Leo eu enim cursus tempus sed viverra laoreet. Nisl ornare velit molestie suspendisse. Hendrerit nibh mauris vulputate sit vitae. Tellus quisque non nibh proin nunc lacus scelerisque dui. Aliquam fermentum libero aliquet volutpat at. Vestibulum ultrices nec felis leo nibh viverra. Hendrerit ut nunc porta egestas sit velit dictumst dis porta. Donec quam aliquam commodo mattis purus. Tellus nulla lectus fusce in fames scelerisque at." -const restrictions = [ +const assignments = [ { - type: 'bestandstype', - details: '.pdf .zip', + id: '1', + name: '#1', + deadline: new Date(2024, 11, 17) }, { - type: 'bestandsgrootte', - details: '< 0.25 gb', + id: '2', + name: '#2', + deadline: new Date(2024, 10, 25) }, { - type: 'docker test', - details: 'filename', + id: '3', + name: '#3', + deadline: new Date(2024, 9, 30) }, -] - -interface restrictionProps { - type: string, - details: string, -} - -export function Restriction({type, details}: restrictionProps) { - return ( - <> - - {type} - {details} - - - ); -} + { + id: '4', + name: '#4', + deadline: new Date(2024, 8, 12) + }, + { + id: '5', + name: '#5', + deadline: new Date(2024, 7, 8) + }, + { + id: '6', + name: '#6', + deadline: new Date(2024, 6, 15) + }, + { + id: '7', + name: '#7', + deadline: new Date(2024, 5, 20) + }, + { + id: '8', + name: '#8', + deadline: new Date(2024, 4, 10) + }, + { + id: '9', + name: '#9', + deadline: new Date(2024, 3, 28) + } +]; const deadline = "02/04/2024"; export function AssignmentTeacherPage() { return ( <> -
+
@@ -71,6 +89,61 @@ export function AssignmentTeacherPage() { {text} + + {/*Indieningen*/} + + + Student + Tijdstip + Score + Download + + + + + {assignments.map((assignment) => ( + + + + + + + ))} + + + + + + {/*Upload knop*/} + + + +
+ + + + + ); From bfc91d9248a436735b39aee6a9132fdbf2824faa Mon Sep 17 00:00:00 2001 From: elias Date: Mon, 25 Mar 2024 08:15:22 +0100 Subject: [PATCH 056/268] opdracht leerkracht pagina nu statisch klaar --- .../AssigmentListItemStudentPage.tsx | 61 +++++++++++++ .../AssignmentListItemTeacherPage.tsx | 39 +++++++-- .../assignmentPage/assignmentStudentPage.tsx | 13 +-- .../assignmentPage/assignmentTeacherPage.tsx | 85 +++++++++++-------- 4 files changed, 149 insertions(+), 49 deletions(-) create mode 100644 frontend/frontend/src/components/AssigmentListItemStudentPage.tsx diff --git a/frontend/frontend/src/components/AssigmentListItemStudentPage.tsx b/frontend/frontend/src/components/AssigmentListItemStudentPage.tsx new file mode 100644 index 000000000..0838a4f31 --- /dev/null +++ b/frontend/frontend/src/components/AssigmentListItemStudentPage.tsx @@ -0,0 +1,61 @@ +import {ListItem, ListItemButton, ListItemIcon, ListItemText} from "@mui/material"; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import HighlightOffIcon from '@mui/icons-material/HighlightOff'; +import {useNavigate} from "react-router-dom"; +import {t} from "i18next"; + +interface AssignmentListItemStudentPageProps { + id: string; + studentName: string; + dueDate?: Date; + status: boolean; +} + +/* +* This component is used to display a single assignment in the list of assignments +* @param key: string - the key of the assignment +* @param projectName: string - the name of the project +* @param dueDate: Date - the due date of the project +* @param status: boolean - the status of the project +* @param isStudent: boolean - if the user is a student or a teacher +*/ + +export function AssignmentListItemStudentPage({id, studentName, dueDate, status}: AssignmentListItemStudentPageProps) { + const navigate = useNavigate(); + const handleProjectClick = () => { + console.log("Project clicked"); + navigate(`/${id}`) + } + + return ( + <> + + + + + + {status ? + () : + () + } + + + + + ); +} \ No newline at end of file diff --git a/frontend/frontend/src/components/AssignmentListItemTeacherPage.tsx b/frontend/frontend/src/components/AssignmentListItemTeacherPage.tsx index ed99c62be..550c7382a 100644 --- a/frontend/frontend/src/components/AssignmentListItemTeacherPage.tsx +++ b/frontend/frontend/src/components/AssignmentListItemTeacherPage.tsx @@ -1,4 +1,4 @@ -import {ListItem, ListItemButton, ListItemText, Divider} from "@mui/material"; +import {ListItem, ListItemText, Divider, ListItemIcon} from "@mui/material"; import {useNavigate} from "react-router-dom"; import DownloadIcon from '@mui/icons-material/Download'; import {t} from "i18next"; @@ -19,15 +19,19 @@ interface AssignmentListItemTeacherPageProps { export function AssignmentListItemTeacherPage({id,studentName,submitted, score}:AssignmentListItemTeacherPageProps) { const navigate = useNavigate(); - const handleProjectClick = () => { + const handleStudentClick = () => { console.log("Project clicked"); navigate(`/${id}`) } + const handleStudentDownloadClick = () => { + console.log("Project clicked"); + navigate(`/${id}/download/`) + } return ( <> - - - - - - + + + + + {submitted ? ( + + ) : ( + + )} + + diff --git a/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx b/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx index aa7402c3e..3f8e8d437 100644 --- a/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx +++ b/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx @@ -1,5 +1,5 @@ import {Header} from "../../components/Header.tsx"; -import {AssignmentListItem} from "../../components/AssignmentListItem.tsx"; +import {AssignmentListItemStudentPage} from "../../components/AssigmentListItemStudentPage.tsx"; import {Box, Button, Card, Divider, List, Stack, Typography} from "@mui/material"; const text = "Lorem ipsum dolor sit amet consectetur. Nisi magna dolor et nisi nibh et velit phasellus. Aliquam semper justo posuere suspendisse amet amet nam nec. Tellus magna in proin tempor hac sit. Faucibus laoreet nulla commodo quis. Porttitor sit facilisis sit dignissim quis. Malesuada etiam tempor donec et ante. Aliquam massa donec augue aliquam semper amet blandit sed faucibus. Et elementum duis adipiscing turpis mi. Senectus eu rutrum accumsan convallis metus mattis risus. Quam eget sapien tellus aliquam facilisi sit volutpat. Scelerisque auctor purus nam sit lacus amet ullamcorper amet. Turpis nulla quis in pretium. Maecenas aliquam ac ullamcorper suspendisse morbi cras. Mi nibh aliquet massa sit eget tristique a. Posuere pretium auctor tellus massa et eu egestas. Sit lorem proin aenean tortor morbi condimentum. Leo eu enim cursus tempus sed viverra laoreet. Nisl ornare velit molestie suspendisse. Hendrerit nibh mauris vulputate sit vitae. Tellus quisque non nibh proin nunc lacus scelerisque dui. Aliquam fermentum libero aliquet volutpat at. Vestibulum ultrices nec felis leo nibh viverra. Hendrerit ut nunc porta egestas sit velit dictumst dis porta. Donec quam aliquam commodo mattis purus. Tellus nulla lectus fusce in fames scelerisque at." @@ -111,11 +111,12 @@ export function AssignmentStudentPage() { - - + + ))} diff --git a/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx b/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx index 9c26d1bbf..29fd3d99b 100644 --- a/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx +++ b/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx @@ -10,52 +10,67 @@ import {DatePicker} from '@mui/x-date-pickers/DatePicker'; const text = "Lorem ipsum dolor sit amet consectetur. Nisi magna dolor et nisi nibh et velit phasellus. Aliquam semper justo posuere suspendisse amet amet nam nec. Tellus magna in proin tempor hac sit. Faucibus laoreet nulla commodo quis. Porttitor sit facilisis sit dignissim quis. Malesuada etiam tempor donec et ante. Aliquam massa donec augue aliquam semper amet blandit sed faucibus. Et elementum duis adipiscing turpis mi. Senectus eu rutrum accumsan convallis metus mattis risus. Quam eget sapien tellus aliquam facilisi sit volutpat. Scelerisque auctor purus nam sit lacus amet ullamcorper amet. Turpis nulla quis in pretium. Maecenas aliquam ac ullamcorper suspendisse morbi cras. Mi nibh aliquet massa sit eget tristique a. Posuere pretium auctor tellus massa et eu egestas. Sit lorem proin aenean tortor morbi condimentum. Leo eu enim cursus tempus sed viverra laoreet. Nisl ornare velit molestie suspendisse. Hendrerit nibh mauris vulputate sit vitae. Tellus quisque non nibh proin nunc lacus scelerisque dui. Aliquam fermentum libero aliquet volutpat at. Vestibulum ultrices nec felis leo nibh viverra. Hendrerit ut nunc porta egestas sit velit dictumst dis porta. Donec quam aliquam commodo mattis purus. Tellus nulla lectus fusce in fames scelerisque at." -const assignments = [ +const students = [ { - id: '1', - name: '#1', - deadline: new Date(2024, 11, 17) + id: '1', + name: 'Lucas', + submitted: new Date(2024, 11, 17), + score: 12, }, { - id: '2', - name: '#2', - deadline: new Date(2024, 10, 25) + id: '2', + name: 'Sophia', + submitted: undefined, + score: 15, }, { - id: '3', - name: '#3', - deadline: new Date(2024, 9, 30) + id: '3', + name: 'Ethan', + submitted: new Date(2024, 11, 19), + score: 18, }, { - id: '4', - name: '#4', - deadline: new Date(2024, 8, 12) + id: '4', + name: 'Emma', + submitted: new Date(2024, 11, 20), + score: 10, }, { - id: '5', - name: '#5', - deadline: new Date(2024, 7, 8) + id: '5', + name: 'Liam', + submitted: undefined, + score: 17, }, { - id: '6', - name: '#6', - deadline: new Date(2024, 6, 15) + id: '6', + name: 'Olivia', + submitted: new Date(2024, 11, 22), + score: 14, }, { - id: '7', - name: '#7', - deadline: new Date(2024, 5, 20) + id: '7', + name: 'Noah', + submitted: undefined, + score: 9, }, { - id: '8', - name: '#8', - deadline: new Date(2024, 4, 10) + id: '8', + name: 'Ava', + submitted: undefined, + score: 16, }, { - id: '9', - name: '#9', - deadline: new Date(2024, 3, 28) - } + id: '9', + name: 'Mia', + submitted: new Date(2024, 11, 25), + score: 11, + }, + { + id: '10', + name: 'William', + submitted: undefined, + score: 19, + }, ]; const deadline = "02/04/2024"; @@ -108,15 +123,15 @@ export function AssignmentTeacherPage() { - {assignments.map((assignment) => ( - + {students.map((student) => ( + From fa22c3ec210f11d662989a7540818693cedf1655 Mon Sep 17 00:00:00 2001 From: elias Date: Mon, 25 Mar 2024 08:38:54 +0100 Subject: [PATCH 057/268] group pagina beter implementeren --- ....tsx => AssignmentListItemStudentPage.tsx} | 0 .../frontend/src/components/GroupListItem.tsx | 49 +++++++++ .../assignmentPage/assignmentStudentPage.tsx | 2 +- .../src/pages/groupsPage/groupsPage.tsx | 103 +++++++++--------- 4 files changed, 104 insertions(+), 50 deletions(-) rename frontend/frontend/src/components/{AssigmentListItemStudentPage.tsx => AssignmentListItemStudentPage.tsx} (100%) create mode 100644 frontend/frontend/src/components/GroupListItem.tsx diff --git a/frontend/frontend/src/components/AssigmentListItemStudentPage.tsx b/frontend/frontend/src/components/AssignmentListItemStudentPage.tsx similarity index 100% rename from frontend/frontend/src/components/AssigmentListItemStudentPage.tsx rename to frontend/frontend/src/components/AssignmentListItemStudentPage.tsx diff --git a/frontend/frontend/src/components/GroupListItem.tsx b/frontend/frontend/src/components/GroupListItem.tsx new file mode 100644 index 000000000..fccf6b076 --- /dev/null +++ b/frontend/frontend/src/components/GroupListItem.tsx @@ -0,0 +1,49 @@ +import {Card,ListItem, ListItemText} from "@mui/material"; +import {useNavigate} from "react-router-dom"; +import {t} from "i18next"; + +interface GroupListItemProps { + id: string; + studentName: string; + groupMemberNames: string[]; +} + +/* +* This component is used to display a single assignment in the list of assignments +* @param id: string - the id of the assignment +* @param studentName: string - the name of the student +* @param groupMemberNames: [string] the names of the groupmembers +*/ + +export function GroupListItem({id, studentName, groupMemberNames}: GroupListItemProps) { + const navigate = useNavigate(); + + return ( + <> + + + + + + + + + + ); +} \ No newline at end of file diff --git a/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx b/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx index 3f8e8d437..ef70ba15d 100644 --- a/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx +++ b/frontend/frontend/src/pages/assignmentPage/assignmentStudentPage.tsx @@ -1,5 +1,5 @@ import {Header} from "../../components/Header.tsx"; -import {AssignmentListItemStudentPage} from "../../components/AssigmentListItemStudentPage.tsx"; +import {AssignmentListItemStudentPage} from "../../components/AssignmentListItemStudentPage.tsx"; import {Box, Button, Card, Divider, List, Stack, Typography} from "@mui/material"; const text = "Lorem ipsum dolor sit amet consectetur. Nisi magna dolor et nisi nibh et velit phasellus. Aliquam semper justo posuere suspendisse amet amet nam nec. Tellus magna in proin tempor hac sit. Faucibus laoreet nulla commodo quis. Porttitor sit facilisis sit dignissim quis. Malesuada etiam tempor donec et ante. Aliquam massa donec augue aliquam semper amet blandit sed faucibus. Et elementum duis adipiscing turpis mi. Senectus eu rutrum accumsan convallis metus mattis risus. Quam eget sapien tellus aliquam facilisi sit volutpat. Scelerisque auctor purus nam sit lacus amet ullamcorper amet. Turpis nulla quis in pretium. Maecenas aliquam ac ullamcorper suspendisse morbi cras. Mi nibh aliquet massa sit eget tristique a. Posuere pretium auctor tellus massa et eu egestas. Sit lorem proin aenean tortor morbi condimentum. Leo eu enim cursus tempus sed viverra laoreet. Nisl ornare velit molestie suspendisse. Hendrerit nibh mauris vulputate sit vitae. Tellus quisque non nibh proin nunc lacus scelerisque dui. Aliquam fermentum libero aliquet volutpat at. Vestibulum ultrices nec felis leo nibh viverra. Hendrerit ut nunc porta egestas sit velit dictumst dis porta. Donec quam aliquam commodo mattis purus. Tellus nulla lectus fusce in fames scelerisque at." diff --git a/frontend/frontend/src/pages/groupsPage/groupsPage.tsx b/frontend/frontend/src/pages/groupsPage/groupsPage.tsx index 1a63cd132..d135c1e8c 100644 --- a/frontend/frontend/src/pages/groupsPage/groupsPage.tsx +++ b/frontend/frontend/src/pages/groupsPage/groupsPage.tsx @@ -1,67 +1,67 @@ import {Header} from "../../components/Header.tsx"; import {Box, Card, Divider, Grid, List, ListItem, ListItemText, Stack, TextField, Typography} from "@mui/material"; import Switch from '@mui/material/Switch'; - +import { GroupListItem } from "../../components/GroupListItem.tsx"; const groups = [ { - name: 'Jane' + id: '1', + name: 'Jane', + members: ['Josh', 'Alice'] }, { - name: 'John' + id: '2', + name: 'John', + members: ['Emily', 'Michael'] }, { - name: 'Alice' + id: '3', + name: 'Emma', + members: ['James', 'Olivia'] }, { - name: 'Bob' + id: '4', + name: 'Jack', + members: ['Sophia', 'William'] }, { - name: 'Emily' + id: '5', + name: 'Ava', + members: ['Alexander', 'Charlotte'] }, { - name: 'David' + id: '6', + name: 'Liam', + members: ['Grace', 'Henry'] }, { - name: 'Sophia' + id: '7', + name: 'Ella', + members: ['Daniel', 'Chloe'] }, { - name: 'Michael' + id: '8', + name: 'Noah', + members: ['Mia', 'Samuel'] }, { - name: 'Olivia' + id: '9', + name: 'Sophie', + members: ['Ethan', 'Madison'] }, { - name: 'William' + id: '10', + name: 'Luke', + members: ['Avery', 'Benjamin'] }, + { + id: '11', + name: 'Mila', + members: ['Jackson', 'Victoria'] + } ]; - - -interface GroupListItemProps { - name: string, -} - -export function GroupListItem({name}: GroupListItemProps) { - return ( - <> - - - {name} - student1, student2.. - - - - ); -} - - + + export function GroupsPage() { return ( <> @@ -99,7 +99,7 @@ export function GroupsPage() { - + - :not(style)': {marginBottom: '8px', width: "150vh"}}}> - - Naam Student - Ingeschreven groep - - + + + Indiening + Ingeschreven groep + + + - {groups.map((res) => - - )} + {groups.map((group) => + + )} + - ); From f82d96c6475ae27eef3de35d41913e1f6ea6cdd3 Mon Sep 17 00:00:00 2001 From: elias Date: Mon, 25 Mar 2024 10:26:00 +0100 Subject: [PATCH 058/268] groepen pagina klaar --- frontend/frontend/src/components/GroupListItem.tsx | 6 ++++-- frontend/frontend/src/pages/groupsPage/groupsPage.tsx | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/frontend/src/components/GroupListItem.tsx b/frontend/frontend/src/components/GroupListItem.tsx index fccf6b076..b89277f01 100644 --- a/frontend/frontend/src/components/GroupListItem.tsx +++ b/frontend/frontend/src/components/GroupListItem.tsx @@ -1,4 +1,4 @@ -import {Card,ListItem, ListItemText} from "@mui/material"; +import {Card,ListItem, ListItemText, Select} from "@mui/material"; import {useNavigate} from "react-router-dom"; import {t} from "i18next"; @@ -40,7 +40,9 @@ export function GroupListItem({id, studentName, groupMemberNames}: GroupListItem borderRadius: 2, }}> - + diff --git a/frontend/frontend/src/pages/groupsPage/groupsPage.tsx b/frontend/frontend/src/pages/groupsPage/groupsPage.tsx index d135c1e8c..db0ece0a4 100644 --- a/frontend/frontend/src/pages/groupsPage/groupsPage.tsx +++ b/frontend/frontend/src/pages/groupsPage/groupsPage.tsx @@ -1,5 +1,5 @@ import {Header} from "../../components/Header.tsx"; -import {Box, Card, Divider, Grid, List, ListItem, ListItemText, Stack, TextField, Typography} from "@mui/material"; +import {Box, Divider, Grid, List, Stack, TextField, Typography} from "@mui/material"; import Switch from '@mui/material/Switch'; import { GroupListItem } from "../../components/GroupListItem.tsx"; @@ -7,7 +7,7 @@ const groups = [ { id: '1', name: 'Jane', - members: ['Josh', 'Alice'] + members: ['Josh', 'Alice','Joost','Michiel','Andy'] }, { id: '2', From bf3073a42afc23d40705f895ef627b9d34ade06d Mon Sep 17 00:00:00 2001 From: Alexandre Paice Date: Mon, 25 Mar 2024 15:31:00 +0100 Subject: [PATCH 059/268] integratie start --- .../src/components/AssignmentListItem.tsx | 23 +++ .../frontend/src/components/CourseCard.tsx | 191 +++++------------- .../src/pages/mainPage/CoursesView.tsx | 31 +-- 3 files changed, 93 insertions(+), 152 deletions(-) diff --git a/frontend/frontend/src/components/AssignmentListItem.tsx b/frontend/frontend/src/components/AssignmentListItem.tsx index d1b63a66c..c4fb2fb65 100644 --- a/frontend/frontend/src/components/AssignmentListItem.tsx +++ b/frontend/frontend/src/components/AssignmentListItem.tsx @@ -3,6 +3,8 @@ import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; import HighlightOffIcon from '@mui/icons-material/HighlightOff'; import {useNavigate} from "react-router-dom"; import {t} from "i18next"; +import { useState, useEffect } from "react"; +import axios from "axios"; interface AssignmentListItemProps { id: string; @@ -23,6 +25,27 @@ interface AssignmentListItemProps { export function AssignmentListItem({id, projectName, dueDate, status, isStudent}: AssignmentListItemProps) { const navigate = useNavigate(); + const [stat, setStatus] = useState(false); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + // Make API request to fetch status + const response = await axios.get(`https://sel2-4.ugent.be/api/projecten/${id}`); + setStatus(response.data.status); + setLoading(false); + } catch (error) { + const errorMessage = (error as Error).message; + setError(errorMessage); + setLoading(false); + } + }; + + fetchData(); + }, [id]); + const handleProjectClick = () => { console.log("Project clicked"); navigate(`/${id}`) diff --git a/frontend/frontend/src/components/CourseCard.tsx b/frontend/frontend/src/components/CourseCard.tsx index 0faa2998d..20f04f2ff 100644 --- a/frontend/frontend/src/components/CourseCard.tsx +++ b/frontend/frontend/src/components/CourseCard.tsx @@ -4,6 +4,8 @@ import List from '@mui/material/List'; import {AssignmentListItem} from "./AssignmentListItem"; import {useNavigate} from "react-router-dom"; import ArchiveOutlinedIcon from '@mui/icons-material/ArchiveOutlined'; +import { useState, useEffect } from "react"; +import axios from "axios"; /* * CourseCard component displays a card with course information and a list of assignments * @param courseId: string, the id of the course @@ -36,9 +38,36 @@ interface Assignment { } export function CourseCard({courseId, archived, isStudent}: CourseCardProps) { - const course = getCourse(courseId) - const assignments = course.assignments.map((assignmentId) => getAssignment(assignmentId)) - const navigate = useNavigate(); + const [course, setCourse] = useState(null); + const [assignments, setAssignments] = useState([]); + const navigate = useNavigate(); + + useEffect(() => { + const fetchCourse = async () => { + try { + const response = await axios.get(`https://sel2-4.ugent.be/api/vakken/${courseId}`); + setCourse(response.data); + } catch (error) { + console.error("Error fetching course:", error); + } + }; + + const fetchAssignments = async () => { + try { + const assignmentPromises = course?.assignments.map(async (assignmentId: string) => { + const response = await axios.get(`https://sel2-4.ugent.be/api/projecten/${assignmentId}`); + return response.data; + }); + + const assignmentData = await Promise.all(assignmentPromises || []); + setAssignments(assignmentData); + } catch (error) { + console.error("Error fetching assignments:", error); + } + }; + + fetchCourse(); + }, [courseId]); const handleCardClick = () => { console.log("Card clicked"); @@ -50,146 +79,28 @@ export function CourseCard({courseId, archived, isStudent}: CourseCardProps) { //update db } - return ( + return ( <> - - - - - - {course.name} - {course.teacher} - - - {t("students")}{course.students.length} + {course && ( + + + + + + {course.name} + {course.teacher} + + + {t("students")}{course.students.length} + + + + {/* Assignment list rendering */} - - - {isStudent ? - - Project - Deadline - Status - : - <> - {archived ? - - Project - Deadline - - : - - Project - Deadline - - } - } - - - {isStudent ? - - - {assignments.map((assignment) => ( - - ))} - - : - <>{!archived ? - - - {assignments.map((assignment) => ( - - ))} - - : - - - {assignments.map((assignment) => ( - - ))} - - - } - {!archived && - - - } - - } - - - - + + + )} ); } - -//TODO: use api to get data, for now use mock data -function getCourse(courseId: string): Course { - return { - id: courseId, - name: "courseName", - teacher: "teacher", - students: ["student1", "student2"], - archived: false, - assignments: ["assignment1", "assignment2"] - } -} - -function getAssignment(assignmentId: string): Assignment { - return { - id: assignmentId, - name: "assignmentName", - deadline: new Date(2022, 11, 17) - } -} \ No newline at end of file diff --git a/frontend/frontend/src/pages/mainPage/CoursesView.tsx b/frontend/frontend/src/pages/mainPage/CoursesView.tsx index 96fbb7f78..8bdc43aba 100644 --- a/frontend/frontend/src/pages/mainPage/CoursesView.tsx +++ b/frontend/frontend/src/pages/mainPage/CoursesView.tsx @@ -1,14 +1,27 @@ import {IconButton, Stack} from "@mui/material"; import {CourseCard} from "../../components/CourseCard.tsx"; import AddIcon from "@mui/icons-material/Add"; - +import axios from "axios"; +import { useEffect, useState } from "react"; interface CourseCardProps { isStudent: boolean; } export function CoursesView({isStudent}: CourseCardProps) { - //TODO: get courses from state - const courses = getCourses(); + const [courses, setCourses] = useState([]); + + useEffect(() => { + async function fetchData() { + try { + const response = await axios.get("https://sel2-4.ugent.be/api/vakken/"); + setCourses(response.data); + } catch (error) { + console.error("Error fetching courses:", error); + } + } + + fetchData(); + }, []); return ( <> @@ -18,10 +31,9 @@ export function CoursesView({isStudent}: CourseCardProps) { overflowY: {sm: "auto"}, maxHeight: "65vh", }}> - - - - + {courses.map((course: any) => ( + + ))} {!isStudent && @@ -34,8 +46,3 @@ export function CoursesView({isStudent}: CourseCardProps) { ); } - -//fix courses with state -function getCourses(): string[] { - return [] -} \ No newline at end of file From 99a0a5c7069ddff6ff065efdde47f6673f3edfe5 Mon Sep 17 00:00:00 2001 From: Alexandre Paice Date: Mon, 25 Mar 2024 16:58:13 +0100 Subject: [PATCH 060/268] axiosConfig --- frontend/frontend/src/axiosConfig.ts | 29 ++++++++++++++++ .../src/components/AssignmentListItem.tsx | 5 ++- .../frontend/src/components/CourseCard.tsx | 34 ++++--------------- .../src/pages/mainPage/CoursesView.tsx | 4 +-- 4 files changed, 40 insertions(+), 32 deletions(-) create mode 100644 frontend/frontend/src/axiosConfig.ts diff --git a/frontend/frontend/src/axiosConfig.ts b/frontend/frontend/src/axiosConfig.ts new file mode 100644 index 000000000..248355ca5 --- /dev/null +++ b/frontend/frontend/src/axiosConfig.ts @@ -0,0 +1,29 @@ +import axios from 'axios'; + + +const instance = axios.create({ + baseURL: 'https://sel2-4.ugent.be/api/', + headers: {'Content-Type': 'application/json'} +}); + +instance.defaults.headers.common['Authorization'] = 'AUTH TOKEN FROM INSTANCE'; + +instance.interceptors.request.use((request) => { + console.log(request); + return request; +}); + +instance.interceptors.response.use((response) => { + console.log(response); + return response +}); + +export function getVakken() { // mogelijk/nodig? + return instance.get('/vakken'); +} + +export function getProjecten() { + return instance.get('/projecten'); +} + +export default instance; \ No newline at end of file diff --git a/frontend/frontend/src/components/AssignmentListItem.tsx b/frontend/frontend/src/components/AssignmentListItem.tsx index c4fb2fb65..47cca0872 100644 --- a/frontend/frontend/src/components/AssignmentListItem.tsx +++ b/frontend/frontend/src/components/AssignmentListItem.tsx @@ -4,7 +4,7 @@ import HighlightOffIcon from '@mui/icons-material/HighlightOff'; import {useNavigate} from "react-router-dom"; import {t} from "i18next"; import { useState, useEffect } from "react"; -import axios from "axios"; +import axios from "../axiosConfig"; interface AssignmentListItemProps { id: string; @@ -32,8 +32,7 @@ export function AssignmentListItem({id, projectName, dueDate, status, isStudent} useEffect(() => { const fetchData = async () => { try { - // Make API request to fetch status - const response = await axios.get(`https://sel2-4.ugent.be/api/projecten/${id}`); + const response = await axios.get(`/projecten/${id}`); setStatus(response.data.status); setLoading(false); } catch (error) { diff --git a/frontend/frontend/src/components/CourseCard.tsx b/frontend/frontend/src/components/CourseCard.tsx index 20f04f2ff..67c57832a 100644 --- a/frontend/frontend/src/components/CourseCard.tsx +++ b/frontend/frontend/src/components/CourseCard.tsx @@ -5,7 +5,7 @@ import {AssignmentListItem} from "./AssignmentListItem"; import {useNavigate} from "react-router-dom"; import ArchiveOutlinedIcon from '@mui/icons-material/ArchiveOutlined'; import { useState, useEffect } from "react"; -import axios from "axios"; +import axios, { getProjecten, getVakken } from '../axiosConfig'; /* * CourseCard component displays a card with course information and a list of assignments * @param courseId: string, the id of the course @@ -42,32 +42,12 @@ export function CourseCard({courseId, archived, isStudent}: CourseCardProps) { const [assignments, setAssignments] = useState([]); const navigate = useNavigate(); - useEffect(() => { - const fetchCourse = async () => { - try { - const response = await axios.get(`https://sel2-4.ugent.be/api/vakken/${courseId}`); - setCourse(response.data); - } catch (error) { - console.error("Error fetching course:", error); - } - }; - - const fetchAssignments = async () => { - try { - const assignmentPromises = course?.assignments.map(async (assignmentId: string) => { - const response = await axios.get(`https://sel2-4.ugent.be/api/projecten/${assignmentId}`); - return response.data; - }); - - const assignmentData = await Promise.all(assignmentPromises || []); - setAssignments(assignmentData); - } catch (error) { - console.error("Error fetching assignments:", error); - } - }; - - fetchCourse(); - }, [courseId]); + // hier zijn nog problemen bij + Promise.all([getVakken(), getProjecten()]) + .then(function (results) { + const course = results[0]; + const assignments = results[1]; + }); const handleCardClick = () => { console.log("Card clicked"); diff --git a/frontend/frontend/src/pages/mainPage/CoursesView.tsx b/frontend/frontend/src/pages/mainPage/CoursesView.tsx index 8bdc43aba..6763a31c2 100644 --- a/frontend/frontend/src/pages/mainPage/CoursesView.tsx +++ b/frontend/frontend/src/pages/mainPage/CoursesView.tsx @@ -1,7 +1,7 @@ import {IconButton, Stack} from "@mui/material"; import {CourseCard} from "../../components/CourseCard.tsx"; import AddIcon from "@mui/icons-material/Add"; -import axios from "axios"; +import axios from "../../axiosConfig.ts"; import { useEffect, useState } from "react"; interface CourseCardProps { isStudent: boolean; @@ -13,7 +13,7 @@ export function CoursesView({isStudent}: CourseCardProps) { useEffect(() => { async function fetchData() { try { - const response = await axios.get("https://sel2-4.ugent.be/api/vakken/"); + const response = await axios.get("/vakken/"); setCourses(response.data); } catch (error) { console.error("Error fetching courses:", error); From 12816bf40880539ac72bfb0ebca368ea1899358a Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke Date: Tue, 26 Mar 2024 16:29:22 +0100 Subject: [PATCH 061/268] upload scores knop --- frontend/frontend/src/i18n/en.ts | 1 + frontend/frontend/src/i18n/nl.ts | 1 + frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index fdfc4c8b2..50a3bf7b3 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -24,6 +24,7 @@ const english = { time: "Time", no_submissions: "No submissions", edit: "Edit", + upload_scores: "Upload Scores" }; export default english; \ No newline at end of file diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts index ce3a4bae2..044af3823 100644 --- a/frontend/frontend/src/i18n/nl.ts +++ b/frontend/frontend/src/i18n/nl.ts @@ -24,6 +24,7 @@ const dutch = { time: "Tijdstip", no_submissions: "Geen indieningen", edit: "Aanpassen", + upload_scores: "Upload Scores" }; export default dutch; \ No newline at end of file diff --git a/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx b/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx index b914b7a1a..68945e318 100644 --- a/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx +++ b/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx @@ -16,6 +16,10 @@ export function ProjectScoresPage() { console.log("export submissions"); } + const uploadScores = () => { + console.log("upload scores"); + } + const saveScores = () => { console.log("save scores"); navigate("/assignment_teacher"); @@ -36,6 +40,7 @@ export function ProjectScoresPage() { + +// +// +// {"delete student?"} +// {"this can not be undone"} +// +// +// +// +// +// +// + +export function AddChangeSubjectPage() { + // State for the different fields of the subject + const [title, setTitle] = useState(""); + const [num, setNum] = useState(""); + const students = [1,2,3].map((id) => getstudent(id)); + const teachers = [1,2,3].map((id) => getteacher(id)); + + + + + return (<> + +
+ + + + + + + subject name: + setTitle(event.target.value)}/> + + + + students: + + :not(style)': {marginBottom: '8px', width: "150vh"}}}> + {students.map((id) => { + const [open, setOpen] = useState(false); + + const handleClickOpen = () => { + setOpen(true); + }; + const handleClose = (value: string) => { + setOpen(false); + setSelectedValue(value); + }; + + return (<> + + + + + + + + + {"delete student?"} + {"this can not be undone"} + + + + + + + + + + + + )})} + + + + + setNum(event.target.value)}/> + + + + + + + + + teachers: + + {teachers.map((id) => ( + <> + + + + + + ))} + + + + + + ); +} + +function getstudent(id: number): Student { + return { + id: id, + studentnumber: 123456, + name: "naam" + } +} + +function getteacher(id: number): Teacher { + return { + id: id, + name: "teacher" + } +} From c06608f95fb692ed353712bc4f41d1ed4968604a Mon Sep 17 00:00:00 2001 From: Robbe Date: Wed, 27 Mar 2024 20:35:33 +0100 Subject: [PATCH 064/268] made popup look better --- .../frontend/src/pages/SubjectsPage/AddChangeSubjectPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/frontend/src/pages/SubjectsPage/AddChangeSubjectPage.tsx b/frontend/frontend/src/pages/SubjectsPage/AddChangeSubjectPage.tsx index 4ac002812..48ff4fa37 100644 --- a/frontend/frontend/src/pages/SubjectsPage/AddChangeSubjectPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/AddChangeSubjectPage.tsx @@ -72,7 +72,7 @@ export function AddChangeSubjectPage() { students: - :not(style)': {marginBottom: '8px', width: "150vh"}}}> + :not(style)': {marginBottom: '8px', width: "75vw"}}}> {students.map((id) => { const [open, setOpen] = useState(false); @@ -102,7 +102,7 @@ export function AddChangeSubjectPage() { - + {"delete student?"} {"this can not be undone"} From 6c021afbb605c81492142d86af12217569d059e8 Mon Sep 17 00:00:00 2001 From: Robbe Date: Wed, 27 Mar 2024 20:39:04 +0100 Subject: [PATCH 065/268] made teachers match students look --- .../SubjectsPage/AddChangeSubjectPage.tsx | 85 ++++++++++++++----- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/frontend/frontend/src/pages/SubjectsPage/AddChangeSubjectPage.tsx b/frontend/frontend/src/pages/SubjectsPage/AddChangeSubjectPage.tsx index 48ff4fa37..7b9aaab6e 100644 --- a/frontend/frontend/src/pages/SubjectsPage/AddChangeSubjectPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/AddChangeSubjectPage.tsx @@ -139,27 +139,72 @@ export function AddChangeSubjectPage() { - + teachers: - - {teachers.map((id) => ( - <> - - - - - - ))} - + + :not(style)': {marginBottom: '8px', width: "75vw"}}}> + {teachers.map((id) => { + const [open, setOpen] = useState(false); + + const handleClickOpen = () => { + setOpen(true); + }; + const handleClose = (value: string) => { + setOpen(false); + setSelectedValue(value); + }; + + return (<> + + + + + + + + {"delete student?"} + {"this can not be undone"} + + + + + + + + + + + + )})} + + + + + setNum(event.target.value)}/> + + + + From eceb51599c8624504063ed4cf21578f7809e1008 Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke Date: Thu, 28 Mar 2024 16:57:48 +0100 Subject: [PATCH 066/268] data modellen frontend synchroniseren met modellen backend --- .../AssignmentListItemSubjectsPage.tsx | 38 +++- .../src/components/StudentScoreListItem.tsx | 26 ++- frontend/frontend/src/i18n/en.ts | 4 +- frontend/frontend/src/i18n/nl.ts | 4 +- .../src/pages/SubjectsPage/ProjectsView.tsx | 111 +++++++++-- .../SubjectsPage/SubjectsStudentPage.tsx | 151 ++++++++++---- .../SubjectsPage/SubjectsTeacherPage.tsx | 185 +++++++++++++----- .../src/pages/scoresPage/StudentsView.tsx | 144 ++++++++++---- 8 files changed, 506 insertions(+), 157 deletions(-) diff --git a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx index 5e2206975..9293403c2 100644 --- a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx +++ b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx @@ -10,8 +10,8 @@ import { useState } from "react"; interface AssignmentListItemSubjectsPageProps { projectName: string; dueDate?: Date; - submissions: number; - score: number; + submission: Indiening; + score: Score; isStudent: boolean; archived: boolean; visible: boolean; @@ -20,17 +20,41 @@ interface AssignmentListItemSubjectsPageProps { visibilityEvent: () => void; } +interface Indiening { + indiening_id: number, + groep: number, + tijdstip: Date, + status: boolean, + indiening_bestanden: Bestand[], + } + + interface Bestand { + indiening_bestand_id: number, + indiening: number, + bestand: File | null, + } + + interface Score { + score_id: number, + score: number, + indiening: number, +} + /* * This component is used to display a single assignment in the list of assignments -* @param key: string - the key of the assignment * @param projectName: string - the name of the project * @param dueDate: Date - the due date of the project * @param submissions: number - number of submissions for the project * @param score: number - assigned score on the project -* @param isStudent: boolean - if the user is a student or a teacher +* @param isStudent: boolean - wether the user is a student or a teacher +* @param archived: boolean - wether the assignment is archived +* @param visible: boolean - wether the assignment is visible +* @param deleteEvent: () => void - event to call to delete assignment +* @param archiveEvent: () => void - event to call to archive assignment +* @param visibilityEvent: () => void - event to call to change visibility of assignment */ -export function AssignmentListItemSubjectsPage({projectName, dueDate, submissions, score, isStudent, archived, visible, +export function AssignmentListItemSubjectsPage({projectName, dueDate, submission, score, isStudent, archived, visible, deleteEvent, archiveEvent, visibilityEvent}:AssignmentListItemSubjectsPageProps) { const navigate = useNavigate(); const handleProjectClick = () => { @@ -59,8 +83,8 @@ export function AssignmentListItemSubjectsPage({projectName, dueDate, submission <> - - + + : <> diff --git a/frontend/frontend/src/components/StudentScoreListItem.tsx b/frontend/frontend/src/components/StudentScoreListItem.tsx index 0ba4466ce..0677a289a 100644 --- a/frontend/frontend/src/components/StudentScoreListItem.tsx +++ b/frontend/frontend/src/components/StudentScoreListItem.tsx @@ -3,11 +3,19 @@ import DownloadIcon from '@mui/icons-material/Download'; import { t } from "i18next"; interface StudentScoreListItemProps { - key: string; - studentName: string; - submissionFiles: string[]; + key: number; + groepName: string; + submissionFiles: Bestand[]; + score: number; + maxScore: number; } +interface Bestand { + indiening_bestand_id: number, + indiening: number, + bestand: File | null, + } + /* * This component is used to display a single assignment in the list of assignments * @param key: string - the key of the studentOnProject @@ -15,10 +23,10 @@ interface StudentScoreListItemProps { * @param submissionFiles: string[] - a list of all files submitted by this student */ -export function StudentScoreListItem({key, studentName, submissionFiles}: StudentScoreListItemProps) { +export function StudentScoreListItem({key, groepName, submissionFiles, score, maxScore}: StudentScoreListItemProps) { return ( <> - + <> - - + - - + + diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index 50a3bf7b3..3f3160c60 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -24,7 +24,9 @@ const english = { time: "Time", no_submissions: "No submissions", edit: "Edit", - upload_scores: "Upload Scores" + upload_scores: "Upload Scores", + group: "Group", + last_submission: "Last Submission:", }; export default english; \ No newline at end of file diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts index 044af3823..8bd8d6ea8 100644 --- a/frontend/frontend/src/i18n/nl.ts +++ b/frontend/frontend/src/i18n/nl.ts @@ -24,7 +24,9 @@ const dutch = { time: "Tijdstip", no_submissions: "Geen indieningen", edit: "Aanpassen", - upload_scores: "Upload Scores" + upload_scores: "Upload Scores", + group: "Groep", + last_submission: "Laatste Indiening:", }; export default dutch; \ No newline at end of file diff --git a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx index 035b5f34c..27c1b7f47 100644 --- a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx @@ -4,25 +4,66 @@ import {t} from "i18next"; import {AssignmentListItemSubjectsPage} from "../../components/AssignmentListItemSubjectsPage.tsx"; interface ProjectsViewProps { - isStudent: boolean; + gebruiker: Gebruiker; archived: boolean; - assignments: Assignment[]; + assignments: Project[]; deleteAssignment: (index: number) => void; archiveAssignment: (index: number) => void; changeVisibilityAssignment: (index: number) => void; } -interface Assignment { - id: string; - name: string; - deadline?: Date; - submissions: number; - score: number; - visible: boolean; - archived: boolean; +interface Project { + project_id: number, + titel: string, + beschrijving: string, + opgave_bestand: File | null, + vak: number, + max_score: number, + deadline: Date, + extra_deadline: Date, + zichtbaar: boolean, + gearchiveerd: boolean, +} + +interface Groep { + groep_id: number, + studenten: number[], + project: number, +} + +interface Score { + score_id: number, + score: number, + indiening: number, +} + +interface Gebruiker { + user: number, + is_lesgever: boolean, + first_name: string, + last_name: string, + email: string, } -export function ProjectsView({isStudent, archived, assignments, deleteAssignment, archiveAssignment, changeVisibilityAssignment}: ProjectsViewProps) { +interface Indiening { + indiening_id: number, + groep: number, + tijdstip: Date, + status: boolean, + indiening_bestanden: Bestand[], + } + + interface Bestand { + indiening_bestand_id: number, + indiening: number, + bestand: File | null, + } + +export function ProjectsView({gebruiker, archived, assignments, deleteAssignment, archiveAssignment, changeVisibilityAssignment}: ProjectsViewProps) { + const groups = assignments.map((assignment) => getGroepVanStudentVoorProject(gebruiker.user, assignment.project_id)); + const submissions = groups.map((group) => getLaatseIndieningVanGroep(group.groep_id)); + const scores = submissions.map((submission) => getScoreVoorIndiening(submission.indiening_id)); + return ( <> - {isStudent? + {!gebruiker.is_lesgever? <> Project Deadline @@ -63,11 +104,12 @@ export function ProjectsView({isStudent, archived, assignments, deleteAssignment {assignments .map((assignment, index) => ({...assignment, index})) - .filter((assignment) => assignment.archived == archived) + .filter((assignment) => assignment.gearchiveerd == archived) .map((assignment) => ( - deleteAssignment(assignment.index)} archiveEvent={() => archiveAssignment(assignment.index)} visibilityEvent={() => changeVisibilityAssignment(assignment.index)}/> @@ -78,4 +120,41 @@ export function ProjectsView({isStudent, archived, assignments, deleteAssignment ); +} + +function getGroepVanStudentVoorProject(gebruikerId: number, projectId: number): Groep { + return { + groep_id: 0, + studenten: [gebruikerId, 1, 2, 3], + project: projectId, + } +} + +function getLaatseIndieningVanGroep(groepId: number): Indiening { + return { + indiening_id: 0, + groep: groepId, + tijdstip: new Date(2022, 11, 17), + status: true, + indiening_bestanden: [ + { + indiening_bestand_id: 0, + indiening: 0, + bestand: null, + }, + { + indiening_bestand_id: 1, + indiening: 0, + bestand: null, + } + ], + } +} + +function getScoreVoorIndiening(indieningId: number): Score { + return { + score_id: 0, + score: 10, + indiening: indieningId, + } } \ No newline at end of file diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx index f7bdf97e1..cfe403534 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx @@ -4,33 +4,43 @@ import TabSwitcher from "../../components/TabSwitcher.tsx"; import {ProjectsView} from "./ProjectsView.tsx"; import { useParams } from "react-router-dom"; -interface Course { - id: string; - name: string; - teacher: string; - students: string[]; - //list of assignment ids - assignments: string[]; - archived: boolean; +interface Vak { + vak_id: number, + naam: string, + studenten: number[], + lesgevers: number[], } -interface Assignment { - id: string; - name: string; - deadline?: Date; - submissions: number; - score: number; - visible: boolean; - archived: boolean; +interface Project { + project_id: number, + titel: string, + beschrijving: string, + opgave_bestand: File | null, + vak: number, + max_score: number, + deadline: Date, + extra_deadline: Date, + zichtbaar: boolean, + gearchiveerd: boolean, +} + +interface Gebruiker { + user: number, + is_lesgever: boolean, + first_name: string, + last_name: string, + email: string, } export function SubjectsStudentPage() { let { courseId } = useParams(); courseId = String(courseId); - const course = getCourse(courseId); + const course = getVak(Number(courseId)); - const assignments = course.assignments.map((assignmentId) => getAssignment(assignmentId)); + const assignments = getProjectenVoorVak(course.vak_id); + + const user = getGebruiker(); return ( <> @@ -38,10 +48,10 @@ export function SubjectsStudentPage() {
undefined} archiveAssignment={() => undefined} changeVisibilityAssignment={() => undefined}/>, - undefined} archiveAssignment={() => undefined} changeVisibilityAssignment={() => undefined}/>]}/> @@ -51,25 +61,96 @@ export function SubjectsStudentPage() { } //TODO: use api to get data, for now use mock data -function getCourse(courseId: string): Course { +function getVak(courseId: number): Vak { return { - id: courseId, - name: "courseName", - teacher: "teacher", - students: ["student1", "student2"], - archived: false, - assignments: ["assignment1", "assignment2", "assignment3", "assignment4", "assignment5", "assignment6", "assignment7", "assignment8", "assignment9"] + vak_id: courseId, + naam: "courseName", + studenten: [0, 1, 2, 3], + lesgevers: [0, 1], } } -function getAssignment(assignmentId: string): Assignment { +function getGebruiker(): Gebruiker { return { - id: assignmentId, - name: assignmentId, - deadline: new Date(2022, 11, 17), - submissions: 2, - score: 10, - visible: true, - archived: Number(assignmentId.slice(-1))%2==0, + user: 0, + is_lesgever: false, + first_name: "flinke", + last_name: "student", + email: "flinke.student@ugent.be", } +} + +function getProjectenVoorVak(courseId: number): Project[] { + return [{ + project_id: 0, + titel: "project 1", + beschrijving: "eerste project", + opgave_bestand: null, + vak: courseId, + max_score: 20, + deadline: new Date(2022, 11, 17), + extra_deadline: new Date(2022, 11, 17), + zichtbaar: true, + gearchiveerd: false, + }, + { + project_id: 1, + titel: "project 2", + beschrijving: "tweede project", + opgave_bestand: null, + vak: courseId, + max_score: 20, + deadline: new Date(2022, 11, 17), + extra_deadline: new Date(2022, 11, 17), + zichtbaar: true, + gearchiveerd: false, + }, + { + project_id: 2, + titel: "project 3", + beschrijving: "derde project", + opgave_bestand: null, + vak: courseId, + max_score: 20, + deadline: new Date(2022, 11, 17), + extra_deadline: new Date(2022, 11, 17), + zichtbaar: true, + gearchiveerd: false, + }, + { + project_id: 3, + titel: "project 4", + beschrijving: "project", + opgave_bestand: null, + vak: courseId, + max_score: 20, + deadline: new Date(2022, 11, 17), + extra_deadline: new Date(2022, 11, 17), + zichtbaar: true, + gearchiveerd: false, + }, + { + project_id: 4, + titel: "project 5", + beschrijving: "project", + opgave_bestand: null, + vak: courseId, + max_score: 20, + deadline: new Date(2022, 11, 17), + extra_deadline: new Date(2022, 11, 17), + zichtbaar: true, + gearchiveerd: false, + }, + { + project_id: 5, + titel: "project 6", + beschrijving: "project", + opgave_bestand: null, + vak: courseId, + max_score: 20, + deadline: new Date(2022, 11, 17), + extra_deadline: new Date(2022, 11, 17), + zichtbaar: true, + gearchiveerd: false, + }] } \ No newline at end of file diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx index 15f6fcb7a..79dcf0cba 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx @@ -6,24 +6,32 @@ import { useNavigate, useParams } from "react-router-dom"; import AddCircleIcon from '@mui/icons-material/AddCircle'; import { useState } from "react"; -interface Course { - id: string; - name: string; - teacher: string; - students: string[]; - //list of assignment ids - assignments: string[]; - archived: boolean; +interface Vak { + vak_id: number, + naam: string, + studenten: number[], + lesgevers: number[], } -interface Assignment { - id: string; - name: string; - deadline?: Date; - submissions: number; - score: number; - visible: boolean; - archived: boolean; +interface Project { + project_id: number, + titel: string, + beschrijving: string, + opgave_bestand: File | null, + vak: number, + max_score: number, + deadline: Date, + extra_deadline: Date, + zichtbaar: boolean, + gearchiveerd: boolean, +} + +interface Gebruiker { + user: number, + is_lesgever: boolean, + first_name: string, + last_name: string, + email: string, } export function SubjectsTeacherPage() { @@ -37,9 +45,11 @@ export function SubjectsTeacherPage() { navigate("/add_change_assignment"); } - const course = getCourse(courseId); + const course = getVak(Number(courseId)); - const [assignments, setAssignments] = useState(course.assignments.map((assignmentId) => getAssignment(assignmentId))); + const [assignments, setAssignments] = useState(getProjectenVoorVak(course.vak_id)); + + const user = getGebruiker(); const deleteAssignment = (index: number) => { setAssignments(assignments.filter((_, i) => i !== index)); @@ -59,10 +69,10 @@ export function SubjectsTeacherPage() {
, - ]}/> @@ -77,49 +87,126 @@ export function SubjectsTeacherPage() { } //TODO: use api to get data, for now use mock data -function getCourse(courseId: string): Course { +function getVak(courseId: number): Vak { return { - id: courseId, - name: "courseName", - teacher: "teacher", - students: ["student1", "student2"], - archived: false, - assignments: ["assignment1", "assignment2", "assignment3", "assignment4", "assignment5", "assignment6", "assignment7", "assignment8", "assignment9"] + vak_id: courseId, + naam: "courseName", + studenten: [0, 1, 2, 3], + lesgevers: [0, 1], } } -function getAssignment(assignmentId: string): Assignment { +function getGebruiker(): Gebruiker { return { - id: assignmentId, - name: assignmentId, - deadline: new Date(2022, 11, 17), - submissions: 2, - score: 10, - visible: true, - archived: Number(assignmentId.slice(-1))%2==0, + user: 0, + is_lesgever: true, + first_name: "goede", + last_name: "leerkracht", + email: "goede.leerkracht@ugent.be", } } -function archiveSingleAssignment(assignment: Assignment): Assignment { +function getProjectenVoorVak(courseId: number): Project[] { + return [{ + project_id: 0, + titel: "project 1", + beschrijving: "eerste project", + opgave_bestand: null, + vak: courseId, + max_score: 20, + deadline: new Date(2022, 11, 17), + extra_deadline: new Date(2022, 11, 17), + zichtbaar: true, + gearchiveerd: false, + }, + { + project_id: 1, + titel: "project 2", + beschrijving: "tweede project", + opgave_bestand: null, + vak: courseId, + max_score: 20, + deadline: new Date(2022, 11, 17), + extra_deadline: new Date(2022, 11, 17), + zichtbaar: true, + gearchiveerd: false, + }, + { + project_id: 2, + titel: "project 3", + beschrijving: "derde project", + opgave_bestand: null, + vak: courseId, + max_score: 20, + deadline: new Date(2022, 11, 17), + extra_deadline: new Date(2022, 11, 17), + zichtbaar: true, + gearchiveerd: false, + }, + { + project_id: 3, + titel: "project 4", + beschrijving: "project", + opgave_bestand: null, + vak: courseId, + max_score: 20, + deadline: new Date(2022, 11, 17), + extra_deadline: new Date(2022, 11, 17), + zichtbaar: true, + gearchiveerd: false, + }, + { + project_id: 4, + titel: "project 5", + beschrijving: "project", + opgave_bestand: null, + vak: courseId, + max_score: 20, + deadline: new Date(2022, 11, 17), + extra_deadline: new Date(2022, 11, 17), + zichtbaar: true, + gearchiveerd: false, + }, + { + project_id: 5, + titel: "project 6", + beschrijving: "project", + opgave_bestand: null, + vak: courseId, + max_score: 20, + deadline: new Date(2022, 11, 17), + extra_deadline: new Date(2022, 11, 17), + zichtbaar: true, + gearchiveerd: false, + }] +} + +function archiveSingleAssignment(assignment: Project): Project { return { - id: assignment.id, - name: assignment.name, + project_id: assignment.project_id, + titel: assignment.titel, + beschrijving: assignment.beschrijving, + opgave_bestand: assignment.opgave_bestand, + vak: assignment.vak, + max_score: assignment.max_score, deadline: assignment.deadline, - submissions: assignment.submissions, - score: assignment.score, - visible: assignment.visible, - archived: true, + extra_deadline: assignment.extra_deadline, + zichtbaar: assignment.zichtbaar, + gearchiveerd: true, } } -function changeVisibilitySingleAssignment(assignment: Assignment): Assignment { +function changeVisibilitySingleAssignment(assignment: Project): Project { return { - id: assignment.id, - name: assignment.name, + project_id: assignment.project_id, + titel: assignment.titel, + beschrijving: assignment.beschrijving, + opgave_bestand: assignment.opgave_bestand, + vak: assignment.vak, + max_score: assignment.max_score, deadline: assignment.deadline, - submissions: assignment.submissions, - score: assignment.score, - visible: !assignment.visible, - archived: assignment.archived, + extra_deadline: assignment.extra_deadline, + zichtbaar: !assignment.zichtbaar, + gearchiveerd: assignment.gearchiveerd, } } \ No newline at end of file diff --git a/frontend/frontend/src/pages/scoresPage/StudentsView.tsx b/frontend/frontend/src/pages/scoresPage/StudentsView.tsx index 52bd414a1..b86d41c75 100644 --- a/frontend/frontend/src/pages/scoresPage/StudentsView.tsx +++ b/frontend/frontend/src/pages/scoresPage/StudentsView.tsx @@ -4,36 +4,68 @@ import { StudentScoreListItem } from "../../components/StudentScoreListItem.tsx" import {t} from "i18next"; interface StudentsViewProps { - projectId: string; + projectId: number; } -interface Assignment { - id: string; - name: string; - teacher: string; - //list of student ids - students: string[]; +interface Vak { + vak_id: number, + naam: string, + studenten: number[], + lesgevers: number[], } -interface Student { - id: string; - name: string; +interface Project { + project_id: number, + titel: string, + beschrijving: string, + opgave_bestand: File | null, + vak: number, + max_score: number, + deadline: Date, + extra_deadline: Date, + zichtbaar: boolean, + gearchiveerd: boolean, } -interface StudentProject { - id: string; - studentId: string; - submissions: string[]; +interface Groep { + groep_id: number, + studenten: number[], + project: number, } -interface Submission { - id: string; - file: string; +interface Score { + score_id: number, + score: number, + indiening: number, } +interface Gebruiker { + user: number, + is_lesgever: boolean, + first_name: string, + last_name: string, + email: string, +} + +interface Indiening { + indiening_id: number, + groep: number, + tijdstip: Date, + status: boolean, + indiening_bestanden: Bestand[], + } + + interface Bestand { + indiening_bestand_id: number, + indiening: number, + bestand: File | null, + } + export function StudentsView({projectId}: StudentsViewProps) { const project = getProject(projectId); - const students = project.students.map((studentProjectId) => getStudentOnProject(studentProjectId)); + const groepen = getGroepenVoorProject(projectId); + const indieningen = groepen.map((groep) => getLaatseIndieningVanGroep(groep.groep_id)); + const scores = indieningen.map((indiening) => getScoreVoorIndiening(indiening.indiening_id)); return ( <> @@ -47,7 +79,7 @@ export function StudentsView({projectId}: StudentsViewProps) { padding:3, }}> <> - Student + {t("group")} {t("time")} Score Download @@ -66,8 +98,10 @@ export function StudentsView({projectId}: StudentsViewProps) { - {students.map((studentOnProject) => ( - getSubmission(submissionId).file)}/> + {groepen.map((groep, index) => ( + ))} @@ -78,33 +112,65 @@ export function StudentsView({projectId}: StudentsViewProps) { } //TODO: use api to get data, for now use mock data -function getProject(projectId: string): Assignment { +function getProject(projectId: number): Project { return { - id: projectId, - name: "courseName", - teacher: "teacher", - students: ["student1", "student2", "student3"], + project_id: projectId, + titel: "courseName", + beschrijving: "project beschrijving", + opgave_bestand: null, + vak: 0, + max_score: 20, + deadline: new Date(2022, 11, 17), + extra_deadline: new Date(2022, 11, 17), + zichtbaar: true, + gearchiveerd: false, } } -function getStudentOnProject(studentProjectId: string): StudentProject { - return { - id: studentProjectId, - studentId: "student", - submissions: ["submission1", "submission2"], - } +function getGroepenVoorProject(projectId: number): Groep[] { + return [{ + groep_id: 0, + studenten: [0, 1, 2, 3], + project: projectId, + }, + { + groep_id: 1, + studenten: [4, 5, 6, 7], + project: projectId, + }, + { + groep_id: 2, + studenten: [8, 9, 10, 11], + project: projectId, + }, + ] } -function getStudent(studentId: string): Student { +function getLaatseIndieningVanGroep(groepId: number): Indiening { return { - id: studentId, - name: "studentName", - } + indiening_id: 0, + groep: groepId, + tijdstip: new Date(2022, 11, 17), + status: true, + indiening_bestanden: [ + { + indiening_bestand_id: 0, + indiening: 0, + bestand: null, + }, + { + indiening_bestand_id: 1, + indiening: 0, + bestand: null, + } + ], + } } -function getSubmission(submissionId: string): Submission { +function getScoreVoorIndiening(indieningId: number): Score { return { - id: submissionId, - file: "file", + score_id: 0, + score: 10, + indiening: indieningId, } } \ No newline at end of file From 76ecc7e95756e2fb60a6fdff6f0131e041a70f7a Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke Date: Thu, 28 Mar 2024 17:14:25 +0100 Subject: [PATCH 067/268] percentage bij score op subjectsstudentpage --- .../src/components/AssignmentListItemSubjectsPage.tsx | 5 +++-- frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx | 4 ++-- .../frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx | 2 +- .../frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx index 9293403c2..f88fa43ad 100644 --- a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx +++ b/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx @@ -12,6 +12,7 @@ interface AssignmentListItemSubjectsPageProps { dueDate?: Date; submission: Indiening; score: Score; + maxScore: number; isStudent: boolean; archived: boolean; visible: boolean; @@ -54,7 +55,7 @@ interface Indiening { * @param visibilityEvent: () => void - event to call to change visibility of assignment */ -export function AssignmentListItemSubjectsPage({projectName, dueDate, submission, score, isStudent, archived, visible, +export function AssignmentListItemSubjectsPage({projectName, dueDate, submission, score, maxScore, isStudent, archived, visible, deleteEvent, archiveEvent, visibilityEvent}:AssignmentListItemSubjectsPageProps) { const navigate = useNavigate(); const handleProjectClick = () => { @@ -84,7 +85,7 @@ export function AssignmentListItemSubjectsPage({projectName, dueDate, submission - + : <> diff --git a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx index 27c1b7f47..272f08e9d 100644 --- a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx @@ -108,8 +108,8 @@ export function ProjectsView({gebruiker, archived, assignments, deleteAssignment .map((assignment) => ( deleteAssignment(assignment.index)} archiveEvent={() => archiveAssignment(assignment.index)} visibilityEvent={() => changeVisibilityAssignment(assignment.index)}/> diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx index cfe403534..76eb27c34 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsStudentPage.tsx @@ -87,7 +87,7 @@ function getProjectenVoorVak(courseId: number): Project[] { beschrijving: "eerste project", opgave_bestand: null, vak: courseId, - max_score: 20, + max_score: 10, deadline: new Date(2022, 11, 17), extra_deadline: new Date(2022, 11, 17), zichtbaar: true, diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx index 79dcf0cba..da182a99d 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx @@ -113,7 +113,7 @@ function getProjectenVoorVak(courseId: number): Project[] { beschrijving: "eerste project", opgave_bestand: null, vak: courseId, - max_score: 20, + max_score: 10, deadline: new Date(2022, 11, 17), extra_deadline: new Date(2022, 11, 17), zichtbaar: true, From 02687842b19a35aeb9949da2a712dcc8053e16f9 Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Fri, 29 Mar 2024 17:05:29 +0100 Subject: [PATCH 068/268] Authenticatie toegevoegd aan frontend, zou moeten werken maar spa wordt nog niet toegelaten door dict --- frontend/frontend/.gitignore | 1 + frontend/frontend/package-lock.json | 33 ++++++++ frontend/frontend/package.json | 2 + .../frontend/src/authConfig/authConfig.ts | 79 +++++++++++++++++++ .../frontend/src/authConfig/authSecrets.ts | 7 ++ frontend/frontend/src/components/Header.tsx | 5 +- frontend/frontend/src/main.tsx | 44 +++++++++-- .../src/pages/loginPage/LoginPage.tsx | 11 +-- 8 files changed, 168 insertions(+), 14 deletions(-) create mode 100644 frontend/frontend/src/authConfig/authConfig.ts create mode 100644 frontend/frontend/src/authConfig/authSecrets.ts diff --git a/frontend/frontend/.gitignore b/frontend/frontend/.gitignore index a547bf36d..02c947941 100644 --- a/frontend/frontend/.gitignore +++ b/frontend/frontend/.gitignore @@ -22,3 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? +!/src/authConfig/authSecrets.ts diff --git a/frontend/frontend/package-lock.json b/frontend/frontend/package-lock.json index 034b0289c..33f877d62 100644 --- a/frontend/frontend/package-lock.json +++ b/frontend/frontend/package-lock.json @@ -8,6 +8,8 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@azure/msal-browser": "^3.11.0", + "@azure/msal-react": "^2.0.13", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@fontsource/roboto": "^5.0.8", @@ -64,6 +66,37 @@ "node": ">=6.0.0" } }, + "node_modules/@azure/msal-browser": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.11.0.tgz", + "integrity": "sha512-Xc0g1gdB2gdscPeuUGKmlGMgP1L/AWDeuxaToWkeautPdZqKvzeE82ggqLMctKZ0yq6e7F1XfGhUDCcUo7Db9w==", + "dependencies": { + "@azure/msal-common": "14.8.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "14.8.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.8.0.tgz", + "integrity": "sha512-FIghuAzpgmc5ZAW2rCTAHKdhGcCRqg/UyroidTgGgSRrG1gOsEbUTW+7lmEFTz84ttCv5RnjOAUUi/SQjUTw0w==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-react": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@azure/msal-react/-/msal-react-2.0.13.tgz", + "integrity": "sha512-M+xmk6Avkrt/Gmxr9TusrGHHcMTr3aqLVm1S9l/mRAvSRlpuvnHE2gCFKSjWP9G3kDoiPPLADXj7Q+r/3VMa6A==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@azure/msal-browser": "^3.11.0", + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/@babel/code-frame": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", diff --git a/frontend/frontend/package.json b/frontend/frontend/package.json index e53efebe1..d75259311 100644 --- a/frontend/frontend/package.json +++ b/frontend/frontend/package.json @@ -10,6 +10,8 @@ "preview": "vite preview" }, "dependencies": { + "@azure/msal-browser": "^3.11.0", + "@azure/msal-react": "^2.0.13", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@fontsource/roboto": "^5.0.8", diff --git a/frontend/frontend/src/authConfig/authConfig.ts b/frontend/frontend/src/authConfig/authConfig.ts new file mode 100644 index 000000000..ebf7dc1d3 --- /dev/null +++ b/frontend/frontend/src/authConfig/authConfig.ts @@ -0,0 +1,79 @@ +/* + This file is used to configure the Auth0 SDK. + We need to provide the domain and client ID of our Auth0 application. + The validation of the token is already implemented in the backend. + That's why we need to send the token to the backend with every request using our Axios configuration. + */ + +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import {LogLevel} from '@azure/msal-browser'; +import authSecrets from './authSecrets'; + +/** + * Configuration object to be passed to MSAL instance on creation. + * For a full list of MSAL.js configuration parameters, visit: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md + */ + +//TODO: fix +export const msalConfig = { + auth: { + clientId: authSecrets.clientId, // This is the ONLY mandatory field that you need to supply. + authority: authSecrets.authority, // Replace the placeholder with your tenant subdomain + redirectUri: window.location.origin + '/', // Points to window.location.origin. You must register this URI on Azure Portal/App Registration. + postLogoutRedirectUri: '/', // Indicates the page to navigate after logout. + navigateToLoginRequestUrl: false, // If "true", will navigate back to the original request location before processing the auth code response. + }, + cache: { + cacheLocation: 'sessionStorage', // Configures cache location. "sessionStorage" is more secure, but "localStorage" gives you SSO between tabs. + storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge + }, + system: { + loggerOptions: { + loggerCallback: (level: LogLevel, message: string, containsPii: boolean) => { + if (containsPii) { + return; + } + switch (level) { + case LogLevel.Error: + console.error(message); + return; + case LogLevel.Info: + console.info(message); + return; + case LogLevel.Verbose: + console.debug(message); + return; + case LogLevel.Warning: + console.warn(message); + return; + default: + return; + } + }, + }, + }, +}; + +/** + * Scopes you add here will be prompted for user consent during sign-in. + * By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request. + * For more information about OIDC scopes, visit: + * https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes + */ +export const loginRequest = { + scopes: [], +}; + +/** + * An optional silentRequest object can be used to achieve silent SSO + * between applications by providing a "login_hint" property. + */ +// export const silentRequest = { +// scopes: ["openid", "profile"], +// loginHint: "example@domain.net" +// }; \ No newline at end of file diff --git a/frontend/frontend/src/authConfig/authSecrets.ts b/frontend/frontend/src/authConfig/authSecrets.ts new file mode 100644 index 000000000..bb0c0c91e --- /dev/null +++ b/frontend/frontend/src/authConfig/authSecrets.ts @@ -0,0 +1,7 @@ +const AuthSecrets = { + clientId: 'e9b4d537-624d-4faf-8122-5416f8ce1469', // This is the ONLY mandatory field that you need to supply. + authority: 'https://login.microsoftonline.com/d7811cde-ecef-496c-8f91-a1786241b99c', // Replace the placeholder with your tenant subdomain + +}; + +export default AuthSecrets; \ No newline at end of file diff --git a/frontend/frontend/src/components/Header.tsx b/frontend/frontend/src/components/Header.tsx index 3329a29cf..5a13a949e 100644 --- a/frontend/frontend/src/components/Header.tsx +++ b/frontend/frontend/src/components/Header.tsx @@ -6,6 +6,7 @@ import React from "react"; import {AccountCircle} from "@mui/icons-material"; import {useNavigate} from "react-router-dom"; import {LanguageSwitcher} from "./LanguageSwitcher.tsx"; +import {useMsal} from "@azure/msal-react"; /** * Header component @@ -31,7 +32,7 @@ interface Props { export const Header = ({variant, title}: Props) => { const {t} = useTranslation(); const [anchorEl, setAnchorEl] = React.useState(null); - + const {instance} = useMsal(); /** * Function to handle menu opening * @param {React.MouseEvent} event - The event object @@ -61,7 +62,7 @@ export const Header = ({variant, title}: Props) => { * Function to handle logout action */ const logout = () => { - localStorage.removeItem("token"); + instance.logoutRedirect().catch((error: Error) => console.error(error)); navigate("/"); }; diff --git a/frontend/frontend/src/main.tsx b/frontend/frontend/src/main.tsx index 76567b3b5..2e6bbaf10 100644 --- a/frontend/frontend/src/main.tsx +++ b/frontend/frontend/src/main.tsx @@ -4,8 +4,10 @@ import {ThemeProvider} from "@mui/material"; import theme from "./Theme.ts"; import "./i18n/config.ts"; import {createBrowserRouter, RouterProvider} from "react-router-dom"; -import ErrorPage from "./pages/ErrorPage.tsx"; +import {AuthenticationResult, EventMessage, EventType, PublicClientApplication} from "@azure/msal-browser"; +import {msalConfig} from "./authConfig/authConfig.ts"; +import ErrorPage from "./pages/ErrorPage.tsx"; import {MainPage} from "./pages/mainPage/MainPage.tsx"; import {Helmet, HelmetProvider} from "react-helmet-async"; import {LocalizationProvider} from "@mui/x-date-pickers"; @@ -18,14 +20,10 @@ import {SubmissionPage} from "./pages/submissionPage/SubmissionPage.tsx"; import {ProjectScoresPage} from "./pages/scoresPage/ProjectScoresPage.tsx"; import {SubjectsTeacherPage} from "./pages/subjectsPage/SubjectsTeacherPage.tsx"; import {AddChangeAssignmentPage} from "./pages/addChangeAssignmentPage/AddChangeAssignmentPage.tsx"; +import {AuthenticatedTemplate, MsalProvider, UnauthenticatedTemplate} from "@azure/msal-react"; //TODO: add change/add course page when implemented const router = createBrowserRouter([ - { - path: '/login', - element: , - errorElement: , - }, { path: '/', element: , @@ -72,6 +70,31 @@ const router = createBrowserRouter([ }, ]); +/** + * MSAL should be instantiated outside the component tree to prevent it from being re-instantiated on re-renders. + * For more, visit: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-react/docs/getting-started.md + */ +const msalInstance = new PublicClientApplication(msalConfig); + +// Default to using the first account if no account is active on page load +if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) { + // Account selection logic is app dependent. Adjust as needed for different use cases. + msalInstance.setActiveAccount(msalInstance.getActiveAccount() || msalInstance.getAllAccounts()[0]) +} + + +// Listen for sign-in event and set active account +msalInstance.addEventCallback((event: EventMessage) => { + if (event.eventType === EventType.LOGIN_SUCCESS) { + // Cast event.payload to AuthenticationResult to access the account property + const authResult = event.payload as AuthenticationResult; + if (authResult.account) { + const account = authResult.account; + msalInstance.setActiveAccount(account); + } + } +}); + ReactDOM.createRoot(document.getElementById("root")!).render( @@ -82,7 +105,14 @@ ReactDOM.createRoot(document.getElementById("root")!).render( Loading...
}> - + + + + + + + + diff --git a/frontend/frontend/src/pages/loginPage/LoginPage.tsx b/frontend/frontend/src/pages/loginPage/LoginPage.tsx index 72d3c320f..ccc61bbca 100644 --- a/frontend/frontend/src/pages/loginPage/LoginPage.tsx +++ b/frontend/frontend/src/pages/loginPage/LoginPage.tsx @@ -1,6 +1,7 @@ import {Box, Button, Typography} from "@mui/material"; import {t} from "i18next"; -import {useNavigate} from "react-router-dom"; +import {useMsal} from "@azure/msal-react"; +import {loginRequest} from "../../authConfig/authConfig.ts"; /* LoginPage component is a simple page with a logo and a login button. @@ -9,11 +10,11 @@ The page is styled with mui components */ export function LoginPage() { - const navigate = useNavigate() + const {instance} = useMsal(); + const handleLogin = () => { //TODO: implement authentication trough backend - navigate("/main") //absolute path, so it will redirect to localhost:3000/main - //navigate("main") //relative path, so it will redirect to localhost:3000/login/main + instance.loginRedirect(loginRequest).catch((error) => console.error(error)); } return ( @@ -90,7 +91,7 @@ export function LoginPage() { maxHeight: "20%", }} > - Naam Platform + Pigeonhole
Date: Fri, 29 Mar 2024 21:24:04 +0100 Subject: [PATCH 069/268] beetje configuratie en integratie api requests toegevoegd --- frontend/frontend/src/axiosConfig.ts | 23 +++ .../frontend/src/components/CourseCard.tsx | 165 +++++++++++++++--- .../frontend/src/pages/mainPage/MainPage.tsx | 2 +- 3 files changed, 165 insertions(+), 25 deletions(-) diff --git a/frontend/frontend/src/axiosConfig.ts b/frontend/frontend/src/axiosConfig.ts index 248355ca5..643b21435 100644 --- a/frontend/frontend/src/axiosConfig.ts +++ b/frontend/frontend/src/axiosConfig.ts @@ -22,8 +22,31 @@ export function getVakken() { // mogelijk/nodig? return instance.get('/vakken'); } +export function getVak(vakId: string) { + return instance.get('/vakken/' + vakId); +} + +export function addVak(assignment: assignment) { + return instance.post('/vakken', assignment); +} + +//mogelijk/nodig? +export function changeVak(vakId: string, assignment: assignment) { + return instance.put('/vakken/' + vakId, assignment); +} + +//mogelijk/nodig? +export function deleteVak(vakId: string) { + return instance.delete('/vakken/' + vakId); +} + export function getProjecten() { return instance.get('/projecten'); } +//mogelijk/nodig? +export function getProject(projectId: string) { + return instance.get('/projecten/' + projectId); +} + export default instance; \ No newline at end of file diff --git a/frontend/frontend/src/components/CourseCard.tsx b/frontend/frontend/src/components/CourseCard.tsx index 67c57832a..a1a5133ad 100644 --- a/frontend/frontend/src/components/CourseCard.tsx +++ b/frontend/frontend/src/components/CourseCard.tsx @@ -1,11 +1,11 @@ -import {Box, Card, CardActionArea, CardContent, Divider, IconButton, Typography} from "@mui/material"; +import {Box, Card, CardActionArea, CardContent, Divider, IconButton, Skeleton, Typography} from "@mui/material"; import {t} from "i18next"; -import List from '@mui/material/List'; -import {AssignmentListItem} from "./AssignmentListItem"; import {useNavigate} from "react-router-dom"; -import ArchiveOutlinedIcon from '@mui/icons-material/ArchiveOutlined'; -import { useState, useEffect } from "react"; -import axios, { getProjecten, getVakken } from '../axiosConfig'; +import {useEffect, useState} from "react"; +import {getProjecten, getVakken} from '../axiosConfig'; +import {AssignmentListItem} from "./AssignmentListItem.tsx"; +import List from "@mui/material/List"; +import ArchiveOutlinedIcon from "@mui/icons-material/ArchiveOutlined"; /* * CourseCard component displays a card with course information and a list of assignments * @param courseId: string, the id of the course @@ -35,23 +35,28 @@ interface Assignment { id: string; name: string; deadline?: Date; + status: boolean; } export function CourseCard({courseId, archived, isStudent}: CourseCardProps) { const [course, setCourse] = useState(null); const [assignments, setAssignments] = useState([]); - const navigate = useNavigate(); + const navigate = useNavigate(); // hier zijn nog problemen bij - Promise.all([getVakken(), getProjecten()]) - .then(function (results) { - const course = results[0]; - const assignments = results[1]; - }); + // geen idee of dit werkt + useEffect(() => { + Promise.all([getVakken(), getProjecten()]) + .then(function (results) { + //Use setCourse and setAssignments to update the state + setCourse(results[0] as unknown as Course); + setAssignments(results[1] as unknown as Assignment[]); + }); + }, []); const handleCardClick = () => { console.log("Card clicked"); - navigate(`/${courseId}`); + navigate(`/subjects_student/${courseId}`); } const archive = () => { @@ -59,28 +64,140 @@ export function CourseCard({courseId, archived, isStudent}: CourseCardProps) { //update db } - return ( + return ( <> - {course && ( - - + {!course ? : + + - - + + {course.name} {course.teacher} - {t("students")}{course.students.length} + {t("students")}{course.students.length} - - {/* Assignment list rendering */} + + {isStudent ? + + Project + Deadline + Status + : + <> + {archived ? + + Project + Deadline + + : + + Project + Deadline + + } + } + + + {isStudent ? + + + {assignments.map((assignment) => ( + + ))} + + : + <>{!archived ? + + + {assignments.map((assignment) => ( + + ))} + + : + + + {assignments.map((assignment) => ( + + ))} + + + } + {!archived && + + + } + + } + - )} + } ); -} +} \ No newline at end of file diff --git a/frontend/frontend/src/pages/mainPage/MainPage.tsx b/frontend/frontend/src/pages/mainPage/MainPage.tsx index 296a7ccb2..6f03458a1 100644 --- a/frontend/frontend/src/pages/mainPage/MainPage.tsx +++ b/frontend/frontend/src/pages/mainPage/MainPage.tsx @@ -68,5 +68,5 @@ export function MainPage() { * @returns {string} - The role of the user */ function getRole(id: string): string { - return "teacher"; + } \ No newline at end of file From 194f563209891250d1afa4f7d07842448b5b7954 Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Fri, 29 Mar 2024 21:31:44 +0100 Subject: [PATCH 070/268] fix merge conflicts --- frontend/frontend/src/i18n/en.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index 8f467db6b..724265154 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -38,6 +38,10 @@ const english = { allowed_file_types: "Allowed file types", dockerfile_error: "Please upload a Dockerfile.", filetype_error: "Please select at least one file type.", + export_submissions: "Export submissions", + time: "Time", + no_submissions: "No submissions", + edit: "Edit", }; From 0448e8f671350ebd016ac81ef8e363f239d9b5b4 Mon Sep 17 00:00:00 2001 From: Robbe Date: Sat, 30 Mar 2024 11:48:23 +0100 Subject: [PATCH 071/268] removed commented code --- .../SubjectsPage/AddChangeSubjectPage.tsx | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/frontend/frontend/src/pages/SubjectsPage/AddChangeSubjectPage.tsx b/frontend/frontend/src/pages/SubjectsPage/AddChangeSubjectPage.tsx index 7b9aaab6e..fb497c1e2 100644 --- a/frontend/frontend/src/pages/SubjectsPage/AddChangeSubjectPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/AddChangeSubjectPage.tsx @@ -25,25 +25,6 @@ interface Teacher { name:string; } -// -// -// -// {"delete student?"} -// {"this can not be undone"} -// -// -// -// -// -// -// - export function AddChangeSubjectPage() { // State for the different fields of the subject const [title, setTitle] = useState(""); From 50ba3aa9825f81993139f4c85335af9471784362 Mon Sep 17 00:00:00 2001 From: Robbe Date: Sat, 30 Mar 2024 14:16:53 +0100 Subject: [PATCH 072/268] url veranderd --- frontend/frontend/src/main.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/frontend/src/main.tsx b/frontend/frontend/src/main.tsx index df38b26df..d53d95ba4 100644 --- a/frontend/frontend/src/main.tsx +++ b/frontend/frontend/src/main.tsx @@ -16,7 +16,7 @@ import {AssignmentTeacherPage} from "./pages/assignmentPage/AssignmentTeacherPag import {AssignmentStudentPage} from "./pages/assignmentPage/AssignmentStudentPage.tsx"; import {SubmissionPage} from "./pages/submissionPage/SubmissionPage.tsx"; import {SimpleRequestsPage} from "./pages/simpleRequestsPage/SimpleRequestsPage.tsx"; -import {AddChangeSubjectPage} from "./pages/SubjectsPage/AddChangeSubjectPage.tsx"; +import {AddChangeSubjectPage} from "./pages/subjectsPage/AddChangeSubjectPage.tsx"; import {ProjectScoresPage} from "./pages/scoresPage/ProjectScoresPage.tsx"; import {SubjectsTeacherPage} from "./pages/subjectsPage/SubjectsTeacherPage.tsx"; import {AddChangeAssignmentPage} from "./pages/addChangeAssignmentPage/AddChangeAssignmentPage.tsx"; @@ -69,7 +69,7 @@ const router = createBrowserRouter([ errorElement: , }, { - path: "/add_change_subject", + path: "/course_teacher/edit/:courseId?", element: , errorElement: , From f6317ea059240ab3b6343523d37fa22c240d54d1 Mon Sep 17 00:00:00 2001 From: Robbe Date: Sat, 30 Mar 2024 14:36:34 +0100 Subject: [PATCH 073/268] internationalisatie --- frontend/frontend/src/i18n/en.ts | 17 +++++++-- frontend/frontend/src/i18n/nl.ts | 16 ++++++++- .../subjectsPage/AddChangeSubjectPage.tsx | 36 +++++++++---------- 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index 8f467db6b..c833f1312 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -38,7 +38,20 @@ const english = { allowed_file_types: "Allowed file types", dockerfile_error: "Please upload a Dockerfile.", filetype_error: "Please select at least one file type.", - + upload_students: "Upload students", + upload_teachers: "Upload teachers", + subject_name: "Subject name", + students: "Students", + teachers: "Teachers", + cancel: "Cancel", + delete: "Delete", + this_can_not_be_undone: "This can not be undone", + delete_student: "Delete student", + delete_teacher: "Delete teacher", + add: "Add", + studentnumber: "Studentnumber", + teacher: "Teacher", + title: "Title", }; -export default english; \ No newline at end of file +export default english; diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts index 09f1fe050..6bd35e324 100644 --- a/frontend/frontend/src/i18n/nl.ts +++ b/frontend/frontend/src/i18n/nl.ts @@ -38,6 +38,20 @@ const dutch = { allowed_file_types: "Toegestane bestandstypes", dockerfile_error: "Gelieve een Docker bestand te uploaden.", filetype_error: "Gelieve minstens één bestandstype te selecteren.", + upload_students: "Upload studenten", + upload_teachers: "Upload leerkrachten", + subject_name: "Naam vak", + students: "Studenten", + teachers: "Leerkrachten", + cancel: "Annuleer", + delete: "Verwijder", + this_can_not_be_undone: "Dit kan niet ongedaan gemaakt worden", + delete_student: "Verwijder student", + delete_teacher: "Verwijder leerkracht", + add: "Voeg toe", + studentnumber: "Studentennummer", + teacher: "Leerkracht", + title: "Titel", }; -export default dutch; \ No newline at end of file +export default dutch; diff --git a/frontend/frontend/src/pages/subjectsPage/AddChangeSubjectPage.tsx b/frontend/frontend/src/pages/subjectsPage/AddChangeSubjectPage.tsx index fb497c1e2..5f3e10ff0 100644 --- a/frontend/frontend/src/pages/subjectsPage/AddChangeSubjectPage.tsx +++ b/frontend/frontend/src/pages/subjectsPage/AddChangeSubjectPage.tsx @@ -45,13 +45,13 @@ export function AddChangeSubjectPage() { subject name: - {t("subject_name")+":"} + setTitle(event.target.value)}/> - students: + {t("students")+":"} :not(style)': {marginBottom: '8px', width: "75vw"}}}> {students.map((id) => { @@ -84,18 +84,17 @@ export function AddChangeSubjectPage() {
- {"delete student?"} - {"this can not be undone"} + {t("delete_student")+"?"} + {t("this_can_not_be_undone")} - @@ -104,15 +103,15 @@ export function AddChangeSubjectPage() { )})} - - setNum(event.target.value)}/> @@ -121,7 +120,7 @@ export function AddChangeSubjectPage() { - teachers: + {t("teachers")+":"} :not(style)': {marginBottom: '8px', width: "75vw"}}}> {teachers.map((id) => { @@ -153,18 +152,17 @@ export function AddChangeSubjectPage() {
- {"delete student?"} - {"this can not be undone"} + {t("delete_teacher")+"?"} + {t("this_can_not_be_undone")} - @@ -173,12 +171,12 @@ export function AddChangeSubjectPage() { )})} - - setNum(event.target.value)}/> + + + + + ) +} \ No newline at end of file diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index 3f3160c60..64792e705 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -27,6 +27,12 @@ const english = { upload_scores: "Upload Scores", group: "Group", last_submission: "Last Submission:", + cancel: "Cancel", + delete_project_warning: "Delete Project?", + archive_project_warning: "Archive Project?", + cant_be_undone: "This cannot be undone.", + delete: "Delete", + archive: "Archive", }; export default english; \ No newline at end of file diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts index 8bd8d6ea8..9b6e85d58 100644 --- a/frontend/frontend/src/i18n/nl.ts +++ b/frontend/frontend/src/i18n/nl.ts @@ -27,6 +27,12 @@ const dutch = { upload_scores: "Upload Scores", group: "Groep", last_submission: "Laatste Indiening:", + cancel: "Annuleer", + delete_project_warning: "Opdracht Verwijderen?", + archive_project_warning: "Opdracht Archiveren?", + cant_be_undone: "Dit kan niet ongedaan gemaakt worden.", + delete: "Verwijder", + archive: "Archiveer", }; export default dutch; \ No newline at end of file diff --git a/frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx b/frontend/frontend/src/pages/SubjectsPage/AssignmentListItemSubjectsPage.tsx similarity index 100% rename from frontend/frontend/src/components/AssignmentListItemSubjectsPage.tsx rename to frontend/frontend/src/pages/SubjectsPage/AssignmentListItemSubjectsPage.tsx diff --git a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx index 272f08e9d..c1c766228 100644 --- a/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/ProjectsView.tsx @@ -1,7 +1,7 @@ import {Box, Typography} from "@mui/material"; import List from '@mui/material/List'; import {t} from "i18next"; -import {AssignmentListItemSubjectsPage} from "../../components/AssignmentListItemSubjectsPage.tsx"; +import {AssignmentListItemSubjectsPage} from "./AssignmentListItemSubjectsPage.tsx"; interface ProjectsViewProps { gebruiker: Gebruiker; diff --git a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx index da182a99d..5383155e1 100644 --- a/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx +++ b/frontend/frontend/src/pages/SubjectsPage/SubjectsTeacherPage.tsx @@ -5,6 +5,8 @@ import {ProjectsView} from "./ProjectsView.tsx"; import { useNavigate, useParams } from "react-router-dom"; import AddCircleIcon from '@mui/icons-material/AddCircle'; import { useState } from "react"; +import WarningPopup from "../../components/WarningPopup.tsx"; +import {t} from "i18next"; interface Vak { vak_id: number, @@ -48,14 +50,26 @@ export function SubjectsTeacherPage() { const course = getVak(Number(courseId)); const [assignments, setAssignments] = useState(getProjectenVoorVak(course.vak_id)); + const [openDeletePopup, setOpenDeletePopup] = useState(false); + const [deleteIndex, setDeleteIndex] = useState(0); + const [openArchivePopup, setOpenArchivePopup] = useState(false); + const [archiveIndex, setArchiveIndex] = useState(0); const user = getGebruiker(); const deleteAssignment = (index: number) => { - setAssignments(assignments.filter((_, i) => i !== index)); + setDeleteIndex(index); + setOpenDeletePopup(true); + } + const doDelete = () => { + setAssignments(assignments.filter((_, i) => i !== deleteIndex)); } const archiveAssignment = (index: number) => { - const newAssignments = assignments.map((a, i) => i==index? archiveSingleAssignment(a): a); + setArchiveIndex(index); + setOpenArchivePopup(true); + } + const doArchive = () => { + const newAssignments = assignments.map((a, i) => i==archiveIndex? archiveSingleAssignment(a): a); setAssignments(newAssignments); } const changeVisibilityAssignment = (index: number) => { @@ -81,6 +95,10 @@ export function SubjectsTeacherPage() { + setOpenDeletePopup(false)} doAction={doDelete}/> + setOpenArchivePopup(false)} doAction={doArchive}/> ); From 4f09fbf95e1c317e030a4cbc7318b51a9d59429d Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke <159711661+gusvanpoucke@users.noreply.github.com> Date: Sat, 30 Mar 2024 22:07:30 +0100 Subject: [PATCH 078/268] rare bug met hoofdletter gevoeligheid gefixt --- frontend/frontend/src/pages/subjectsPage/ProjectsView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/frontend/src/pages/subjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/subjectsPage/ProjectsView.tsx index c1c766228..fd6e2d5e6 100644 --- a/frontend/frontend/src/pages/subjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/subjectsPage/ProjectsView.tsx @@ -1,7 +1,7 @@ import {Box, Typography} from "@mui/material"; import List from '@mui/material/List'; import {t} from "i18next"; -import {AssignmentListItemSubjectsPage} from "./AssignmentListItemSubjectsPage.tsx"; +import { AssignmentListItemSubjectsPage } from "../subjectsPage/AssignmentListItemSubjectsPage"; interface ProjectsViewProps { gebruiker: Gebruiker; From 7e9248bcb8519fa68f6111542df8b0ea26f551c9 Mon Sep 17 00:00:00 2001 From: Gus Vanpoucke <159711661+gusvanpoucke@users.noreply.github.com> Date: Sat, 30 Mar 2024 22:17:08 +0100 Subject: [PATCH 079/268] kleine routing en type errors fixen --- .../frontend/src/pages/scoresPage/ProjectScoresPage.tsx | 6 +++--- frontend/frontend/src/pages/scoresPage/StudentsView.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx b/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx index 68945e318..d6b4a5375 100644 --- a/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx +++ b/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx @@ -7,8 +7,8 @@ import SaveIcon from '@mui/icons-material/Save'; import CloseIcon from '@mui/icons-material/Close'; export function ProjectScoresPage() { - let { projectId } = useParams(); - projectId = String(projectId); + let { assigmentId } = useParams(); + const projectId = Number(assigmentId); const navigate = useNavigate(); @@ -40,7 +40,7 @@ export function ProjectScoresPage() { - +
}> - - - + + + - - - + + + diff --git a/frontend/frontend/src/pages/loginPage/LoginPage.tsx b/frontend/frontend/src/pages/loginPage/LoginPage.tsx index ccc61bbca..53bd349d2 100644 --- a/frontend/frontend/src/pages/loginPage/LoginPage.tsx +++ b/frontend/frontend/src/pages/loginPage/LoginPage.tsx @@ -14,7 +14,9 @@ export function LoginPage() { const handleLogin = () => { //TODO: implement authentication trough backend - instance.loginRedirect(loginRequest).catch((error) => console.error(error)); + instance.loginRedirect(loginRequest).catch((e) => { + console.log(e); + }); } return ( From 60e2dfccefa0494e5f892394f62bc1b6b96d7d3f Mon Sep 17 00:00:00 2001 From: BenDeMeurichy <48103261+Bendemeurichy@users.noreply.github.com> Date: Sat, 30 Mar 2024 23:20:05 +0100 Subject: [PATCH 081/268] oude authsecrets verwijderd --- frontend/frontend/src/authConfig/authSecrets.ts | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 frontend/frontend/src/authConfig/authSecrets.ts diff --git a/frontend/frontend/src/authConfig/authSecrets.ts b/frontend/frontend/src/authConfig/authSecrets.ts deleted file mode 100644 index bb0c0c91e..000000000 --- a/frontend/frontend/src/authConfig/authSecrets.ts +++ /dev/null @@ -1,7 +0,0 @@ -const AuthSecrets = { - clientId: 'e9b4d537-624d-4faf-8122-5416f8ce1469', // This is the ONLY mandatory field that you need to supply. - authority: 'https://login.microsoftonline.com/d7811cde-ecef-496c-8f91-a1786241b99c', // Replace the placeholder with your tenant subdomain - -}; - -export default AuthSecrets; \ No newline at end of file From cd0491f1ba79445c57c12559ae42e9ca286c4f34 Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Sat, 30 Mar 2024 23:26:21 +0100 Subject: [PATCH 082/268] updated gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index cd8ecbd62..41e1f04b7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ venv __pycache__ migrations ./api/.env + +frontend/frontend/src/authConfig/authSecrets.ts + +api/.env From ccda64ccae7f2af8675791f358779d920863e759 Mon Sep 17 00:00:00 2001 From: arallaer Date: Sun, 31 Mar 2024 12:14:50 +0200 Subject: [PATCH 083/268] restrictie views coverage --- .coverage | Bin 53248 -> 53248 bytes .coveragerc | 11 +++++++++++ api/tests/views/test_restrictie.py | 27 +++++++++++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 .coveragerc diff --git a/.coverage b/.coverage index 4500b42537a23bd5bf89146f334d882519023124..86db4fbd0187e3beb8bdd5e65f471006cf2b1714 100644 GIT binary patch literal 53248 zcmeI4eQX>@6~Je2>^tAx-j2V|XFKtNXdJh;Fa9D;TD1va(X^3DNFXVVaMyQhdy~6c zdw0(tjndd{AW{Jd@n5Aht>6zNkf5lbRwO7^5&r-RE&Yd9A*CQeB$^LCs(?%z-ptr{;O*IR(ChEv+vEk_nS8}H}|po_w6+tT^%#6tmdc#q?Sk$*`=z4kO(|u@br!l zWU9OqNTrf|xom{=9(*{)zD(rcw+Q=c>`~SupN?&bz9DDB&qmLLv~UV8;DH2?01`j~ z8=JtwJ~x<`}tg)tS&e>AbC{`9cR}u+(wEUZKx(BSspu9DN+Bn6!+nX3eQb z^|@{#P-xB3XC1cyD$$Mc9G6hH7Sy{`OCQrMJ(tpL*NUx1x+~K4%!x=S-q0YO(uGf& zm4Zifg*Q-8bY;&NRFpEUG`D`#(sHQ@-R@SkWe-y(*g5OC-dbih>6mK9$O$)dw&56N zPSt1iRNm3kt2)@tp?L?uEAzD;+efP~OeOdH(MR$70d5^kU`J6G8*OxoLTP2)HX6jPGcW2t=hmYfok1{nm(y|dI_ZF_Y)51p`NDw-G z;b45Yqg?1LhrmeRayGJ%2!`TqZPL?W5k`gHDkPy5A>{SsD$}TUDXVLC-ts1rwI)eV zqc}@?W2A5e-02Ag@*9OqoV?&#UtqL1%UzHv#)?v@_I>x6vg-zb#!l(HI zFzCGT1(|GC%cTqJ6<>zkT4CfF`nbDE<&Mpjq3h%iP3){JJWDNBcou$Fdp(zU{X_tr=sf91R6 z8va&pp+gGA@3}`>$QK5qJI{>Ru&8G>&+Ni!U`3GJa5 z_~hpH;HXQ%lPbK|iFEM|zQmRYJn%pQNB{{S0VIF~kN^@u0!RP}AOR$R1nxWnR0>ET zzW)!fcL}=&FL)pUB!C2v01`j~NB{{S0VIF~kN^@u0v}HTa)54P-cLNXF^RUt3qJzb z-aFVk$baM?V3!HI%-;WaI*3*y0VIF~kN^@u0!RP}AOR$R1dsp{Kmr~CnQoH2PXPiE z+7>Q+2*AJpZ;xaM`vdy{JHhs_gz`7#W#zc?h|(B)EB4*kc&u0cr+ilay8O7@CzI%J zqNk%qbTCRIzmJ>-MLduI5rA zpVxCv&tOuUG?G~(ozCbpaBl(lFYjO*CFa`l(}q4{CoSD}ECc?}&$W`Rl<0o6#w}By z zY1X(tYUPcix&=zfMxV|L(x9-lfs%u38*Hb{2WQ}S2^NyiYP19cTmw{^?VGRBke zpYWvX?@%ozQ)|~>s^s`ldPj6! zEtbZ_P^4muSu?F?ZW(yn!jz1!?PEdB>x#jeiUYi$Q3|9#M9EQK?G*WM3;MOqzOw11TY1;tx=+)o3iX#NdA(PAA3e4XwH5n+b}!AZL$}zK%1%1B z4#m>IiLK8~FRJDBwOASx(aJ5Zw*Qax(eVmC-m?D>S03Q2?Eh;9=uv;|6#0jI+vf2L z`k1Ur_y5(teJMpZj7iJ`Y1dsp{Kmter z2_OL^fCP{L52^zx?7ITHguKh=}ZqisI z6tp)!6_}q%>5FprDnF9X|Bl{jW(43Y=|K&PXrP`T3>5#RsJKYC@416gJ1k z<_C6#8fD1eEngsu)#m~id*7`gH|8!-B?_gRqZi0z{3SAThK3_>-Wb_J1}=reJR9yO zlT@kUQZ@DSGPyoR7KdUXIBySu6d~8oOW`1w4EDp7a5c|X_k&!xif5~Uzf?mZTTegy zfVKr7cW)r@r}~FJK?1LL?7Gfg&F*SCY5n)cb5cmE3Ww`YD{1lnfA#=jOYGn53i~U2 z2kr#;2KzPpB|FD{#(u=U51$4+$G*e9!A`Q{Y?_&Df@$n4Y(Lw}hCvYzB!C2v01`j~ zNB{{S0VIF~kN^@u0(U$Cz8ja?+eOmWCX&`xkt7l#X=xEjJT8*vW|1^CiKMYnBn=HB zsjnAFU7bj3YemACNEAgRv6x6?StQY@NFouDgvBQVQcaCG3xz}y42q<>S|n9fBB4|y zfj|{}1R%cuC$)EcN1=mA00|%gB!C2v01`j~NB{{S0VIF~HZ%eL{Xg#iH*^S~^GE;* zAOR$R1dsp{Kmter2_OL^umK40|NrCue*<(hx`+gj01`j~NB{{S0VIF~kN^@u0vnpZ F{{bi6rUn22 literal 53248 zcmeI5UyK{Y9mn^dyHgv%*+XHKDwN*EFs=oBCyny=BH^KuE4~0_Hs;a1f-^}iw?c@$o zX>};+cVzF(&Y$1><}<&U@$BC2tvvFO7jvf@gnc)5jw$;TRaH(pj-n`K`c~`e*Z5}4a>h~T!eQNczvwG^g51n?BIOj;&A-A~boN~fo!x`|< z>3Tixcz(xgyRql5Iq^Erat|Zk5epr~(NZTeF3IvD)bu)3D&}h>Vi00urp=6ppE!-vL{2;?RE-0#|!cu2{*l!W? zXNLWO=o~7MR2&DpmE5gt9Hfo?!hN$tUyj>5)twn~(W6PJq3>NB^6k!FaKWceficN~VoOjG|u83A%E~h|*5dIb_X~W2TY8a6>WBoFL6entrL) zCX^)|oK`PrP1~BZn26H=p}8Q-)*bq7F;pVahIVj?hwd6*N>h_*;mbB8GM#0sSU)|x zo9S$uKy!IJSRNfJn)R6(^?6I0QQBLnXHJ<=){{F-qghg)yU{SrCX&yaB(oCZSuz_V zX$m!G(Ja~*y zMKmcsS18s`9NbNDp+a+69E&eJsY#Wu9Fp|=H0g`4KBcmV8LRvQzO{JC?a^#9aJ`U* zEX~2m3gC#09hWk@tzZ~S`xU3sJjL2G);AF_KF`=wu6dcAhWEK3iac6Y#j+LMpT#uw}wp=i!&1woIyezG=Di3cO9(~BIb!>y~*cg8ZJEg@s=(wx*OTU^96zevF( z^r$)-rh_q=XPOZ$>U}p;J3ZB=jO3=ovAvw7373BDOxlTHH-ST(CN+r3E$P8lLLtkQ z{?;kaiw(ZYK33=h2Ld1f0w4eaAOHd&00JNY0w4eaAaLgq&{a(}#r|Jo?NEB9j{uG@-M4g1{E@%L-c#6n z>|eKM1EDntfB*=900@8p2!H?xfB*=900@9UM!?qZQnRZ7T3Mg5(u)A%{=Z#$TVb!V z3$;&bzpAZNKdkkMp=((-N8of$E2F{=1e!O^J!yR~( z6nbvY`!UI)C#|2XkmwURiN56Vji?dD!wywP!dJ>9{Af61td|-a&^;#20jux}1~Lh{xiULxS=JEl0T%XwRu6_izsS(pwnB`%VU1;{Lx~ zxvsFsYOB?M)P7yLUi-9qt@?odbNht-KKsq>GSNX#5C8!X009sH0T2KI5C8#1dBPYS zS991f+`9j_e`t(On%or`_C3Rx|ujeUuV*jt5Ge(E49ES7u{vYrE z!zuv+AOHd&00JNY0w4eaAOHd&00MV30bSLVs<{8Jvi~adfdc^$009sH0T2KI5C8!X z009sH0T8$Y3Fvy&lF$FMp2Gf3{{i4H>`(0X?6>R{_Dl9N_7Z!J65v1p1V8`;KmY_l z00ck)1V8`;KmY{(Cj$FzL$&U)|M-;l+|}!cH($C+AG1Pn`zkLVE0-zoweklgMKMYi zh4)(zE2>qZ&{s>E^7?Giq+rd|ibdfUHM2nezQPSwGbr#4}~c} z_7?jyT?zODdyNv{KmY_l00ck)1V8`;KmY_l00ck)1ooJK*!rusExk%bdgZe8EK7Q& zlJrbddPRAyKrIyHnPEsz*QKXv23-P>_y3iBd)y3E2Ld1f0w4eaAOHd&00JNY0w4ea zAh4$d#QlHl|MwI*Dgyx!009sH0T2KI5C8!X009sHfjuW6p8v=Gf6uX_LJ$A}5C8!X U009sH0T2KI5C8!X*i! Date: Sun, 31 Mar 2024 12:17:40 +0200 Subject: [PATCH 084/268] linter --- api/tests/serializers/test_gebruiker.py | 4 ++-- api/tests/serializers/test_groep.py | 5 ++--- api/tests/serializers/test_project.py | 6 +++--- api/tests/serializers/test_restrictie.py | 5 ++--- api/tests/views/test_restrictie.py | 12 ++++++------ 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/api/tests/serializers/test_gebruiker.py b/api/tests/serializers/test_gebruiker.py index e14b3df49..c4e4846ac 100644 --- a/api/tests/serializers/test_gebruiker.py +++ b/api/tests/serializers/test_gebruiker.py @@ -55,7 +55,7 @@ def test_update(self): self.assertTrue(serializer.is_valid()) self.gebruiker = serializer.save() self.assertTrue(self.gebruiker.is_lesgever) - + def test_update_invalid_teacher(self): vak = VakFactory.create() vak.studenten.add(self.gebruiker) @@ -78,7 +78,7 @@ def test_update_invalid_student(self): instance=self.gebruiker, data=data, partial=True ) self.assertTrue(serializer.is_valid()) - self.assertRaises(ValidationError, serializer.save, raise_exception=True) + self.assertRaises(ValidationError, serializer.save, raise_exception=True) def test_validation_for_blank_items(self): serializer = GebruikerSerializer(data={"user": "", "is_lesgever": []}) diff --git a/api/tests/serializers/test_groep.py b/api/tests/serializers/test_groep.py index 1ca994f65..da943a99e 100644 --- a/api/tests/serializers/test_groep.py +++ b/api/tests/serializers/test_groep.py @@ -98,16 +98,15 @@ def test_update(self): self.assertEqual( [student.user.id for student in groep.studenten.all()], data["studenten"] ) - + def test_update_invalid_project(self): project = ProjectFactory.create(vak=self.groep.project.vak) data = self.serializer.data - data['project'] = project.project_id + data["project"] = project.project_id serializer = GroepSerializer(instance=self.groep, data=data, partial=True) self.assertTrue(serializer.is_valid()) self.assertRaises(ValidationError, serializer.save, raise_exception=True) - def test_update_invalid_user_already_in_this_group(self): data = self.serializer.data self.assertEqual(len(data["studenten"]), 1) diff --git a/api/tests/serializers/test_project.py b/api/tests/serializers/test_project.py index dd73d2fec..af2c6978b 100644 --- a/api/tests/serializers/test_project.py +++ b/api/tests/serializers/test_project.py @@ -154,12 +154,12 @@ def test_update(self): self.assertTrue(serializer.is_valid()) project = serializer.save() self.assertEqual(project.deadline, parse(data["deadline"])) - + def test_update_invalid_vak(self): vak = VakFactory.create() data = self.serializer.data - data['vak'] = vak.vak_id - data['opgave_bestand'] = SimpleUploadedFile("file.txt", b"file_content") + data["vak"] = vak.vak_id + data["opgave_bestand"] = SimpleUploadedFile("file.txt", b"file_content") serializer = ProjectSerializer(instance=self.project, data=data, partial=True) self.assertTrue(serializer.is_valid()) self.assertRaises(ValidationError, serializer.save, raise_exception=True) diff --git a/api/tests/serializers/test_restrictie.py b/api/tests/serializers/test_restrictie.py index 507065468..d8bef8c7a 100644 --- a/api/tests/serializers/test_restrictie.py +++ b/api/tests/serializers/test_restrictie.py @@ -54,7 +54,7 @@ def test_create(self): + str(data["script"]), ) self.assertEqual(restrictie.moet_slagen, data["moet_slagen"]) - + def test_create_invalid_script(self): data = { "project": self.restrictie.project.project_id, @@ -75,7 +75,7 @@ def test_update(self): self.assertTrue(serializer.is_valid()) self.restrictie = serializer.save() self.assertEqual(self.restrictie.moet_slagen, data["moet_slagen"]) - + def test_update_invalid_project(self): project = ProjectFactory.create() data = self.serializer.data @@ -87,7 +87,6 @@ def test_update_invalid_project(self): self.assertTrue(serializer.is_valid()) self.assertRaises(ValidationError, serializer.save, raise_exception=True) - def test_validation_for_blank_items(self): serializer = RestrictieSerializer( data={"project": "", "script": "", "moet_slagen": ""} diff --git a/api/tests/views/test_restrictie.py b/api/tests/views/test_restrictie.py index 6d87cdd2a..702ca4ce5 100644 --- a/api/tests/views/test_restrictie.py +++ b/api/tests/views/test_restrictie.py @@ -26,9 +26,9 @@ def test_restrictie_list_get_project(self): ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) - + def test_restrictie_list_get_project_invalid(self): - response = self.client.get(self.url, {"project": 'invalid'}) + response = self.client.get(self.url, {"project": "invalid"}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_restrictie_list_get_moet_slagen(self): @@ -54,10 +54,10 @@ def test_restrictie_list_post(self): } response = self.client.post(self.url, data, format="multipart") self.assertEqual(response.status_code, status.HTTP_201_CREATED) - + def test_restrictie_list_post_invalid(self): data = { - "project": 'invalid', + "project": "invalid", "script": SimpleUploadedFile("nieuw_script.sh", b"file_content"), "moet_slagen": False, } @@ -79,7 +79,7 @@ def test_restrictie_detail_get(self): response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["restrictie_id"], self.restrictie.restrictie_id) - + def test_restrictie_detail_get_invalid(self): response = self.client.get(reverse("restrictie_detail", kwargs={"id": 999})) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) @@ -100,7 +100,7 @@ def test_restrictie_put(self): response = self.client.put(self.url, new_data, format="multipart") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["moet_slagen"], not self.restrictie.moet_slagen) - + def test_restrictie_put_invalid(self): new_data = { "restrictie_id": self.restrictie.restrictie_id, From 63a4719ecd3faa643d1dcee086e41014e145d227 Mon Sep 17 00:00:00 2001 From: Alexandre Paice Date: Sun, 31 Mar 2024 12:23:23 +0200 Subject: [PATCH 085/268] Remove api/env from repository --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a5d392e8c..b933cd9cf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ venv /venv/ __pycache__ migrations +api/.env \ No newline at end of file From 0bc7765cfd27d8987513000e9d3771f9303170ad Mon Sep 17 00:00:00 2001 From: Alexandre Paice Date: Sun, 31 Mar 2024 12:28:58 +0200 Subject: [PATCH 086/268] merge conflict fixed --- .../addChangeAssignmentPage/AddChangeAssignmentPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/frontend/src/pages/addChangeAssignmentPage/AddChangeAssignmentPage.tsx b/frontend/frontend/src/pages/addChangeAssignmentPage/AddChangeAssignmentPage.tsx index f27da8ae7..9b7a9d9eb 100644 --- a/frontend/frontend/src/pages/addChangeAssignmentPage/AddChangeAssignmentPage.tsx +++ b/frontend/frontend/src/pages/addChangeAssignmentPage/AddChangeAssignmentPage.tsx @@ -184,7 +184,7 @@ export function AddChangeAssignmentPage() { minutes: renderTimeViewClock, seconds: renderTimeViewClock, }} - slotProps={{ +slotProps={{ textField: { error: assignmentErrors.dueDate, helperText: assignmentErrors.dueDate ? "Deadline" + " " + t('is_required') : "", @@ -201,7 +201,7 @@ export function AddChangeAssignmentPage() { setDescription(event.target.value)} fullWidth - error={assignmentErrors.description} +error={assignmentErrors.description} helperText={assignmentErrors.description ? t("descriptionName") + " " + t('is_required') : ""} sx={{overflowY: 'auto', maxHeight: '25svh'}}/>
@@ -298,7 +298,7 @@ export function AddChangeAssignmentPage() { - Date: Sun, 31 Mar 2024 12:42:42 +0200 Subject: [PATCH 087/268] internationalisatie van opdracht student/leekracht en groepen pagina --- .../AssignmentListItemTeacherPage.tsx | 4 ++-- frontend/frontend/src/i18n/en.ts | 15 +++++++++++++++ frontend/frontend/src/i18n/nl.ts | 15 +++++++++++++++ .../assignmentPage/assignmentStudentPage.tsx | 13 +++++++------ .../assignmentPage/assignmentTeacherPage.tsx | 17 ++++++----------- .../src/pages/groupsPage/groupsPage.tsx | 13 +++++++------ 6 files changed, 52 insertions(+), 25 deletions(-) diff --git a/frontend/frontend/src/components/AssignmentListItemTeacherPage.tsx b/frontend/frontend/src/components/AssignmentListItemTeacherPage.tsx index 550c7382a..a021f4fac 100644 --- a/frontend/frontend/src/components/AssignmentListItemTeacherPage.tsx +++ b/frontend/frontend/src/components/AssignmentListItemTeacherPage.tsx @@ -48,8 +48,8 @@ export function AssignmentListItemTeacherPage({id,studentName,submitted, score}: color: 'primary.light', }, }} primary={studentName}/> - - + + {submitted ? ( Deadline {deadline}
@@ -85,7 +86,7 @@ export function AssignmentStudentPage() { }} > - Opgave + {t("assignment")} {text} @@ -100,8 +101,8 @@ export function AssignmentStudentPage() { }} > - Indiening - Tijdstip + {t("submission")} + {t("time")} Status @@ -131,11 +132,11 @@ export function AssignmentStudentPage() { >
diff --git a/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx b/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx index 29fd3d99b..90ef4fb7d 100644 --- a/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx +++ b/frontend/frontend/src/pages/assignmentPage/assignmentTeacherPage.tsx @@ -1,12 +1,7 @@ import {Header} from "../../components/Header.tsx"; import { AssignmentListItemTeacherPage } from "../../components/AssignmentListItemTeacherPage.tsx"; import { Box, Button, Card, Divider, Grid, List, ListItem, ListItemText, ListSubheader, Stack, TextField, Typography} from "@mui/material"; -import DownloadIcon from '@mui/icons-material/Download'; -import SaveIcon from '@mui/icons-material/Save'; -import AddIcon from '@mui/icons-material/Add'; -import {LocalizationProvider} from '@mui/x-date-pickers'; -import {AdapterDayjs} from '@mui/x-date-pickers/AdapterDayjs' -import {DatePicker} from '@mui/x-date-pickers/DatePicker'; +import {t} from "i18next"; const text = "Lorem ipsum dolor sit amet consectetur. Nisi magna dolor et nisi nibh et velit phasellus. Aliquam semper justo posuere suspendisse amet amet nam nec. Tellus magna in proin tempor hac sit. Faucibus laoreet nulla commodo quis. Porttitor sit facilisis sit dignissim quis. Malesuada etiam tempor donec et ante. Aliquam massa donec augue aliquam semper amet blandit sed faucibus. Et elementum duis adipiscing turpis mi. Senectus eu rutrum accumsan convallis metus mattis risus. Quam eget sapien tellus aliquam facilisi sit volutpat. Scelerisque auctor purus nam sit lacus amet ullamcorper amet. Turpis nulla quis in pretium. Maecenas aliquam ac ullamcorper suspendisse morbi cras. Mi nibh aliquet massa sit eget tristique a. Posuere pretium auctor tellus massa et eu egestas. Sit lorem proin aenean tortor morbi condimentum. Leo eu enim cursus tempus sed viverra laoreet. Nisl ornare velit molestie suspendisse. Hendrerit nibh mauris vulputate sit vitae. Tellus quisque non nibh proin nunc lacus scelerisque dui. Aliquam fermentum libero aliquet volutpat at. Vestibulum ultrices nec felis leo nibh viverra. Hendrerit ut nunc porta egestas sit velit dictumst dis porta. Donec quam aliquam commodo mattis purus. Tellus nulla lectus fusce in fames scelerisque at." @@ -100,7 +95,7 @@ export function AssignmentTeacherPage() { }} > - Opgave + {t("assignment")} {text} @@ -116,9 +111,9 @@ export function AssignmentTeacherPage() { > Student - Tijdstip + {t("time")} Score - Download + {t("download")} @@ -149,11 +144,11 @@ export function AssignmentTeacherPage() { >
diff --git a/frontend/frontend/src/pages/groupsPage/groupsPage.tsx b/frontend/frontend/src/pages/groupsPage/groupsPage.tsx index db0ece0a4..72756bc6c 100644 --- a/frontend/frontend/src/pages/groupsPage/groupsPage.tsx +++ b/frontend/frontend/src/pages/groupsPage/groupsPage.tsx @@ -2,6 +2,7 @@ import {Header} from "../../components/Header.tsx"; import {Box, Divider, Grid, List, Stack, TextField, Typography} from "@mui/material"; import Switch from '@mui/material/Switch'; import { GroupListItem } from "../../components/GroupListItem.tsx"; +import {t} from "i18next"; const groups = [ { @@ -74,13 +75,13 @@ export function GroupsPage() { }} > - Groepen + {t("groups")} - Leden per groep + {t("amount")} {t("members")}/{t("group")} @@ -90,11 +91,11 @@ export function GroupsPage() { - Willekeurige groepen + {t("random")} {t("groups")} - Studenten kunnen kiezen + {t("students_choose")} @@ -109,8 +110,8 @@ export function GroupsPage() { > - Indiening - Ingeschreven groep + Student + {t("group_members")} From 776dd5e26e9ba08609f29fbab86d87b7c0b8973b Mon Sep 17 00:00:00 2001 From: Ben De Meurichy Date: Sun, 31 Mar 2024 15:43:12 +0200 Subject: [PATCH 088/268] authenticatie functionaliteit toegevoegd, nog error voor externe profielen --- .../frontend/src/authConfig/authConfig.ts | 2 +- frontend/frontend/src/authConfig/graph.ts | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 frontend/frontend/src/authConfig/graph.ts diff --git a/frontend/frontend/src/authConfig/authConfig.ts b/frontend/frontend/src/authConfig/authConfig.ts index 42b9d1b80..1cbb3f375 100644 --- a/frontend/frontend/src/authConfig/authConfig.ts +++ b/frontend/frontend/src/authConfig/authConfig.ts @@ -22,7 +22,7 @@ import authSecrets from "./authSecrets.ts"; export const msalConfig = { auth: { clientId: authSecrets.clientId, - authority: authSecrets.authority, + authority: "https://login.microsoftonline.com/" + authSecrets.authority, redirectUri: window.location.origin + "/", // This redirectUri is the default for single-page applications }, cache: { diff --git a/frontend/frontend/src/authConfig/graph.ts b/frontend/frontend/src/authConfig/graph.ts new file mode 100644 index 000000000..c207fb4ed --- /dev/null +++ b/frontend/frontend/src/authConfig/graph.ts @@ -0,0 +1,21 @@ +import {graphConfig} from "./authConfig"; + +/** + * Attaches a given access token to a MS Graph API call. Returns information about the user + * @param accessToken + */ +export async function callMsGraph(accessToken: string) { + const headers = new Headers(); + const bearer = `Bearer ${accessToken}`; + + headers.append("Authorization", bearer); + + const options = { + method: "GET", + headers: headers + }; + + return fetch(graphConfig.graphMeEndpoint, options) + .then(response => response.json()) + .catch(error => console.log(error)); +} \ No newline at end of file From df376cd35ed9282371420e5533d947dd94319917 Mon Sep 17 00:00:00 2001 From: lgdtimtou Date: Sun, 31 Mar 2024 16:07:15 +0200 Subject: [PATCH 089/268] Begin docker implementatie backend --- api/docker/Dockerfile | 19 +++++++++++++++++++ api/docker/python_entrypoint.py | 11 +++++++++++ api/docker/rundocker.sh | 4 ++++ api/docker/testing_entrypoint.sh | 15 +++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 api/docker/Dockerfile create mode 100644 api/docker/python_entrypoint.py create mode 100644 api/docker/rundocker.sh create mode 100644 api/docker/testing_entrypoint.sh diff --git a/api/docker/Dockerfile b/api/docker/Dockerfile new file mode 100644 index 000000000..e16c06722 --- /dev/null +++ b/api/docker/Dockerfile @@ -0,0 +1,19 @@ +FROM ubuntu:latest +LABEL maintainer="Your Name " +ARG INDIENING_ID +ARG PROJECT_ID +ARG CUR_DIR +RUN apt-get update -y && \ + apt-get upgrade -y && \ + apt-get dist-upgrade -y && \ + apt-get -y autoremove && \ + apt-get clean +RUN apt-get install -y zip \ + unzip \ + python3 \ + && rm -rf /var/lib/apt/lists/* +# Copy the script to the container +ADD ./testing_entrypoint.sh / +RUN chmod +x /testing_entrypoint.sh +# Set the entrypoint to the script with CMD arguments +ENTRYPOINT ["/testing_entrypoint.sh", $INDIENING_ID, $PROJECT_ID] \ No newline at end of file diff --git a/api/docker/python_entrypoint.py b/api/docker/python_entrypoint.py new file mode 100644 index 000000000..55b935e9f --- /dev/null +++ b/api/docker/python_entrypoint.py @@ -0,0 +1,11 @@ +import os + + +def run_tests_on(indiening_id, project_id): + stream = os.popen(f'bash rundocker.sh {indiening_id} {project_id}') + output = stream.read() + print("-----\n" + output) + + + +run_tests_on(20, 12) \ No newline at end of file diff --git a/api/docker/rundocker.sh b/api/docker/rundocker.sh new file mode 100644 index 000000000..4df5ef9a3 --- /dev/null +++ b/api/docker/rundocker.sh @@ -0,0 +1,4 @@ +yes | docker system prune -a >&2 +docker build -t script-demo --build-arg INDIENING_ID=$1 --build-arg PROJECT_ID=$2 . +docker run --mount type=bind,source="$(pwd)"/../../data,target=/data --name demo -d script-demo +docker logs demo -f diff --git a/api/docker/testing_entrypoint.sh b/api/docker/testing_entrypoint.sh new file mode 100644 index 000000000..12e8d51cc --- /dev/null +++ b/api/docker/testing_entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/bash +echo $1 +echo $2 +echo $(ls) +for filename in ./data/restricties/*; do + echo "Testing ${filename:8} ..." + + if [[ "$filename" == *.sh ]] + then + bash $filename + elif [[ "$filename" == *.py ]] + then + python3 $filename + fi +done From 0744cb5ccb170bbeeda43c462aad6cd72266981d Mon Sep 17 00:00:00 2001 From: lgdtimtou Date: Sun, 31 Mar 2024 16:15:14 +0200 Subject: [PATCH 090/268] Kleine fix om te zorgen dat het microsoft identity association bestand niet redirecte naar de login pagina --- api/middleware.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/middleware.py b/api/middleware.py index 685195c61..1e8b09d32 100644 --- a/api/middleware.py +++ b/api/middleware.py @@ -25,6 +25,7 @@ def __call__(self, request): if request.user.is_anonymous and request.path not in [ "/oauth2/login", "/oauth2/callback", + "/.well-known/microsoft-identity-association.json" ]: # Redirect to the login page return redirect(settings.LOGIN_URL) From 0f8dcee9fdfd43041b856b6f83e439c6c755c29a Mon Sep 17 00:00:00 2001 From: Mathis De Witte Date: Sun, 31 Mar 2024 18:27:54 +0200 Subject: [PATCH 091/268] setup restricties UI --- .coveragerc | 10 ++ .gitignore | 8 +- README.md.orig | 5 + .../assignmentPage/AddRestrictionButton.tsx | 57 ++++++++++ .../assignmentPage/AssignmentTeacherPage.tsx | 7 +- .../assignmentPage/RestrictionsDialog.tsx | 105 ++++++++++++++++++ frontend/package-lock.json | 6 + 7 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 .coveragerc create mode 100644 README.md.orig create mode 100644 frontend/frontend/src/pages/assignmentPage/AddRestrictionButton.tsx create mode 100644 frontend/frontend/src/pages/assignmentPage/RestrictionsDialog.tsx create mode 100644 frontend/package-lock.json diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..26072f108 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,10 @@ +[run] +omit= + */tests/* + */migrations/* + */__init__.py + */__pycache__/* + */data/* + */frontend/* + */static/* + */.github/* \ No newline at end of file diff --git a/.gitignore b/.gitignore index a5d392e8c..1fc17a763 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,11 @@ projectenv venv .idea /venv/ -__pycache__ +**/__pycache__/ migrations +.env +htmlcov +data +uploads +api/.env + diff --git a/README.md.orig b/README.md.orig new file mode 100644 index 000000000..b13c6ec06 --- /dev/null +++ b/README.md.orig @@ -0,0 +1,5 @@ +# UGent-4 +<<<<<<< HEAD +======= + +>>>>>>> 43302259fa87109dcb81dc91d568e658a9aa7358 diff --git a/frontend/frontend/src/pages/assignmentPage/AddRestrictionButton.tsx b/frontend/frontend/src/pages/assignmentPage/AddRestrictionButton.tsx new file mode 100644 index 000000000..f3d272f2e --- /dev/null +++ b/frontend/frontend/src/pages/assignmentPage/AddRestrictionButton.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import Dialog, { DialogProps } from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import AddIcon from '@mui/icons-material/Add'; +import RestrictionsDialog from './RestrictionsDialog'; + + + +export default function AddRestrictionButton() { + const [open, setOpen] = React.useState(false); + const [scroll, setScroll] = React.useState('paper'); + + const handleClose = () => { + setOpen(false); + }; + + const descriptionElementRef = React.useRef(null); + React.useEffect(() => { + if (open) { + const { current: descriptionElement } = descriptionElementRef; + if (descriptionElement !== null) { + descriptionElement.focus(); + } + } + }, [open]); + + return ( + + + + Add restriction + + + + + + + + + ); +} \ No newline at end of file diff --git a/frontend/frontend/src/pages/assignmentPage/AssignmentTeacherPage.tsx b/frontend/frontend/src/pages/assignmentPage/AssignmentTeacherPage.tsx index 660605aeb..2f3a7b614 100644 --- a/frontend/frontend/src/pages/assignmentPage/AssignmentTeacherPage.tsx +++ b/frontend/frontend/src/pages/assignmentPage/AssignmentTeacherPage.tsx @@ -3,6 +3,7 @@ import {Button, Card, Divider, Grid, List, ListItem, ListItemText, Stack, TextFi import UploadIcon from '@mui/icons-material/Upload'; import SaveIcon from '@mui/icons-material/Save'; import AddIcon from '@mui/icons-material/Add'; +import AddRestrictionButton from "./AddRestrictionButton.tsx"; import {LocalizationProvider} from '@mui/x-date-pickers'; import {AdapterDayjs} from '@mui/x-date-pickers/AdapterDayjs' import {DatePicker} from '@mui/x-date-pickers/DatePicker'; @@ -116,9 +117,11 @@ export function AssignmentTeacherPage() { - + */} diff --git a/frontend/frontend/src/pages/assignmentPage/RestrictionsDialog.tsx b/frontend/frontend/src/pages/assignmentPage/RestrictionsDialog.tsx new file mode 100644 index 000000000..352e4b8fa --- /dev/null +++ b/frontend/frontend/src/pages/assignmentPage/RestrictionsDialog.tsx @@ -0,0 +1,105 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import AppBar from '@mui/material/AppBar'; +import Toolbar from '@mui/material/Toolbar'; +import IconButton from '@mui/material/IconButton'; +import Typography from '@mui/material/Typography'; +import CloseIcon from '@mui/icons-material/Close'; +import Slide from '@mui/material/Slide'; +import { TransitionProps } from '@mui/material/transitions'; +import { ButtonGroup, TextField } from '@mui/material'; + +const Transition = React.forwardRef(function Transition( + props: TransitionProps & { + children: React.ReactElement; + }, + ref: React.Ref, +) { + return ; +}); + +export default function RestrictionsDialog(){ + const [open, setOpen] = React.useState(false); + const fileInput = React.useRef(null); + + + const handleClickOpen = () => { + setOpen(true); + }; + + const buttons = [ + , + , + , + , + ]; + + + + const handleClose = () => { + setOpen(false); + }; + + return ( + + + + {buttons} + + + + + + + + + Test code + + + + + + + + {/*