diff --git a/frontend/frontend/src/components/CopyToClipboard.tsx b/frontend/frontend/src/components/CopyToClipboard.tsx index fdaf5818..80c021a1 100644 --- a/frontend/frontend/src/components/CopyToClipboard.tsx +++ b/frontend/frontend/src/components/CopyToClipboard.tsx @@ -1,8 +1,10 @@ import { useState } from 'react' +import { darken } from '@mui/system' import { Box, Collapse, Tooltip, Typography } from '@mui/material' -import Button from '@mui/material/Button' +import { Button } from './CustomComponents.tsx' import { AssignmentTurnedIn, ContentPaste } from '@mui/icons-material' import { t } from 'i18next' +import theme from '../Theme.ts' interface CopyToClipboardProps { invitationLink: string @@ -42,13 +44,12 @@ export const CopyToClipboard = ({ invitationLink }: CopyToClipboardProps) => { > { padding: 0, gap: 1, '&:hover': { - backgroundColor: 'secondary.main', + backgroundColor: darken( + theme.palette.secondary.main, + 0.2 + ), }, }} > diff --git a/frontend/frontend/src/components/CustomComponents.tsx b/frontend/frontend/src/components/CustomComponents.tsx index 21d8c38e..f5bf4610 100644 --- a/frontend/frontend/src/components/CustomComponents.tsx +++ b/frontend/frontend/src/components/CustomComponents.tsx @@ -1,24 +1,33 @@ +import { darken } from '@mui/system' import { Button as BaseButton, Card as BaseCard, Divider as BaseDivider, + Box, + ButtonProps, + CardProps, + DividerProps, } from '@mui/material' import theme from '../Theme.ts' +import { ReactNode } from 'react' -export const Button = ({ children, ...props }: any) => { +interface PrimaryButtonProps extends ButtonProps { + children: ReactNode +} + +export const Button = ({ children, ...props }: PrimaryButtonProps) => { return ( { ) } -export const Card = ({ children, ...props }: any) => { +interface SecondaryButtonProps extends ButtonProps { + children: ReactNode +} + +export const SecondaryButton = ({ + children, + ...props +}: SecondaryButtonProps) => { + return ( + + {children} + + ) +} + +interface CustomCardProps extends CardProps { + children: ReactNode +} + +export const Card = ({ children, ...props }: CustomCardProps) => { return ( { ) } -export const Divider = ({ children, ...props }: any) => { +interface CustomDividerProps extends DividerProps { + children?: ReactNode +} + +export const Divider = ({ children, ...props }: CustomDividerProps) => { return ( { ) } -export default { Button, Card, Divider } +interface EvenlySpacedRowProps { + items: ReactNode[] +} + +export const EvenlySpacedRow = ({ items }: EvenlySpacedRowProps) => { + return ( + + {items.map((item, index) => ( + + {item} + + ))} + + ) +} + +export default { Button, Card, Divider, EvenlySpacedRow } diff --git a/frontend/frontend/src/components/DeadlineCalendar.tsx b/frontend/frontend/src/components/DeadlineCalendar.tsx index 5ce3c688..51c041a8 100644 --- a/frontend/frontend/src/components/DeadlineCalendar.tsx +++ b/frontend/frontend/src/components/DeadlineCalendar.tsx @@ -7,13 +7,13 @@ import { import dayjs, { Dayjs } from 'dayjs' import { Badge, + SxProps, + Stack, + Typography, List, ListItem, ListItemButton, ListItemText, - Stack, - SxProps, - Typography, } from '@mui/material' import { useEffect, useRef, useState } from 'react' import AssignmentIcon from '@mui/icons-material/Assignment' @@ -125,14 +125,7 @@ function DeadlineMenu({ assignments, selectedDay }: DeadlineMenuProps) { handleProjectClick( @@ -141,11 +134,8 @@ function DeadlineMenu({ assignments, selectedDay }: DeadlineMenuProps) { ) } > - - - {assignment.titel} - - + + ))} diff --git a/frontend/frontend/src/components/Header.tsx b/frontend/frontend/src/components/Header.tsx index bf43afc6..750212c3 100644 --- a/frontend/frontend/src/components/Header.tsx +++ b/frontend/frontend/src/components/Header.tsx @@ -194,6 +194,7 @@ export const Header = ({ variant, title }: Props) => { sx={{ color: 'background.default', paddingTop: 1, + textTransform: 'none', }} > Logout diff --git a/frontend/frontend/src/components/SubmissionListItemStudentPage.tsx b/frontend/frontend/src/components/SubmissionListItemStudentPage.tsx index d5ed7d60..4e0f80cf 100644 --- a/frontend/frontend/src/components/SubmissionListItemStudentPage.tsx +++ b/frontend/frontend/src/components/SubmissionListItemStudentPage.tsx @@ -1,8 +1,10 @@ +import { EvenlySpacedRow } from './CustomComponents.tsx' import { ListItem, ListItemButton, ListItemIcon, ListItemText, + Box, } from '@mui/material' import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline' import HighlightOffIcon from '@mui/icons-material/HighlightOff' @@ -49,46 +51,40 @@ export function SubmissionListItemStudentPage({ return ( <> - + - {/* Display submission id */} - , + , + + + {status ? ( + + ) : ( + + )} + + , + ]} /> - {/* Display submission timestamp */} - - {/* Display submission status icon */} - - {status ? ( - - ) : ( - - )} - > diff --git a/frontend/frontend/src/components/SubmissionListItemTeacherPage.tsx b/frontend/frontend/src/components/SubmissionListItemTeacherPage.tsx index cd9b9c8a..8174774d 100644 --- a/frontend/frontend/src/components/SubmissionListItemTeacherPage.tsx +++ b/frontend/frontend/src/components/SubmissionListItemTeacherPage.tsx @@ -3,7 +3,7 @@ import { ListItemButton, ListItemIcon, ListItemText, - Skeleton, + Box, } from '@mui/material' import { useNavigate } from 'react-router-dom' import DownloadIcon from '@mui/icons-material/Download' @@ -14,6 +14,7 @@ import instance from '../axiosConfig' import { Submission } from '../pages/submissionPage/SubmissionPage.tsx' import { t } from 'i18next' import dayjs from 'dayjs' +import { EvenlySpacedRow } from './CustomComponents.tsx' interface SubmissionListItemTeacherPageProps { group_name: string @@ -135,90 +136,75 @@ export function SubmissionListItemTeacherPage({ return ( <> - + - {/* Display group id */} - {!group_name ? ( - - ) : ( - - )} - {/* Display submission timestamp */} - - {/* Display score */} - - {/* Display submission status icon */} - - {!submitted?.status ? ( - - ) : ( - submitted !== undefined && ( - - ) - )} - - {/* Display download icon */} - - - - {submitted ? ( - - ) : ( - - )} - - - + , + , + , + + + {!submitted?.status ? ( + + ) : ( + submitted !== undefined && ( + + ) + )} + + , + + + + {submitted ? ( + + ) : ( + + )} + + + , + ]} + > > diff --git a/frontend/frontend/src/i18n/en.ts b/frontend/frontend/src/i18n/en.ts index a533e24c..61370cea 100644 --- a/frontend/frontend/src/i18n/en.ts +++ b/frontend/frontend/src/i18n/en.ts @@ -116,6 +116,7 @@ const english = { noGroup: "You don't seem to be in a group yet.", contactTeacher: 'Please contact your teacher.', chooseGroup: 'Join a group before submitting.', + group_number: 'Group Number', } export default english diff --git a/frontend/frontend/src/i18n/nl.ts b/frontend/frontend/src/i18n/nl.ts index bfcb54c1..6799bd67 100644 --- a/frontend/frontend/src/i18n/nl.ts +++ b/frontend/frontend/src/i18n/nl.ts @@ -116,6 +116,7 @@ const dutch = { noGroup: 'Je lijkt nog geen groep te hebben.', chooseGroup: 'Kies een groep voor je indient', contactTeacher: 'Gelieve contact op te nemen met je lesgever.', + group_number: 'GroepsNummer', } export default dutch diff --git a/frontend/frontend/src/pages/addChangeAssignmentPage/AddChangeAssignmentPage.tsx b/frontend/frontend/src/pages/addChangeAssignmentPage/AddChangeAssignmentPage.tsx index 667d5978..71ee8679 100644 --- a/frontend/frontend/src/pages/addChangeAssignmentPage/AddChangeAssignmentPage.tsx +++ b/frontend/frontend/src/pages/addChangeAssignmentPage/AddChangeAssignmentPage.tsx @@ -585,7 +585,7 @@ export function AddChangeAssignmentPage() { {/* Deadline section. - There is both the normal deadline, + There is both the normal deadline, and an extra deadline in case people need more time. */} {/* Assignment description */} - + + - - Student - - - {t('time')} - - - Score - - - Status - - - {t('download')} - + + {t('group')} + , + + {t('time')} + , + + Score + , + + Status + , + + {t('download')} + , + ]} + /> + - - {loading ? ( - [...Array(3)].map((_, index) => ( - - )) - ) : ( - - {groups.map((group, index) => ( - - - - - 1 - ? t( - 'group' - ) + - ' ' + - ( - index + - 1 - ).toString() - : singleStudents[ - index - ] - ? singleStudents[ - index - ] - .first_name + - ' ' + - singleStudents[ - index - ] - .last_name - : '' - : '' - } - group_id={group.groep_id.toString()} - assignment_id={ - assignmentId - ? assignmentId - : '' - } - course_id={ - courseId - ? courseId - : '' - } + + + {loading ? ( + [...Array(3)].map( + (_, index) => ( + - - - ))} + ) + ) + ) : ( + <> + {groups.map( + (group, index) => ( + <> + {index != + 0 ? ( + + ) : ( + <>> + )} + + group.groep_id + ) + ) + + 1 + ).toString()} + group_id={group.groep_id.toString()} + assignment_id={ + assignmentId + ? assignmentId + : '' + } + course_id={ + courseId + ? courseId + : '' + } + /> + > + ) + )} + > + )} - )} + @@ -602,20 +604,16 @@ export function AssignmentPage() { > {submissions.length > 0 && ( - {t('export')}{' '} {t('submissions')} - + )} - + {loading ? ( ) : ( - - - {t('adjust_scores')} - - + {t('adjust_scores')} + )} @@ -883,15 +875,7 @@ export function AssignmentPage() { {/* Assignment */} - + {/* Submissions */} - + - - {t('submission')} - - - {t('time')} - - - Status - + + {t('submission')} + , + + {t('time')} + , + + Status + , + ]} + /> + - ( - - + ) : ( + <> + + > + )} + + submission.indiening_id + ) + ) + + 1 + ).toString()} + timestamp={dayjs( + submission.tijdstip + ).format( + 'DD/MM/YYYY HH:mm' + )} + status={ + submission.status > + 0 } - pl={ - 3 + assignment_id={ + assignmentId + ? assignmentId + : '' } - pr={ - 3 + course_id={ + courseId + ? courseId + : '' } - > - - submission.indiening_id - ) - ) + - 1 - ).toString()} - timestamp={dayjs( - submission.tijdstip - ).format( - 'DD/MM/YYYY HH:mm' - )} - status={ - submission.status > - 0 - } - assignment_id={ - assignmentId - ? assignmentId - : '' - } - course_id={ - courseId - ? courseId - : '' - } - /> - + /> ) ) @@ -1082,65 +1059,43 @@ export function AssignmentPage() { {/*Upload button, this is what the student will see. */} - - - { - - } + + + + + - - + {t('submit')} - + - - + + void,handleLeave: ()=> void){ - if(isin){ +function joinLeaveButton( + isin: boolean, + handleJoin: () => void, + handleLeave: () => void +) { + if (isin) { return ( <> - + {t('leave')} > @@ -55,11 +58,7 @@ function joinLeaveButton(isin:boolean,handleJoin: ()=> void,handleLeave: ()=> vo } return ( <> - + {t('join_group')} > @@ -67,15 +66,14 @@ function joinLeaveButton(isin:boolean,handleJoin: ()=> void,handleLeave: ()=> vo } export function ChooseGroup() { - const params = useParams() const navigate = useNavigate() - const [studenten,setStudenten]=useState>({}); - const [groups,setGroups]=useState([]); - const [open,setOpen] = useState(false); + const [studenten, setStudenten] = useState>({}) + const [groups, setGroups] = useState([]) + const [open, setOpen] = useState(false) const [user, setUser] = useState() - const [assignment,setAssignment]= useState() + const [assignment, setAssignment] = useState() const [loading, setLoading] = useState(true) const [userLoading, setUserLoading] = useState(true) @@ -83,12 +81,12 @@ export function ChooseGroup() { const assignmentId = params.assignmentId const handleClose = () => { - setOpen(false); + setOpen(false) } - useEffect(()=>{ + useEffect(() => { setUserLoading(true) - setLoading(true); + setLoading(true) instance .get('/gebruikers/me/') .then((res) => { @@ -99,24 +97,30 @@ export function ChooseGroup() { }) setUserLoading(false) instance - .get('/projecten/'+assignmentId+'/') + .get('/projecten/' + assignmentId + '/') .then((res) => { setAssignment(res.data) }) .catch((err) => { console.log(err) }) - instance.get('projecten/'+assignmentId) - .then((res) =>{ - instance.get('vakken/'+res.data.vak) + instance + .get('projecten/' + assignmentId) + .then((res) => { + instance + .get('vakken/' + res.data.vak) .then((res) => { //setStudenten(res.data.studenten) - for (let i=0;i { setStudenten((oldstudenten) => { - return {...oldstudenten,[res.data.user]: res.data} + return { + ...oldstudenten, + [res.data.user]: res.data, + } }) }) .catch((err) => { @@ -132,12 +136,11 @@ export function ChooseGroup() { console.log(err) }) - - instance.get('groepen/?project='+assignmentId) + instance + .get('groepen/?project=' + assignmentId) .then((res) => { - for (let i=0;i { - + for (let i = 0; i < res.data.length; i++) { + setGroups((oldGroups) => { let found = false const id = res.data[i].groep_id for (const group of oldGroups) { @@ -148,19 +151,18 @@ export function ChooseGroup() { if (found) { return oldGroups } else { - return [...oldGroups,res.data[i]].sort((a,b) =>{ - return a.groep_id-b.groep_id + return [...oldGroups, res.data[i]].sort((a, b) => { + return a.groep_id - b.groep_id }) } }) - } + } }) .catch((err) => { console.log(err) }) - setLoading(false) - },[]) - + setLoading(false) + }, []) return ( <> @@ -181,217 +183,517 @@ export function ChooseGroup() { <> {!user?.is_lesgever ? ( // Rendering UI for teacher - <> - - - - - - - {t('group_number')} - - - {t('members')} - - - - - - :not(style)': { - marginBottom: '8px', - }, - }} - > - {groups.map((group: Group) => { - //const group=getGroup(id) - - const handleJoin = () =>{ - setGroups((oldGroups) :Group[] =>{ - if (user==undefined){ - return oldGroups - } - if (assignment==undefined){ - return oldGroups - } - - let j=0 - - let edittedgroup=undefined - - for (let i = 0; i < oldGroups.length; i++) { - if(oldGroups[i].studenten.includes(user.user)){ - const newgroup1={ - groep_id: oldGroups[i].groep_id, - project: oldGroups[i].project, - studenten: oldGroups[i].studenten.filter(student => student != user.user), - } - - instance.patch("groepen/"+oldGroups[i].groep_id+"/",{studenten: oldGroups[i].studenten.filter(student => student != user.user)}) - .catch((err) =>{ - console.log(err) - }) - j=i - edittedgroup=newgroup1 - } + <> + + + + + + + {t('group_number')} + , + + {t('members')} + , + , + ]} + /> + + :not(style)': { + marginBottom: '8px', + }, + }} + > + {groups.map((group: Group) => { + //const group=getGroup(id) + + const handleJoin = () => { + setGroups( + ( + oldGroups + ): Group[] => { + if ( + user == + undefined + ) { + return oldGroups + } + if ( + assignment == + undefined + ) { + return oldGroups + } - for (let i = 0; i < oldGroups.length; i++) { - if(oldGroups[i].groep_id==group.groep_id){ - if (group.studenten.length >= assignment.max_groep_grootte){ - return oldGroups - } - const newgroup={ - groep_id: group.groep_id, - project: group.project, - studenten: [...group.studenten,user.user], - } - instance.patch("groepen/"+group.groep_id+"/",{studenten: [...group.studenten,user.user]}) - .catch((err) =>{ - console.log(err) - }) + let j = 0 + + let edittedgroup = + undefined + + for ( + let i = 0; + i < + oldGroups.length; + i++ + ) { + if ( + oldGroups[ + i + ].studenten.includes( + user.user + ) + ) { + const newgroup1 = + { + groep_id: + oldGroups[ + i + ] + .groep_id, + project: + oldGroups[ + i + ] + .project, + studenten: + oldGroups[ + i + ].studenten.filter( + ( + student + ) => + student != + user.user + ), + } + + instance + .patch( + 'groepen/' + + oldGroups[ + i + ] + .groep_id + + '/', + { + studenten: + oldGroups[ + i + ].studenten.filter( + ( + student + ) => + student != + user.user + ), + } + ) + .catch( + ( + err + ) => { + console.log( + err + ) + } + ) + j = i + edittedgroup = + newgroup1 + } + } - if (edittedgroup!=undefined){ - if(i{ - return a.groep_id-b.groep_id - }) + for ( + let i = 0; + i < + oldGroups.length; + i++ + ) { + if ( + oldGroups[i] + .groep_id == + group.groep_id + ) { + if ( + group + .studenten + .length >= + assignment.max_groep_grootte + ) { + return oldGroups + } + const newgroup = + { + groep_id: + group.groep_id, + project: + group.project, + studenten: + [ + ...group.studenten, + user.user, + ], + } + instance + .patch( + 'groepen/' + + group.groep_id + + '/', + { + studenten: + [ + ...group.studenten, + user.user, + ], + } + ) + .catch( + ( + err + ) => { + console.log( + err + ) + } + ) + + if ( + edittedgroup != + undefined + ) { + if ( + i < + j + ) { + return [ + ...oldGroups.slice( + 0, + i + ), + newgroup, + ...oldGroups.slice( + i + + 1, + j + ), + edittedgroup, + ...oldGroups.slice( + j + + 1 + ), + ].sort( + ( + a, + b + ) => { + return ( + a.groep_id - + b.groep_id + ) + } + ) + } + return [ + ...oldGroups.slice( + 0, + j + ), + edittedgroup, + ...oldGroups.slice( + j + + 1, + i + ), + newgroup, + ...oldGroups.slice( + i + + 1 + ), + ].sort( + ( + a, + b + ) => { + return ( + a.groep_id - + b.groep_id + ) + } + ) + } + + return [ + ...oldGroups.slice( + 0, + i + ), + newgroup, + ...oldGroups.slice( + i + + 1 + ), + ].sort( + ( + a, + b + ) => { + return ( + a.groep_id - + b.groep_id + ) + } + ) + } + } + return oldGroups + } + ) } - return [...oldGroups.slice(0,j),edittedgroup,...oldGroups.slice(j+1,i),newgroup,...oldGroups.slice(i+1)].sort((a,b) =>{ - return a.groep_id-b.groep_id - }) - } - - return [...oldGroups.slice(0,i),newgroup,...oldGroups.slice(i+1)].sort((a,b) =>{ - return a.groep_id-b.groep_id - }) - } - } - return oldGroups - }) - } - - const handleLeave = () =>{ - setGroups((oldGroups)=>{ - if (user==undefined){ - return oldGroups - } - for (let i = 0; i < oldGroups.length; i++) { - if(oldGroups[i].groep_id==group.groep_id){ - const newgroup={ - groep_id: group.groep_id, - project: group.project, - studenten: group.studenten.filter(student => student != user.user), - } - instance.patch("groepen/"+group.groep_id+"/",{studenten: group.studenten.filter(student => student != user.user)}) - .catch((err) =>{ - console.log(err) - }) - - return [...oldGroups.slice(0,i),newgroup,...oldGroups.slice(i+1)].sort((a,b) =>{ - return a.groep_id-b.groep_id - }) - } - } - return oldGroups - }) - } - - return ( - <> - - - - - {loading ? ( - {t('members_loading')} - ) : ( - <> - {group.studenten.length > 0 ? ( - group.studenten.map((studentid) => { - const student = studenten[studentid]; - if (student) { - console.log('Student:', student); - return ( - - {student.first_name + " " + student.last_name} - - ); + const handleLeave = () => { + setGroups((oldGroups) => { + if (user == undefined) { + return oldGroups + } + for ( + let i = 0; + i < + oldGroups.length; + i++ + ) { + if ( + oldGroups[i] + .groep_id == + group.groep_id + ) { + const newgroup = + { + groep_id: + group.groep_id, + project: + group.project, + studenten: + group.studenten.filter( + ( + student + ) => + student != + user.user + ), + } + instance + .patch( + 'groepen/' + + group.groep_id + + '/', + { + studenten: + group.studenten.filter( + ( + student + ) => + student != + user.user + ), + } + ) + .catch( + ( + err + ) => { + console.log( + err + ) + } + ) + + return [ + ...oldGroups.slice( + 0, + i + ), + newgroup, + ...oldGroups.slice( + i + 1 + ), + ].sort( + (a, b) => { + return ( + a.groep_id - + b.groep_id + ) + } + ) } - return null; - }) - ) : ( - {t('no_members_yet')} - )} - > - )} - - - {joinLeaveButton(user!=undefined ? group.studenten.includes(user.user):false,handleJoin,handleLeave)} - - - - > - ) - }) - } - - - - - {"placeholder"} - - - + } + return oldGroups + }) + } - - - + return ( + <> + + + , + + {loading ? ( + + {t( + 'members_loading' + )} + + ) : ( + <> + {group + .studenten + .length > + 0 ? ( + group.studenten.map( + ( + studentid + ) => { + const student = + studenten[ + studentid + ] + if ( + student + ) { + console.log( + 'Student:', + student + ) + return ( + + {student.first_name + + ' ' + + student.last_name} + + ) + } + return null + } + ) + ) : ( + + {t( + 'no_members_yet' + )} + + )} + > + )} + , + <> + {joinLeaveButton( + user != + undefined + ? group.studenten.includes( + user.user + ) + : false, + handleJoin, + handleLeave + )} + >, + ]} + /> + + > + ) + })} + + + + + + {' '} + {'placeholder'}{' '} + + + + + + + > + ) : ( + navigate('*') + )} + > + )} > - ):( - navigate('*') - )} -> -)} -> -)} \ No newline at end of file + ) +} diff --git a/frontend/frontend/src/pages/groupsPage/GroupsPage.tsx b/frontend/frontend/src/pages/groupsPage/GroupsPage.tsx index 7e392958..a0d53b84 100644 --- a/frontend/frontend/src/pages/groupsPage/GroupsPage.tsx +++ b/frontend/frontend/src/pages/groupsPage/GroupsPage.tsx @@ -1,6 +1,7 @@ import { Header } from '../../components/Header.tsx' -import { Button } from '../../components/CustomComponents.tsx' +import { Button, Card } from '../../components/CustomComponents.tsx' import { + Autocomplete, Box, CircularProgress, Grid, @@ -14,7 +15,6 @@ import { TableBody, TableCell, TableContainer, - TableHead, TableRow, Tooltip, Typography, @@ -30,6 +30,7 @@ import { Add } from '@mui/icons-material' import ClearIcon from '@mui/icons-material/Clear' import SaveIcon from '@mui/icons-material/Save' import WarningPopup from '../../components/WarningPopup.tsx' +import axios, { AxiosResponse } from 'axios' import { User } from '../subjectsPage/AddChangeSubjectPage.tsx' // group interface @@ -61,8 +62,7 @@ export function GroupsPage() { const [currentGroup, setCurrentGroup] = useState('') const [availableStudents, setAvailableStudents] = useState([]) const [projectName, setProjectName] = useState('') - const [user, setUser] = useState() - const [max_group_size, setMaxGroupSize] = useState(0) + // confirmation dialog state const [confirmOpen, setConfirmOpen] = useState(false) @@ -71,7 +71,6 @@ export function GroupsPage() { // state for correct loading of the page const [loading, setLoading] = useState(true) - const [userLoading, setUserLoading] = useState(true) // handle confirmation dialog const confirmSave = async () => { @@ -137,51 +136,72 @@ export function GroupsPage() { navigate('/course/' + courseId + '/assignment/' + assignmentId) } + // Change max group size + const handleGroupSizeChange = (newValue: number) => { + setNewGroupSize(newValue) + setAvailableStudents(() => Array.from(studentNames.keys())) + setFilteredStudents(availableStudents) + setCurrentGroup('0') + setNewGroups(() => { + const newGroups = [] + for ( + let i = 0; + i < Math.ceil(availableStudents.length / newValue); + i++ + ) { + newGroups.push({ + studenten: [], + project: parseInt(assignmentId), + }) + } + return newGroups + }) + async function fetchCourse() { + setLoading(true) + instance + .get('/vakken/' + courseId) + .then((response) => { + setAvailableStudents(response.data.studenten) + setFilteredStudents(availableStudents) + }) + .catch((error) => { + console.error(error) + }) + setLoading(false) + } + fetchCourse() + .catch((error) => { + console.error(error) + }) + .catch((error) => { + console.log(error) + }) + } + //get the current groups and group size from the backend useEffect(() => { async function fetchData() { - setUserLoading(true) setLoading(true) - const userResponse = await instance.get('/gebruikers/me/') - setUser(userResponse.data) - setUserLoading(false) // Get the student names await instance .get('/vakken/' + courseId) .then(async (response) => { + // This function fetches the names of the students in parallel const newStudentNames = new Map() - for (const student of response.data.studenten) { - await instance - .get('/gebruikers/' + student) - .then((response) => { - newStudentNames.set( - student, - response.data.first_name + - ' ' + - response.data.last_name - ) - }) - .catch((error) => { - console.log(error) - }) - } - for (const student of response.data.studenten) { - await instance - .get('/gebruikers/' + student) - .then((response) => { - newStudentNames.set( - student, - response.data.first_name + - ' ' + - response.data.last_name - ) - console.log( - 'available names:' + - Array.from(newStudentNames.entries()) - ) - }) - } + const studentPromises: Promise>[] = + response.data.studenten.map((id: number) => + instance.get('/gebruikers/' + id) + ) + const studentResponses = await axios.all(studentPromises) + + studentResponses.forEach((response) => { + const student: User = response.data + newStudentNames.set( + student.user, + student.first_name + ' ' + student.last_name + ) + }) setStudentNames(() => newStudentNames) }) @@ -221,11 +241,14 @@ export function GroupsPage() { setLoading(false) } - fetchData().catch((error) => { - console.error(error) - }) - setCurrentGroup('0') - }, [assignmentId, courseId, studentNames.size]) + fetchData() + .catch((error) => { + console.error(error) + }) + .catch((error) => { + console.log(error) + }) + }, [assignmentId, courseId]) useEffect(() => { setAvailableStudents(() => @@ -236,8 +259,38 @@ export function GroupsPage() { ) ) ) + setFilteredStudents(availableStudents) }, [newGroups, studentNames]) + // Create new groups when the group size changes + useEffect(() => { + if (newGroups.length === 0) { + setAvailableStudents(() => Array.from(studentNames.keys())) + setFilteredStudents(availableStudents) + setCurrentGroup('0') + setNewGroups(() => { + const newGroups = [] + for ( + let i = 0; + i < Math.ceil(availableStudents.length / newGroupSize); + i++ + ) { + newGroups.push({ + studenten: [], + project: parseInt(assignmentId), + }) + } + return newGroups + }) + } + }, [ + assignmentId, + availableStudents.length, + newGroupSize, + newGroups.length, + studentNames, + ]) + //Handle current group change const handleCurrentGroupChange = (event: SelectChangeEvent) => { setCurrentGroup(event.target.value as string) @@ -267,6 +320,7 @@ export function GroupsPage() { (student) => student !== studentId ) setAvailableStudents(updatedAvailableStudents) + setFilteredStudents(availableStudents) // Then, create a new copy of the newGroups array with the updated group const updatedNewGroups = newGroups.map((group, index) => { if (index === groupId) { @@ -307,420 +361,434 @@ export function GroupsPage() { setNewGroups(updatedNewGroups) } + const [filteredStudents, setFilteredStudents] = useState(availableStudents) + + // for filtering students + const handleAutocompleteChange = (_: unknown, value: number | null) => { + if (value) { + setFilteredStudents([value]) + } + } + + const resetAutocompleteChange = () => { + setFilteredStudents(availableStudents) + } + + const filterOptions = ( + _: unknown, + { inputValue }: { inputValue: string } + ) => { + return availableStudents.filter((option) => { + const label = studentNames.get(option) + return label?.toLowerCase().startsWith(inputValue.toLowerCase()) + }) + } return ( <> - {/* Rendering different UI based on user role */} - {userLoading ? ( - + + - - - - ) : ( - <> - {user?.is_lesgever ? ( - // Rendering UI for teacher - <> - + + - - + + + + + {t('amount')} {t('members')}/ + {t('group')} + + + + {loading ? ( + + ) : ( + { + if ( + parseInt( + newValue.target + .value + ) < 1 + ) + return + handleGroupSizeChange( + parseInt( + newValue.target + .value + ) + ) + }} + variant="outlined" + sx={{ width: 80 }} + /> + )} + + + + + {loading ? ( + + ) : ( + + {t('random')} {t('groups')} + + )} + - - + {t('students_choose')} + + {loading ? ( + - - {t('groups')} - - - - - - {t('amount')}{' '} - {t('members')}/ - {t('group')} - - - - - + ) : ( + + )} + + + + + *:not(:last-child)': { + marginRight: '40px', // Add right margin of 20 pixels + }, + }} + > + + + + - {loading ? ( - - ) : ( - + - setRandomOpen(true) - } - > - - {t('random')}{' '} - {t('groups')} - - - )} - - - {t('students_choose')} + {t('group')} + + {loading ? ( ) : ( - + + {newGroups.map( + (_, index) => ( + + {t( + 'group' + ) + + (index + + 1)} + + ) + )} + )} - - - - - + + + + + - - - - - - - + {loading ? ( + [...Array(3)].map( + (_, index) => ( + + ) + ) + ) : ( + <> + {newGroups[ + parseInt( + currentGroup + ) + ] && + newGroups[ + parseInt( + currentGroup + ) + ].studenten.map( + (student) => ( + - {t( - 'students' - )} - - - - - - {loading ? ( - [ - ...Array(3), - ].map( - ( - _, - index - ) => ( - - ) - ) - ) : ( - <> - {availableStudents.map( - ( - student - ) => ( - + {studentNames.get( + student + )} + { + removeStudent( + student, + parseInt( + currentGroup + ) + ) + }} > - - {studentNames.get( - student - ) === - '' - ? student - : studentNames.get( - student - )} - = - max_group_size - : true - } - onClick={() => { - assignStudent( - student, - parseInt( - currentGroup - ) - ) - }} - > - - - - - ) - )} - > + + + + + ) )} - - - - - + )} + + + + + + + + + + - - - - - {t( - 'group' - )} - - {loading ? ( - - ) : ( - <> - - {newGroups.map( - ( - _, - index - ) => ( - - {t( - 'group' - ) + - (index + - 1)} - - ) - )} - - > - )} - - - - - {loading ? ( - [ - ...Array(3), - ].map( - ( - _, - index - ) => ( - + {t('studenten')} + + + + + + {loading ? ( + + ) : ( + { + const name = + studentNames.get( + student + ) + return name != + null + ? name + : '' // This checks for both null and undefined + }} + onChange={ + handleAutocompleteChange + } + filterOptions={ + filterOptions + } + renderInput={( + student + ) => ( + + )} + /> + )} + + + + + + + + + {loading ? ( + [...Array(3)].map( + (_, index) => ( + + ) + ) + ) : ( + <> + + {chunkArray( + filteredStudents, + Math.ceil( + Math.sqrt( + filteredStudents.length ) ) - ) : ( - <> - {newGroups[ - parseInt( - currentGroup - ) - ] && - newGroups[ - parseInt( - currentGroup - ) - ].studenten.map( + ).map( + (students) => ( + + {students.map( ( student ) => ( @@ -744,139 +812,140 @@ export function GroupsPage() { > {studentNames.get( student - )} - = + newGroupSize + : true + } onClick={() => { - removeStudent( + assignStudent( + student, parseInt( currentGroup ) ) + resetAutocompleteChange() }} > - + + ) )} - > + + ) )} - - - - - - - - + > + )} + + + + + + + + + + + + + - - + + + + {loading ? ( + + ) : ( + <> + - - - - - - - {loading ? ( - - ) : ( - <> - - - - > - )} - - - - - - {/* Warning popup for when the user wants to confirm the group changes */} - - setRandomOpen(false)} - doAction={randomGroups} - /> - > - ) : ( - navigate( - '/course/' + - courseId + - '/assignment/' + - assignmentId + - '/groups/choose' - ) - )} - > - )} + + + > + )} + + + + + + {/* Warning popup for when the user wants to confirm the group changes */} + > ) } + +function chunkArray(arr: T[], chunkSize: number): T[][] { + const result: T[][] = [] + for (let i = 0; i < arr.length; i += chunkSize) { + const chunk: T[] = arr.slice(i, i + chunkSize) + result.push(chunk) + } + return result +} diff --git a/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx b/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx index 09ae0368..9f5d4723 100644 --- a/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx +++ b/frontend/frontend/src/pages/scoresPage/ProjectScoresPage.tsx @@ -1,5 +1,5 @@ import { Header } from '../../components/Header' -import { Button, Card } from '../../components/CustomComponents.tsx' +import { Card, SecondaryButton } from '../../components/CustomComponents.tsx' import { Box, CircularProgress, @@ -167,38 +167,41 @@ export function ProjectScoresPage() { groepen .filter((groep) => groep.lastSubmission !== undefined) .map((groep) => groep.lastSubmission) - .forEach(submission => { + .forEach((submission) => { downloadPromises.push( new Promise(async (resolve, reject) => { try { // Get the submission details const submissionResponse = await instance.get( `/indieningen/${submission?.indiening_id}/` - ); - const newSubmission = submissionResponse.data; + ) + const newSubmission = submissionResponse.data // Get the submission file const fileResponse = await instance.get( `/indieningen/${submission?.indiening_id}/indiening_bestand/`, { responseType: 'blob' } - ); - let filename = 'indiening.zip'; + ) + let filename = 'indiening.zip' if (newSubmission.bestand) { - filename = newSubmission.bestand.replace(/^.*[\\/]/, ''); + filename = newSubmission.bestand.replace( + /^.*[\\/]/, + '' + ) } const blob = new Blob([fileResponse.data], { type: fileResponse.headers['content-type'], - }); + }) const file = new File([blob], filename, { type: fileResponse.headers['content-type'], - }); - newSubmission.bestand = file; - newSubmission.filename = filename; + }) + newSubmission.bestand = file + newSubmission.filename = filename // Add the file to the zip - zip.file(filename, fileResponse.data); - resolve(newSubmission); + zip.file(filename, fileResponse.data) + resolve(newSubmission) } catch (err) { - console.error(`Error downloading submission:`, err); - reject(err); + console.error(`Error downloading submission:`, err) + reject(err) } }) ) @@ -210,7 +213,8 @@ export function ProjectScoresPage() { const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url - a.download = 'all_submissions_' + project?.titel +'_.zip' + a.download = + 'all_submissions_' + project?.titel + '_.zip' document.body.appendChild(a) a.click() a.remove() @@ -299,149 +303,177 @@ export function ProjectScoresPage() { <> {user?.is_lesgever ? ( // Rendering UI for teacher - <> - - - {/* Main content box */} - - - {/* Render StudentsView component if project is defined */} - {loading ? ( - - - - ) : ( <> - {project && ( - + - )} - > - )} - - {/* Footer section with action buttons */} - - - - - {t('export_submissions')} - + {/* Main content box */} - - {t('upload_scores')} - - - - - - {loading ? ( - - ) : ( - <> - setOpenSaveScoresPopup(true)} + - - - > - )} - setOpenDeleteScoresPopup(true)} - sx={{ - backgroundColor: 'secondary.main', - borderRadius: 2, - }} - > - - - - - {/* Popup for confirming saving scores */} - setOpenSaveScoresPopup(false)} - doAction={saveScores} - /> - {/* Popup for confirming deletion of scores */} - setOpenDeleteScoresPopup(false)} - doAction={deleteScores} - /> - + {/* Render StudentsView component if project is defined */} + {loading ? ( + + + + ) : ( + <> + {project && ( + + )} + > + )} + + {/* Footer section with action buttons */} + + + + + {t('export_submissions')} + + + + {t('upload_scores')} + + + + + + {loading ? ( + + ) : ( + <> + + setOpenSaveScoresPopup( + true + ) + } + sx={{ + color: 'background.default', + '&:hover': { + color: 'text.primary', + }, + backgroundColor: + 'primary.main', + borderRadius: 2, + }} + > + + + > + )} + + + setOpenDeleteScoresPopup(true) + } + sx={{ + backgroundColor: + 'secondary.main', + borderRadius: 2, + }} + > + + + + + {/* Popup for confirming saving scores */} + + setOpenSaveScoresPopup(false) + } + doAction={saveScores} + /> + {/* Popup for confirming deletion of scores */} + + setOpenDeleteScoresPopup(false) + } + doAction={deleteScores} + /> + + > + ) : ( + navigate('*') + )} + > + )} > - ):( - navigate('*') - )} -> -)} -> -)} + ) +} diff --git a/frontend/frontend/src/pages/scoresPage/StudentScoreListItem.tsx b/frontend/frontend/src/pages/scoresPage/StudentScoreListItem.tsx index 7805f909..92dcc299 100644 --- a/frontend/frontend/src/pages/scoresPage/StudentScoreListItem.tsx +++ b/frontend/frontend/src/pages/scoresPage/StudentScoreListItem.tsx @@ -1,4 +1,4 @@ -import { Divider } from '../../components/CustomComponents.tsx' +import { Divider, EvenlySpacedRow } from '../../components/CustomComponents.tsx' import { CircularProgress, ListItemIcon, @@ -65,22 +65,22 @@ export function StudentScoreListItem({ // Get the submission details const submissionResponse = await instance.get( `/indieningen/${lastSubmission?.indiening_id}/` - ); - const newSubmission = submissionResponse.data; + ) + const newSubmission = submissionResponse.data // Get the submission file const fileResponse = await instance.get( `/indieningen/${lastSubmission?.indiening_id}/indiening_bestand/`, { responseType: 'blob' } - ); + ) if (newSubmission.bestand) { - filename = newSubmission.bestand.replace(/^.*[\\/]/, ''); + filename = newSubmission.bestand.replace(/^.*[\\/]/, '') } const blob = new Blob([fileResponse.data], { type: fileResponse.headers['content-type'], - }); + }) const file = new File([blob], filename, { type: fileResponse.headers['content-type'], - }); + }) const url = window.URL.createObjectURL(file) const a = document.createElement('a') a.href = url @@ -95,84 +95,86 @@ export function StudentScoreListItem({ return ( <> - + {/* Inner list item for displaying submission details */} - + {/* Content section */} <> {loading ? ( ) : ( - , + , + + {lastSubmission ? ( + <> + + + changeScore( + parseInt( + event.target + .value + ) + ) + } + variant="standard" + size="small" + /> + + + > + ) : ( + + )} + , + + + + {lastSubmission ? ( + + ) : ( + + )} + + + , + ]} /> )} - - {/* Score section */} - - {lastSubmission ? ( - <> - - - changeScore( - parseInt(event.target.value) - ) - } - variant="outlined" - size="small" - /> - - - > - ) : ( - - )} - - {/* Display download icon */} - - - - {lastSubmission ? ( - - ) : ( - - )} - - - > diff --git a/frontend/frontend/src/pages/scoresPage/StudentsView.tsx b/frontend/frontend/src/pages/scoresPage/StudentsView.tsx index 75457d0d..c64c76fa 100644 --- a/frontend/frontend/src/pages/scoresPage/StudentsView.tsx +++ b/frontend/frontend/src/pages/scoresPage/StudentsView.tsx @@ -1,4 +1,4 @@ -import { Divider } from '../../components/CustomComponents.tsx' +import { Divider, EvenlySpacedRow } from '../../components/CustomComponents.tsx' import { Box, Skeleton, Typography } from '@mui/material' import List from '@mui/material/List' import { StudentScoreListItem } from './StudentScoreListItem.tsx' @@ -116,29 +116,27 @@ export function StudentsView({ - <> - - {t('group')} - - - {t('time')} - - - Score - - - Download - - > + + {t('group')} + , + + {t('time')} + , + + Score + , + + Download + , + ]} + /> {/* Scrollable list of students */} diff --git a/frontend/frontend/src/pages/subjectsPage/AddChangeSubjectPage.tsx b/frontend/frontend/src/pages/subjectsPage/AddChangeSubjectPage.tsx index 58bae7af..b86c6118 100644 --- a/frontend/frontend/src/pages/subjectsPage/AddChangeSubjectPage.tsx +++ b/frontend/frontend/src/pages/subjectsPage/AddChangeSubjectPage.tsx @@ -1,15 +1,20 @@ -import { Button, Card, Divider } from '../../components/CustomComponents.tsx' +import { + Divider, + Card, + SecondaryButton, + EvenlySpacedRow, +} from '../../components/CustomComponents.tsx' + import { Box, - CircularProgress, IconButton, ListItem, - ListItemButton, ListItemText, Skeleton, Stack, TextField, Typography, + CircularProgress, } from '@mui/material' import { Header } from '../../components/Header' import React, { ChangeEvent, useEffect, useState } from 'react' @@ -47,11 +52,7 @@ function UserList( :not(style)': { - marginBottom: '8px', - width: '99vw', - }, - minHeight: '20vh', + minHeight: '30vh', maxHeight: '30vh', overflowY: 'auto', }} @@ -79,66 +80,55 @@ function UserList( } return ( <> - - - - - - - - - - - - + + , + + + + + + , + ]} + /> + + > ) })} @@ -174,9 +164,9 @@ function UploadPart( /> - + {t('add')} - + {t('cancel')} + {t('delete')} @@ -583,7 +574,6 @@ export function AddChangeSubjectPage() { marginTop={11} sx={{ width: '100%', - height: '70%', backgroundColor: 'background.default', }} > @@ -622,7 +612,8 @@ export function AddChangeSubjectPage() { ) : ( setTitle( event.target.value @@ -633,106 +624,117 @@ export function AddChangeSubjectPage() { )} - {t('save')} - + - - - - {t('students')} - - - - {UserList( - loading, - students, - setSelectedStudent, - setOpenStudent - )} - - - - {UploadPart( - studentFile, - handleStudentFileChange, - setEmailStudent, - handleAddStudent, - t('upload_students') - )} - - - {DialogWindow( - handleCloseStudent, - openStudent, - handleRemoveStudent, - t('delete_student') - )} - - - - - {t('teachers')} - + + + + + + + {t('students')} + + + + + {UserList( + loading, + students, + setSelectedStudent, + setOpenStudent + )} + + + + {UploadPart( + studentFile, + handleStudentFileChange, + setEmailStudent, + handleAddStudent, + t('upload_students') + )} + + {DialogWindow( + handleCloseStudent, + openStudent, + handleRemoveStudent, + t('delete_student') + )} + - - {UserList( - loading, - teachers, - setSelectedTeacher, - setOpenTeacher - )} + + + + + + {t('teachers')} + + + + + {UserList( + loading, + teachers, + setSelectedTeacher, + setOpenTeacher + )} + + + + {UploadPart( + teacherFile, + handleTeacherFileChange, + setEmailTeacher, + handleAddTeacher, + t('upload_teachers') + )} + + {DialogWindow( + handleCloseTeacher, + openTeacher, + handleRemoveTeacher, + t('delete_teacher') + )} + - - - {UploadPart( - teacherFile, - handleTeacherFileChange, - setEmailTeacher, - handleAddTeacher, - t('upload_teachers') - )} - + - {DialogWindow( - handleCloseTeacher, - openTeacher, - handleRemoveTeacher, - t('delete_teacher') - )} > diff --git a/frontend/frontend/src/pages/subjectsPage/AssignmentListItemSubjectsPage.tsx b/frontend/frontend/src/pages/subjectsPage/AssignmentListItemSubjectsPage.tsx index 25ff8fd2..516eda48 100644 --- a/frontend/frontend/src/pages/subjectsPage/AssignmentListItemSubjectsPage.tsx +++ b/frontend/frontend/src/pages/subjectsPage/AssignmentListItemSubjectsPage.tsx @@ -13,6 +13,7 @@ import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined' import React, { useState } from 'react' import dayjs, { Dayjs } from 'dayjs' import { Score } from '../../components/SubmissionListItemTeacherPage.tsx' +import { EvenlySpacedRow } from '../../components/CustomComponents.tsx' /** * This component is used to display a single assignment in the list of assignments. @@ -73,91 +74,79 @@ export function AssignmentListItemSubjectsPage({ <> {isStudent ? ( - <> - - - 0 - ? submissions > 1 - ? submissions + - ' ' + - t('submissions') - : submissions + - ' ' + - t('submission') - : t('no_submissions') - } - /> - {submissions > 0 ? ( + , - ) : ( + />, - )} - > + primary={ + submissions > 0 + ? submissions > 1 + ? submissions + + ' ' + + t('submissions') + : submissions + + ' ' + + t('submission') + : t('no_submissions') + } + />, + <> + {submissions > 0 ? ( + + ) : ( + + )} + >, + ]} + /> ) : ( <> {/* In case of the user being the teacher: */} - - - , + , + , + ]} /> > )} diff --git a/frontend/frontend/src/pages/subjectsPage/ProjectsView.tsx b/frontend/frontend/src/pages/subjectsPage/ProjectsView.tsx index 62e4d46d..0e7a64e4 100644 --- a/frontend/frontend/src/pages/subjectsPage/ProjectsView.tsx +++ b/frontend/frontend/src/pages/subjectsPage/ProjectsView.tsx @@ -1,4 +1,8 @@ -import { Divider } from '../../components/CustomComponents.tsx' +import { + Card, + Divider, + EvenlySpacedRow, +} from '../../components/CustomComponents.tsx' import { Box, Skeleton, Typography } from '@mui/material' import List from '@mui/material/List' import { t } from 'i18next' @@ -150,65 +154,85 @@ export function ProjectsView({ return ( <> - - {!gebruiker.is_lesgever ? ( - <> - {/* Show the UI from the perspective of a student. */} - - Project - - - Deadline - - - {t('submissions')} - - - Score - - > - ) : ( - <> - {/* Show the UI from the perspective of a teacher. */} - - Project - - - Deadline - - - {t('edit')} - - > - )} - - - + + + {!gebruiker.is_lesgever ? ( + <> + {/* Show the UI from the perspective of a student. */} + + Project + , + + Deadline + , + + {t('submissions')} + , + + Score + , + ]} + /> + > + ) : ( + <> + {/* Show the UI from the perspective of a teacher. */} + + Project + , + + Deadline + , + + {t('edit')} + , + ]} + /> + > + )} + + {/* The list below will display the projects with their information */} - + {loading ? ( [...Array(3).keys()].map((index) => ( ( <> + - > ))} > @@ -304,7 +326,7 @@ export function ProjectsView({ - + > ) } diff --git a/frontend/frontend/src/pages/subjectsPage/StudentPopUp.tsx b/frontend/frontend/src/pages/subjectsPage/StudentPopUp.tsx index 7d5529bb..cc728f50 100644 --- a/frontend/frontend/src/pages/subjectsPage/StudentPopUp.tsx +++ b/frontend/frontend/src/pages/subjectsPage/StudentPopUp.tsx @@ -1,7 +1,13 @@ import { User } from './AddChangeSubjectPage' import * as React from 'react' -import { IconButton, List, ListItem, ListItemText, Typography } from '@mui/material' -import { Button } from '../../components/CustomComponents' +import { + IconButton, + List, + ListItem, + ListItemText, + Typography, +} from '@mui/material' +import { SecondaryButton } from '../../components/CustomComponents' import Dialog from '@mui/material/Dialog' import DialogTitle from '@mui/material/DialogTitle' import CloseIcon from '@mui/icons-material/Close' @@ -23,15 +29,13 @@ export default function StudentPopUp({ students, text }: StudentPopUpProps) { return ( <> {/* Students Button */} - { setOpen(true) }} > {t(text)} - + {/* Students Dialog */} {/* List of Students */} {students.length > 0 ? ( - - {students.map((student) => ( - - - - ))} - + + {students.map((student) => ( + + + + ))} + ) : ( - {t('loading') + ' ' + t('students') + '...'} + + {t('loading') + ' ' + t('students') + '...'} + )} diff --git a/frontend/frontend/src/pages/subjectsPage/SubjectsPage.tsx b/frontend/frontend/src/pages/subjectsPage/SubjectsPage.tsx index abd72c50..83f6e456 100644 --- a/frontend/frontend/src/pages/subjectsPage/SubjectsPage.tsx +++ b/frontend/frontend/src/pages/subjectsPage/SubjectsPage.tsx @@ -1,12 +1,5 @@ import { Header } from '../../components/Header' -import { - Box, - Card, - CircularProgress, - Grid, - IconButton, - Stack, -} from '@mui/material' +import { Box, CircularProgress, Grid, IconButton, Stack } from '@mui/material' import TabSwitcher from '../../components/TabSwitcher.tsx' import { ProjectsView } from './ProjectsView.tsx' import { useNavigate, useParams } from 'react-router-dom' @@ -320,6 +313,7 @@ export function SubjectsPage() { )} - + - + > )} diff --git a/frontend/frontend/src/pages/submissionPage/SubmissionPage.tsx b/frontend/frontend/src/pages/submissionPage/SubmissionPage.tsx index fe1c0297..d9f06161 100644 --- a/frontend/frontend/src/pages/submissionPage/SubmissionPage.tsx +++ b/frontend/frontend/src/pages/submissionPage/SubmissionPage.tsx @@ -134,6 +134,7 @@ export function SubmissionPage() { ) //Get the submission file const newSubmission: Submission = submissionResponse.data + if (newSubmission.result !== 'No tests: OK') { const regex = /Testing (.*):/g const matches = newSubmission.result.match(regex) @@ -509,7 +510,15 @@ export function SubmissionPage() { height={40} /> ) : ( - + {submission?.status === SubmissionStatus.PENDING ? t('pending') diff --git a/package-lock.json b/package-lock.json index de269d44..1b8a0d3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@mui/material": "^5.15.12", + "lodash": "^4.17.21", "react-datepicker": "^6.2.0" } }, @@ -768,6 +769,11 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", diff --git a/package.json b/package.json index dde0d41f..f4a11f7a 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@mui/material": "^5.15.12", + "lodash": "^4.17.21", "react-datepicker": "^6.2.0" } }