From 292ef17be62f67c2b86447f21661b264d6c22da8 Mon Sep 17 00:00:00 2001 From: adam-skowronek Date: Fri, 16 Dec 2022 01:01:07 +0100 Subject: [PATCH] Fix export/import of students, add grade card view --- frontend/src/App.tsx | 4 + frontend/src/api/grades.ts | 33 + frontend/src/api/groups.ts | 3 + frontend/src/api/students.ts | 20 +- frontend/src/views/GradeCard.module.css | 13 + frontend/src/views/GradeCard.tsx | 721 ++++++++++++++++++++ frontend/src/views/coordinator/Group.tsx | 24 + frontend/src/views/coordinator/Groups.tsx | 10 +- frontend/src/views/coordinator/Students.tsx | 4 +- 9 files changed, 823 insertions(+), 9 deletions(-) create mode 100644 frontend/src/api/grades.ts create mode 100644 frontend/src/views/GradeCard.module.css create mode 100644 frontend/src/views/GradeCard.tsx create mode 100644 frontend/src/views/coordinator/Group.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 79d8023..455b18d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -23,6 +23,8 @@ import SupervisorSchedule from './views/supervisor/SupervisorSchedule' import Home from './views/coordinator/Home' import SupervisorAvailabilities from './views/coordinator/SupervisorAvailabilities' import AvailabilitySchedule from './views/coordinator/AvailabilitySchedule' +import GradeCard from './views/GradeCard' +import Group from './views/coordinator/Group' const queryClient = new QueryClient({ defaultOptions: { @@ -41,6 +43,7 @@ function App() { }> } /> } /> + } /> } /> } /> } /> @@ -52,6 +55,7 @@ function App() { path="supervisors_availability" element={} /> + } /> } diff --git a/frontend/src/api/grades.ts b/frontend/src/api/grades.ts new file mode 100644 index 0000000..085d51a --- /dev/null +++ b/frontend/src/api/grades.ts @@ -0,0 +1,33 @@ +import axiosInstance from './axiosInstance' + +export const updateGradesFirstTerm = ( + groupId: number, + supervisorId: number, + payload: any, +) => { + return axiosInstance.patch( + `project_supervisor/project-grade-sheet/group/${groupId}/first-term/?id=${supervisorId}`, + payload, + ) +} + +export const updateGradesSecondTerm = ( + groupId: number, + supervisorId: number, + payload: any, +) => { + return axiosInstance.patch( + `project_supervisor/project-grade-sheet/group/${groupId}/second-term/?id=${supervisorId}`, + payload, + ) +} + +export const getGradesForSupervisor = ( + groupId: number, + term: number, + supervisorId: number, +) => { + return axiosInstance.get<{ [key: string]: any }>( + `project_supervisor/project-grade-sheet/group/${groupId}/?term=${term}&id=${supervisorId}`, + ) +} diff --git a/frontend/src/api/groups.ts b/frontend/src/api/groups.ts index b2f15a3..7f49486 100644 --- a/frontend/src/api/groups.ts +++ b/frontend/src/api/groups.ts @@ -38,3 +38,6 @@ export const createGroup = (year_group_id: number, payload: CreateGroup) => export const deleteGroup = (id: number) => axiosInstance.delete(`coordinator/groups/${id}/`) + +export const getGroup = (id: number) => + axiosInstance.get(`coordinator/groups/${id}/detail/`) diff --git a/frontend/src/api/students.ts b/frontend/src/api/students.ts index 2de2b3a..e181a09 100644 --- a/frontend/src/api/students.ts +++ b/frontend/src/api/students.ts @@ -35,13 +35,21 @@ export const getStudents = ( export const createStudent = (payload: Student & { year_group_id: number }) => axiosInstance.post('coordinator/students/', payload) -export const uploadStudents = (payload: FormData) => - axiosInstance.post('coordinator/students/upload/', payload) +export const uploadStudents = (payload: FormData, year_group_id: number) => + axiosInstance.post( + `coordinator/students/upload/?id=${year_group_id}`, + payload, + ) export const deleteStudent = (index: number) => axiosInstance.delete(`coordinator/students/${index}/`) -export const downloadStudents = (mode: boolean) => - axiosInstance.post(`coordinator/students/download/?mode=${Number(mode)}`, { - responseType: 'blob', - }) +export const downloadStudents = (mode: boolean, year_group_id: number) => + axiosInstance.post( + `coordinator/students/download/?mode=${Number( + mode, + )}&year_group_id=${year_group_id}`, + { + responseType: 'blob', + }, + ) diff --git a/frontend/src/views/GradeCard.module.css b/frontend/src/views/GradeCard.module.css new file mode 100644 index 0000000..1b47403 --- /dev/null +++ b/frontend/src/views/GradeCard.module.css @@ -0,0 +1,13 @@ +.grade-table td, +.grade-table th { + border: 1px solid lightgray; +} +.grade-table th { + background: #f2f2f2; +} +.grade-table input { + background: #f2f2f2; + padding: 1rem; + text-align: center; + width: 100px; +} diff --git a/frontend/src/views/GradeCard.tsx b/frontend/src/views/GradeCard.tsx new file mode 100644 index 0000000..7f19e8f --- /dev/null +++ b/frontend/src/views/GradeCard.tsx @@ -0,0 +1,721 @@ +import { useForm } from 'react-hook-form' +import { useMutation, useQuery } from 'react-query' +import { useParams } from 'react-router-dom' +import useLocalStorageState from 'use-local-storage-state' +import { + getGradesForSupervisor, + updateGradesFirstTerm, + updateGradesSecondTerm, +} from '../api/grades' +import styles from './GradeCard.module.css' // Import css modules stylesheet as styles + +const GradeCard = () => { + const { register, handleSubmit, setValue } = useForm() + const { id } = useParams<{ id: string }>() + const [supervisorId] = useLocalStorageState('supervisorId') + + useQuery( + ['getGradesFirst'], + () => getGradesForSupervisor(Number(id), 1, Number(supervisorId)), + { + onSuccess: (data) => { + for (const [key, value] of Object.entries(data.data)) { + if (key !== 'id') setValue(key, value) + } + }, + }, + ) + + useQuery( + ['getGradesSecond'], + () => getGradesForSupervisor(Number(id), 2, Number(supervisorId)), + { + onSuccess: (data) => { + for (const [key, value] of Object.entries(data.data)) { + if (key !== 'id') setValue(key, value) + } + }, + }, + ) + + const { mutate: mutateGradesFirstTerm } = useMutation( + 'update_grades_first', + (payload: any) => updateGradesFirstTerm(1, 1, payload), + ) + + const { mutate: mutateGradesSecondTerm } = useMutation( + 'update_grades_second', + (payload: any) => updateGradesSecondTerm(1, 1, payload), + ) + + const onSubmit = (data: any) => { + let firstTermGrades: { [key: string]: any } = {} + let secondTermGrades: { [key: string]: any } = {} + for (const [key, value] of Object.entries(data)) { + if (key.endsWith('_1')) { + firstTermGrades[key] = Number(value) + } else { + secondTermGrades[key] = Number(value) + } + } + mutateGradesFirstTerm(firstTermGrades) + mutateGradesSecondTerm(secondTermGrades) + } + + return ( +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KryteriumWaga I semestrWaga II semestrPunkty I semestr [0,1,3,4]Punkty II semestr [0,1,3,4]Udział IUdział II
+

Prezentacja

+
1,51,5    
+ Czy prezentacja zawierała wszystkie wymagane treści (cel i + założenia projektowe, podział prac na semestry, przegląd + zrealizowanych funkcjonalności, architektura i użyte + technologie, role osób w zespole, demo systemu)? * (oceniane + podczas obrony semestralnej) + 31 + + + + 3,5%1,2%
+ Czy prezentacja (omówienie projektu i jego demonstracja) była + przeprowadzona zgodnie ze sztuką? (oceniane podczas obrony + semestralnej) + 44 + + + + 4,6%4,6%
+ Demonstracja systemu * (oceniane podczas obrony semestralnej) + 46 + + + + 4,6%6,9%
+ Odpowiedzi na pytania komisji (oceniane podczas obrony + semestralnej) + 22 + + + + 2,3%2,3%
+ {' '} +

Dokumentacja

+
21  
Dokument wizji projektu *30 + + + + 7,5%0,0%
Dokument wymagań projektowych *32 + + + + 7,5%2,9%
Dokumentacja dla klienta/grupy docelowej/użytkownika02 + + + + 0,0%2,9%
Dokumentacja deweloperska12 + + + + 2,5%2,9%
Licencja i podział praw własności11 + + + + 2,5%1,4%
Praca grupy w semestrze33    
Systematyczność pracy w semestrze *55 + + + + 7,9%6,5%
Podział prac w semestrze * (obowiązkowe w II sem)33 + + + + 4,7%3,9%
+ Kontakt z klientem/grupą docelową, testy w grupie docelowej + 44 + + + + 6,3%5,2%
Zarządzanie ryzykiem i zakresem projektu23 + + + + 3,2%3,9%
Metodyka pracy i narzędzia ją wspierające *33 + + + + 4,7%3,9%
Zarządzanie kodem źródłowym *22 + + + + 3,2%2,6%
DevOps03 + + + + 0,0%3,9%
Produkty projektu3,54,5    
+ Czy złożoność produktu projektu odpowiada wielkości zespołu? Czy + jakość produktu odpowiada wielkości projektu * + 35 + + + + 4,8%6,6%
+ Dostęp do produktu projektu dla komisji do testów podczas + prezentacji * (obowiązkowe w II sem) (oceniane podczas obrony + semestralnej) + 11 + + + + 1,6%1,3%
+ Brak krytycznych błędów w tym: bezpieczeństwa oraz + uniemożliwiających korzystanie z systemu * (oceniane podczas + obrony semestralnej) + 02 + + + + 0,0%2,6%
+ Dostęp do produktu projektu dla komisji do testów tydzień przed + prezentacją (oceniane podczas obrony semestralnej) + 05 + + + + 0,0%6,6%
Spełnienie kryteriów akceptacji *55 + + + + 8,0%6,6%
+ Czy wdrożone zostały wszystkie zakładane na dany semestr + funkcjonalności (ocenia prowadzący)? + 22 + + + + 3,2%2,6%
Czy projekt dobrze rokuje?50 + + + + 8,0%0,0%
Czy projekt został wdrożony?03 + + + + 0,0%4,0%
+ Czy projekt jest użyteczny dla grupy docelowej; Czy spełnia + kryteria użyteczności i został przetestowany pod tym kątem? + 35 + + + + 4,8%6,6%
Czy przygotowano prototyp projektu zgodnie ze sztuką? *20 + + + + 3,2%0,0%
+ Czy przeprowadzono i udokumentowano testy, w tym testy: + jednostkowe, integracyjne, akceptacyjne, obciążeniowe, + funkcjonalne (kryterium nie obejmuje testów użyteczności) oraz + statyczna analiza jakości kodu + 04 + + + + 0,0%5,3%
+ Dobór techologii i architektury systemu do rozwiązywanego + problemu + 12 + + + + 1,6%2,6%
+
+ +
+
+
+ ) +} + +export default GradeCard diff --git a/frontend/src/views/coordinator/Group.tsx b/frontend/src/views/coordinator/Group.tsx new file mode 100644 index 0000000..6a5e45b --- /dev/null +++ b/frontend/src/views/coordinator/Group.tsx @@ -0,0 +1,24 @@ +import { useQuery } from 'react-query' +import { Link, useParams } from 'react-router-dom' +import { getGroup } from '../../api/groups' + +const Group = () => { + const { id } = useParams<{ id: string }>() + + const { data: groups } = useQuery(['getGroup'], () => getGroup(Number(id))) + const { name, project_supervisor } = groups?.data || {} + + return ( +
+

{name}

+

+ Opiekun: {project_supervisor?.first_name}{' '} + {project_supervisor?.last_name} +

+ + + +
+ ) +} +export default Group diff --git a/frontend/src/views/coordinator/Groups.tsx b/frontend/src/views/coordinator/Groups.tsx index cdb5e79..424a3b7 100644 --- a/frontend/src/views/coordinator/Groups.tsx +++ b/frontend/src/views/coordinator/Groups.tsx @@ -5,6 +5,7 @@ import { useNavigate } from 'react-router-dom' import useLocalStorageState from 'use-local-storage-state' import { deleteGroup, getGroups } from '../../api/groups' import { ReactComponent as IconRemove } from '../../assets/svg/icon-remove.svg' +import { Link } from 'react-router-dom' const Groups = () => { let navigate = useNavigate() @@ -94,7 +95,14 @@ const Groups = () => { points_for_second_term, }) => ( - {name} + + + {name} + + {`${project_supervisor.first_name} ${project_supervisor.last_name}`} diff --git a/frontend/src/views/coordinator/Students.tsx b/frontend/src/views/coordinator/Students.tsx index 439fdb9..1a9944e 100644 --- a/frontend/src/views/coordinator/Students.tsx +++ b/frontend/src/views/coordinator/Students.tsx @@ -53,7 +53,7 @@ const Students = () => { const { mutate: mutateDownload } = useMutation( 'downloadStudents', - (mode: boolean) => downloadStudents(mode), + (mode: boolean) => downloadStudents(mode, Number(yearGroupId)), { onSuccess: (res) => { const url = window.URL.createObjectURL(new Blob([res.data])) @@ -68,7 +68,7 @@ const Students = () => { const { mutate: mutateUpload } = useMutation( 'uploadStudents', - (payload: FormData) => uploadStudents(payload), + (payload: FormData) => uploadStudents(payload, Number(yearGroupId)), { onSuccess: () => refetchStudents(), },