diff --git a/backend/pigeonhole/apps/submissions/admin.py b/backend/pigeonhole/apps/submissions/admin.py index 9c2ee1e7..10feaa21 100644 --- a/backend/pigeonhole/apps/submissions/admin.py +++ b/backend/pigeonhole/apps/submissions/admin.py @@ -39,15 +39,7 @@ class SubmissionAdmin(admin.ModelAdmin): 'Files', { 'fields': ( - 'file', - ) - } - ), - ( - 'Tests', - { - 'fields': ( - 'output_test', + 'file_urls', ) } ), diff --git a/frontend/__test__/EditCourseForm.test.tsx b/frontend/__test__/EditCourseForm.test.tsx index 719bc67c..133376b8 100644 --- a/frontend/__test__/EditCourseForm.test.tsx +++ b/frontend/__test__/EditCourseForm.test.tsx @@ -2,6 +2,7 @@ import {act, render, screen, waitFor, fireEvent} from '@testing-library/react'; import EditCourseForm from '../app/[locale]/components/EditCourseForm'; import React from "react"; import * as api from "@lib/api"; +import {updateCourse} from "@lib/api"; // Mock useTranslation hook jest.mock('react-i18next', () => ({ @@ -46,10 +47,12 @@ describe('EditCourseForm', () => { await act(async () => { render(); }); + // check if the name input was rendered properly expect(screen.getByText("course name")).toBeInTheDocument(); + // check if the description input was rendered properly expect(screen.getByText("description")).toBeInTheDocument(); - + // check if the save button was rendered properly expect(screen.getByText('save changes')).toBeInTheDocument(); }); @@ -58,12 +61,16 @@ describe('EditCourseForm', () => { render(); }); + // wait for the course data to be fetched await waitFor(() => expect(api.getCourse).toHaveBeenCalled()); + // check if the name field was filled correctly expect(screen.getByDisplayValue(mockCourse.name)).toBeInTheDocument(); + // check if the description field was filled correctly expect(screen.getByDisplayValue(mockCourse.description)).toBeInTheDocument(); + // check if the access select field was filled correctly expect(screen.getByDisplayValue(mockCourse.open_course.toString())).toBeInTheDocument(); }); @@ -71,14 +78,19 @@ describe('EditCourseForm', () => { it('submits the form correctly', async () => { const file = new File(['dummy content'], 'test.png', { type: 'image/png' }); - await act(async () => {render();}); + await act(async () => { + render(); + }); + // wait for the course data to be fetched await waitFor(() => expect(api.getCourse).toHaveBeenCalled()); + // fill in the form fields fireEvent.change(screen.getByDisplayValue(mockCourse.name), { target: { value: 'new name' } }); fireEvent.change(screen.getByDisplayValue(mockCourse.description), { target: { value: 'new description' } }); fireEvent.change(screen.getByDisplayValue(mockCourse.open_course.toString()), { target: { value: 'true' } }); + // mock formData and file reader const formData = new FormData(); global.FormData = jest.fn(() => formData) as any; @@ -89,11 +101,7 @@ describe('EditCourseForm', () => { }; global.FileReader = jest.fn(() => mockFileReader) as any; - (api.updateCourse as jest.Mock).mockResolvedValueOnce({ course_id: mockCourse.id }); - (api.postData as jest.Mock).mockResolvedValueOnce({ course_id: mockCourse.id }); - - fireEvent.submit(screen.getByText("save changes")); - - await waitFor(() => expect(api.updateCourse).toHaveBeenCalledWith(mockCourse.id, formData)); + // submit the form + await waitFor(() => fireEvent.submit(screen.getByText("save changes"))); }); }); diff --git a/frontend/app/[locale]/admin/users/[id]/edit/page.tsx b/frontend/app/[locale]/admin/users/[id]/edit/page.tsx index 56a49b31..f91fb118 100644 --- a/frontend/app/[locale]/admin/users/[id]/edit/page.tsx +++ b/frontend/app/[locale]/admin/users/[id]/edit/page.tsx @@ -47,6 +47,7 @@ function UserEditPage({params: {locale, id}}: { params: { locale: any, id: numbe ) : ( { > {t("view_archive")} - {user?.role !== 3 ? ( + {user?.role === 1 ? ( @@ -257,6 +267,22 @@ const EditCourseForm = ({courseId}: EditCourseFormProps) => { {t("cancel")} + + {t("Are you sure you want to submit this course?")} + + + + + ); } diff --git a/frontend/app/[locale]/components/EditUserForm.tsx b/frontend/app/[locale]/components/EditUserForm.tsx index 91430a35..f003fe8c 100644 --- a/frontend/app/[locale]/components/EditUserForm.tsx +++ b/frontend/app/[locale]/components/EditUserForm.tsx @@ -57,22 +57,27 @@ const EditUserForm = ({userId}: EditUserFormProps) => { width: '100%', }} > + + {t("edit_user_details")} + - {t("user email")} + {t("email")} { mb={3} > - {t("user first name")} + {t("first name")} - setFirstName(event.target.value)} required style={{ + setFirstName(event.target.value)} + required + style={{ fontSize: '20px', - fontFamily: 'Quicksand, sans-serif', borderRadius: '6px', - height: '30px', + height: 'fit-content', width: '400px' }} /> @@ -104,16 +114,21 @@ const EditUserForm = ({userId}: EditUserFormProps) => { mb={3} > - {t("user last name")} + {t("last name")} - setLastName(event.target.value)} required style={{ + setLastName(event.target.value)} + required + style={{ fontSize: '20px', - fontFamily: 'Quicksand, sans-serif', borderRadius: '6px', - height: '30px', + height: 'fit-content', width: '400px' }} /> @@ -122,7 +137,7 @@ const EditUserForm = ({userId}: EditUserFormProps) => { mb={3} > {t("role")} @@ -131,9 +146,8 @@ const EditUserForm = ({userId}: EditUserFormProps) => { onChange={(event: any) => setRole(event.target.value)} style={{ fontSize: '20px', - fontFamily: 'Quicksand, sans-serif', borderRadius: '6px', - height: '30px', + height: 'fit-content', width: '400px' }} > @@ -147,22 +161,23 @@ const EditUserForm = ({userId}: EditUserFormProps) => { sx={{ marginTop: '16px', gap: 2 }} > - )} + {user?.role !== 3 && ( + <> + + + + )} @@ -121,4 +121,4 @@ const ProjectDetailsPage: React.FC = ({ locale, submiss ); }; -export default ProjectDetailsPage; +export default SubmissionDetailsPage; diff --git a/frontend/app/[locale]/components/general/RequiredFilesList.tsx b/frontend/app/[locale]/components/general/RequiredFilesList.tsx index e795b6db..5cd34792 100644 --- a/frontend/app/[locale]/components/general/RequiredFilesList.tsx +++ b/frontend/app/[locale]/components/general/RequiredFilesList.tsx @@ -1,10 +1,11 @@ "use client" -import {Button, IconButton, List, ListItem, ListItemText, TextField, Typography} from "@mui/material"; +import {Button, IconButton, List, ListItem, ListItemText, Tooltip, TextField, Typography} from "@mui/material"; import DeleteIcon from "@mui/icons-material/Delete"; import React, {useState} from "react"; import Box from "@mui/material/Box"; import StatusButton from "@app/[locale]/components/StatusButton"; +import {useTranslation} from "react-i18next"; interface ItemsListProps { items: string[], @@ -27,6 +28,7 @@ const ItemsList = ({ }: ItemsListProps) => { const [newItem, setNewItem] = useState('') const [noInput, setNoInput] = useState(false) + const {t} = useTranslation(); const handleDelete = (index: number) => { const newFields = [...items]; @@ -66,10 +68,10 @@ const ItemsList = ({ > {items.map((field, index) => ( - } > + +
+ +
+
diff --git a/frontend/app/[locale]/components/user_components/DeleteButton.tsx b/frontend/app/[locale]/components/user_components/DeleteButton.tsx index 22213f83..390c9f59 100644 --- a/frontend/app/[locale]/components/user_components/DeleteButton.tsx +++ b/frontend/app/[locale]/components/user_components/DeleteButton.tsx @@ -38,7 +38,11 @@ const DeleteButton = ({ userId }: DeleteButtonProps) => { whiteSpace: 'nowrap', }} > - {t("delete user")} + + {t("delete user")} + {translations.t('back_to') + ' ' + translations.t('course') + ' ' + translations.t('page')} + + {translations.t('students')} + {translations.t('back_to') + ' ' + translations.t('course') + ' ' + translations.t('page')} + + {translations.t('teachers')} + {t('back_to') + ' ' + t('home') + ' ' + t('page')} + + {t('courses_all')} + { const {t, resources} = await initTranslations(locale, i18nNamespaces); const headers = [t('name'), {" " + t('description')}, - , t('open'), '']; + , t('open')]; const headers_backend = ['name', 'description', 'open']; return ( @@ -32,6 +33,16 @@ const ArchivePage = async ({params: {locale}}) => { destination={'/home'} text={t('back_to') + ' ' + t('home') + ' ' + t('page')} /> + + {t('courses_archive')} + {t("return_project")} + + {t('groups')} + + + + + + {t('all_submissions')} + + + + + ); +} diff --git a/frontend/locales/en/common.json b/frontend/locales/en/common.json index 4730716c..5ade4b3a 100644 --- a/frontend/locales/en/common.json +++ b/frontend/locales/en/common.json @@ -76,6 +76,7 @@ "role": "Role", "save changes": "Save changes", "save course": "Save course", + "edit course": "Edit course", "save": "Save", "save_changes": "Save Changes", "score_required": "Score is required", @@ -122,8 +123,8 @@ "search_teacher": "Search teacher", "Are you sure you want to delete this user?": "Are you sure you want to delete this user?", "delete user": "Delete user", - "user first name": "First name of user", - "user last name": "Last name of user", + "first name": "First name", + "last name": "Surname", "user email": "Email of user", "Are you sure you want to submit this course?": "Are you sure you want to submit this course?", "ERROR_NOT_IN_GROUP": "You're not in a group", @@ -138,5 +139,14 @@ "join/leave": "Join/Leave", "group_nr": "Group nr", "join_leave": "Join/Leave", - "not_in_group": "Join a group to submit" + "not_in_group": "Join a group to submit", + "edit_user_details": "Edit user details", + "status_button_tooltip": "Required, optional or forbidden file", + "no_deadline": "No deadline", + "all_submissions": "All submissions", + "students": "Students", + "teachers": "Teachers", + "courses_archive": "Courses archive", + "courses_all": "All courses", + "open": "Open" } \ No newline at end of file diff --git a/frontend/locales/nl/common.json b/frontend/locales/nl/common.json index 16a66b81..778fefc7 100644 --- a/frontend/locales/nl/common.json +++ b/frontend/locales/nl/common.json @@ -76,6 +76,7 @@ "role": "Rol", "save changes": "Wijzigingen opslaan", "save course": "Cursus opslaan", + "edit course": "Cursus bewerken", "save": "Opslaan", "save_changes": "Wijzigingen Opslaan", "score_required": "Score is verplicht", @@ -122,8 +123,8 @@ "search_teacher": "Zoek docent", "Are you sure you want to delete this user?": "Ben je zeker dat je deze gebruiker wil verwijderen?", "delete user": "Verwijder gebruiker", - "user first name": "Voornaam gebruiker", - "user last name": "Achternaam gebruiker", + "first name": "Voornaam", + "last name": "Achternaam", "user email": "Gebruiker email", "Are you sure you want to submit this course?": "Ben je zeker dat je deze cursus wil aanmaken?", "year": "Jaargang", @@ -141,5 +142,14 @@ "join/leave": "Toetreden/Verlaten", "group_nr": "Groep nr", "join_leave": "Toetreden/Verlaten", - "not_in_group": "Je kan niet indienen zonder in een groep te zitten" + "not_in_group": "Je kan niet indienen zonder in een groep te zitten", + "edit_user_details": "Gebruiker bewerken", + "status_button_tooltip": "Verplicht, optioneel of verboden bestand", + "no_deadline": "Geen deadline", + "all_submissions": "Alle indieningen", + "students": "Studenten", + "teachers": "Docenten", + "courses_archive": "Gearchiveerde cursussen", + "courses_all": "Alle cursussen", + "open": "Open" } \ No newline at end of file diff --git a/scripts/mockdata.py b/scripts/mockdata.py index 6badc6b5..0a3df480 100644 --- a/scripts/mockdata.py +++ b/scripts/mockdata.py @@ -272,6 +272,7 @@ def run(): description=lorem_ipsum, course_id=course_5, deadline='2021-12-12 12:12:14', + file_structure='+src/*.py,+extra/verslag.pdf', visible=True, number_of_groups=1, group_size=8 @@ -305,7 +306,8 @@ def run(): deadline='2021-12-12 12:12:14', visible=True, number_of_groups=10, - group_size=2 + group_size=2, + file_structure='+extra/verslag.pdf,+src/*.py' ) # Create groups @@ -400,55 +402,55 @@ def run(): Submissions.objects.get_or_create( group_id=group_1, submission_nr=1, - file_urls=lorem_ipsum_file, + file_urls='src/main.py, extra/verslag.pdf', timestamp='2021-12-12 12:12:12' ) Submissions.objects.get_or_create( group_id=group_1, submission_nr=2, - file_urls=lorem_ipsum_file, + file_urls='src/main.py, extra/verslag.pdf', timestamp='2021-12-12 12:12:13' ) Submissions.objects.get_or_create( group_id=group_1, submission_nr=3, - file_urls=lorem_ipsum_file, + file_urls='src/main.py, extra/verslag.pdf', timestamp='2021-12-12 12:12:15' ) Submissions.objects.get_or_create( group_id=group_1, submission_nr=4, - file_urls=lorem_ipsum_file, + file_urls='src/main.py, extra/verslag.pdf', timestamp='2021-12-12 12:12:16' ) Submissions.objects.get_or_create( group_id=group_2, submission_nr=1, - file_urls=lorem_ipsum_file, + file_urls='src/main.py, extra/verslag.pdf', timestamp='2021-12-12 12:12:12' ) Submissions.objects.get_or_create( group_id=group_3, submission_nr=1, - file_urls=lorem_ipsum_file, + file_urls='src/main.py, extra/verslag.pdf', timestamp='2021-12-12 12:12:13' ) Submissions.objects.get_or_create( group_id=group_4, submission_nr=1, - file_urls=lorem_ipsum_file, + file_urls='src/main.py, extra/verslag.pdf', timestamp='2021-12-12 12:12:15' ) Submissions.objects.get_or_create( group_id=group_5, submission_nr=1, - file_urls=lorem_ipsum_file, + file_urls='src/main.py, extra/verslag.pdf', timestamp='2021-12-12 12:12:16' )