From 0cc9482878acac68057276cd92d6ac88f6072ea9 Mon Sep 17 00:00:00 2001 From: adam-skowronek Date: Fri, 4 Nov 2022 01:18:26 +0100 Subject: [PATCH] 3.6 Add schedules view for students, coordinator can now assign to committees --- backend/app/coordinator/routes/enrollments.py | 6 +- backend/app/examination_schedule/schemas.py | 1 + frontend/src/App.tsx | 6 + frontend/src/api/schedule.ts | 54 ++++++ frontend/src/views/Login.tsx | 2 +- .../src/views/coordinator/EditSchedule.tsx | 108 +++++++++++ frontend/src/views/coordinator/Schedule.tsx | 40 +++- frontend/src/views/coordinator/Schedules.tsx | 4 +- .../src/views/student/ScheduleAddGroup.tsx | 77 ++++++++ frontend/src/views/student/Student.tsx | 7 +- .../src/views/student/StudentSchedule.tsx | 175 ++++++++++++++++++ .../src/views/student/StudentSchedules.tsx | 24 +++ frontend/src/views/supervisor/Supervisor.tsx | 7 +- .../views/supervisor/SupervisorSchedules.tsx | 24 +++ 14 files changed, 524 insertions(+), 11 deletions(-) create mode 100644 frontend/src/views/coordinator/EditSchedule.tsx create mode 100644 frontend/src/views/student/ScheduleAddGroup.tsx create mode 100644 frontend/src/views/student/StudentSchedule.tsx create mode 100644 frontend/src/views/student/StudentSchedules.tsx create mode 100644 frontend/src/views/supervisor/SupervisorSchedules.tsx diff --git a/backend/app/coordinator/routes/enrollments.py b/backend/app/coordinator/routes/enrollments.py index 8429c77..b8a1878 100644 --- a/backend/app/coordinator/routes/enrollments.py +++ b/backend/app/coordinator/routes/enrollments.py @@ -46,9 +46,9 @@ def create_enrollments(examination_schedule_id: int, data: dict) -> dict: enrollments = [] for i in range(amount): sd = start_date + datetime.timedelta(minutes=i * prt) - ed = start_date + datetime.timedelta(minutes=(i + 1) * prt) - enrollment = Enrollment(start_date=sd, end_date=ed, examination_schedule_id=examination_schedule_id) - enrollments.append(enrollment) + ed = start_date + datetime.timedelta(minutes=(i + 1) * prt) + enrollment = Enrollment(start_date=sd, end_date=ed, examination_schedule_id=examination_schedule_id) + enrollments.append(enrollment) db.session.add_all(enrollments) db.session.commit() diff --git a/backend/app/examination_schedule/schemas.py b/backend/app/examination_schedule/schemas.py index ad69647..96a9dd9 100644 --- a/backend/app/examination_schedule/schemas.py +++ b/backend/app/examination_schedule/schemas.py @@ -15,6 +15,7 @@ class GroupSchema(Schema): class EnrollmentSchema(Schema): + id = fields.Integer() start_date = fields.DateTime() end_date = fields.DateTime() committee = fields.Nested(CommitteeSchema) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 671091d..ba2683d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -16,6 +16,9 @@ import Student from './views/student/Student' import Supervisor from './views/supervisor/Supervisor' import Schedules from './views/coordinator/Schedules' import Schedule from './views/coordinator/Schedule' +import SupervisorSchedules from './views/supervisor/SupervisorSchedules' +import StudentSchedules from './views/student/StudentSchedules' +import StudentSchedule from './views/student/StudentSchedule' const queryClient = new QueryClient({ defaultOptions: { @@ -45,10 +48,13 @@ function App() { }> } /> } /> + } /> + } /> }> } /> } /> + } /> diff --git a/frontend/src/api/schedule.ts b/frontend/src/api/schedule.ts index bea7547..00845ab 100644 --- a/frontend/src/api/schedule.ts +++ b/frontend/src/api/schedule.ts @@ -7,12 +7,32 @@ export const getEvents = (scheduleId: number) => { start_date: string end_date: string title: string + committee: { + members: { first_name: string; last_name: string }[] + } + group: { name: string } }[] }>( `http://127.0.0.1:5000/api/examination_schedule/enrollments/${scheduleId}/coordinator-view/?per_page=10000`, ) } +export const getStudentsSchedule = (scheduleId: number) => { + return axiosInstance.get<{ + enrollments: { + id: number + start_date: string + end_date: string + title: string + committee: { + members: { first_name: string; last_name: string }[] + } + group: { name: string } + }[] + }>( + `http://127.0.0.1:5000/api/examination_schedule/enrollments/${scheduleId}/student-view?per_page=10000`, + ) +} export const getSchedules = () => { return axiosInstance.get<{ examination_schedules: { @@ -72,3 +92,37 @@ export const setEventDate = ({ }, ) } + +export const assignGroup = ({ + scheduleId, + enrollmentId, + studentIndex, +}: { + scheduleId: number + enrollmentId: number + studentIndex: number +}) => { + return axiosInstance.post( + `http://127.0.0.1:5000/api/students/${scheduleId}/enrollments/${enrollmentId}/`, + { + student_index: studentIndex, + }, + ) +} + +export const assignSupervisor = ({ + scheduleId, + enrollmentId, + supervisorId, +}: { + scheduleId: number + enrollmentId: number + supervisorId: number +}) => { + return axiosInstance.post( + `http://127.0.0.1:5000/api/project_supervisor/${scheduleId}/enrollments/${enrollmentId}/`, + { + project_supervisor_id: supervisorId, + }, + ) +} diff --git a/frontend/src/views/Login.tsx b/frontend/src/views/Login.tsx index f126412..8bb6124 100644 --- a/frontend/src/views/Login.tsx +++ b/frontend/src/views/Login.tsx @@ -23,7 +23,7 @@ const Login = () => {
Koordynator Student - Opiekun + Opiekun
diff --git a/frontend/src/views/coordinator/EditSchedule.tsx b/frontend/src/views/coordinator/EditSchedule.tsx new file mode 100644 index 0000000..99ac15e --- /dev/null +++ b/frontend/src/views/coordinator/EditSchedule.tsx @@ -0,0 +1,108 @@ +import { useState } from 'react' +import { Controller, NestedValue, useForm } from 'react-hook-form' +import { useMutation, useQuery } from 'react-query' +import Select from 'react-select' +import { getLeaders } from '../../api/leaders' +import { assignSupervisor } from '../../api/schedule' + +type SelectValue = { + value: string | number + label: string +} + +const EditSchedule = ({ + eventData, + scheduleId, +}: { + eventData: { + start: Date + end: Date + title: string + id: number + resource: any + } + scheduleId: string +}) => { + const { register, handleSubmit, reset, control } = useForm<{ + committee: NestedValue + }>({ mode: 'onBlur' }) + const [committeeOptions, setCommitteeOptions] = useState([]) + + const { isLoading: areLeadersLoading } = useQuery( + 'leaders', + () => getLeaders({ per_page: 1000 }), + { + onSuccess: (data) => { + setCommitteeOptions( + data?.data.project_supervisors + .filter((ld) => ld.count_groups < ld.limit_group) + .map(({ id, first_name, last_name }) => ({ + value: id, + label: `${first_name} ${last_name}`, + })), + ) + }, + }, + ) + + const { mutate: mutateAssignSupervisor } = useMutation( + ['assignSupervisor'], + (data: { + scheduleId: number + enrollmentId: number + supervisorId: number + }) => assignSupervisor(data), + ) + + const onSubmit = (data: any) => { + data?.committee?.forEach((id: number) => { + mutateAssignSupervisor({ + scheduleId: Number(scheduleId), + enrollmentId: eventData.id, + supervisorId: id, + }) + }) + } + + return ( +
+
+

Termin

+
+
+ + ( + +
+ + +
+ ) +} + +export default ScheduleAddGroup diff --git a/frontend/src/views/student/Student.tsx b/frontend/src/views/student/Student.tsx index ebbcf38..c5ff6a5 100644 --- a/frontend/src/views/student/Student.tsx +++ b/frontend/src/views/student/Student.tsx @@ -4,7 +4,12 @@ import TopBar from '../../components/TopBar' const Student = () => { return ( <> - +
diff --git a/frontend/src/views/student/StudentSchedule.tsx b/frontend/src/views/student/StudentSchedule.tsx new file mode 100644 index 0000000..490f8f6 --- /dev/null +++ b/frontend/src/views/student/StudentSchedule.tsx @@ -0,0 +1,175 @@ +import { Calendar, luxonLocalizer, Views } from 'react-big-calendar' +import { DateTime, Settings } from 'luxon' +import { useCallback, useState } from 'react' +import { useQuery } from 'react-query' +import { getStudentsSchedule } from '../../api/schedule' +import { useParams } from 'react-router-dom' +import Modal from 'react-modal' +import { useForm } from 'react-hook-form' +import ScheduleAddGroup from './ScheduleAddGroup' + +const customStyles = { + content: { + top: '50%', + left: '50%', + right: 'auto', + bottom: 'auto', + marginRight: '-50%', + transform: 'translate(-50%, -50%)', + }, +} + +const StudentSchedule = () => { + Settings.defaultZone = DateTime.local().zoneName + Settings.defaultLocale = 'pl' + + const { id } = useParams<{ id: string }>() + const [events, setEvents] = useState< + { + id: number + title: string + start: Date + end: Date + resource: any + }[] + >([]) + const [selectedDate, setSelectedDate] = useState<{ + start: Date + end: Date + title: string + id: number + resource: any + }>() + const [view, setView] = useState(Views.WEEK) + const onView = useCallback((newView: any) => setView(newView), [setView]) + + const [isModalOpen, setIsModalOpen] = useState(false) + const [isEditModalOpen, setIsEditModalOpen] = useState(false) + Modal.setAppElement('#root') + + const { register, handleSubmit, reset } = useForm<{ + from: string + to: string + }>({ mode: 'onBlur' }) + + const { refetch } = useQuery( + ['studentSchedules'], + () => getStudentsSchedule(Number(id)), + { + onSuccess: (data) => { + setEvents( + data.data.enrollments.map( + ({ + id, + start_date, + end_date, + title = 'Obrona', + committee, + group, + }) => { + return { + id, + title: `Obrona ${group?.name ?? ''}`, + start: new Date(start_date), + end: new Date(end_date), + resource: { + committee, + }, + } + }, + ), + ) + }, + }, + ) + + const handleSelectSlot = async (event: any) => { + setSelectedDate(event) + } + + const handleSelectEvent = useCallback( + (event: { + id: number + title: string + start: Date + end: Date + resource: any + }) => { + console.log(event) + setSelectedDate(event) + setIsEditModalOpen(true) + }, + [], + ) + + function closeModal() { + setIsModalOpen(false) + } + const onSubmit = async (data: any) => {} + + return ( +
+

+ Wybierz i zatwierdź termin obrony dla swojej grupy +

+ + + +
+

Dostępne godziny

+
+ + + + +
+ +
+
+ setIsEditModalOpen(false)} + contentLabel="modal" + style={customStyles} + > + {selectedDate && id ? ( + + ) : null} + +
+ ) +} + +export default StudentSchedule diff --git a/frontend/src/views/student/StudentSchedules.tsx b/frontend/src/views/student/StudentSchedules.tsx new file mode 100644 index 0000000..3c5f262 --- /dev/null +++ b/frontend/src/views/student/StudentSchedules.tsx @@ -0,0 +1,24 @@ +import { useQuery } from 'react-query' +import { getSchedules } from '../../api/schedule' +import { Link } from 'react-router-dom' + +const StudentSchedules = () => { + const { data: schedules } = useQuery(['getSchedules'], () => getSchedules()) + + return ( +
+

Wybierz zapisy:

+ {schedules && + schedules?.data?.examination_schedules.map((schedule) => ( +

+ -{' '} + + {schedule.title} + +

+ ))} +
+ ) +} + +export default StudentSchedules diff --git a/frontend/src/views/supervisor/Supervisor.tsx b/frontend/src/views/supervisor/Supervisor.tsx index be1b8a9..6540b2b 100644 --- a/frontend/src/views/supervisor/Supervisor.tsx +++ b/frontend/src/views/supervisor/Supervisor.tsx @@ -4,7 +4,12 @@ import TopBar from '../../components/TopBar' const Supervisor = () => { return ( <> - +
diff --git a/frontend/src/views/supervisor/SupervisorSchedules.tsx b/frontend/src/views/supervisor/SupervisorSchedules.tsx new file mode 100644 index 0000000..79fdb90 --- /dev/null +++ b/frontend/src/views/supervisor/SupervisorSchedules.tsx @@ -0,0 +1,24 @@ +import { useQuery } from 'react-query' +import { getSchedules } from '../../api/schedule' +import { Link } from 'react-router-dom' + +const SupervisorSchedules = () => { + const { data: schedules } = useQuery(['getSchedules'], () => getSchedules()) + + return ( +
+

Wybierz zapisy:

+ {schedules && + schedules?.data?.examination_schedules.map((schedule) => ( +

+ -{' '} + + {schedule.title} + +

+ ))} +
+ ) +} + +export default SupervisorSchedules