Update studentId, update opening enrollments

This commit is contained in:
adam-skowronek 2023-01-16 17:23:45 +01:00
parent 5fda4127cd
commit 5aa8bfee45
17 changed files with 196 additions and 145 deletions

View File

@ -14,7 +14,7 @@ class GroupSchema(ma.SQLAlchemyAutoSchema):
class StudentSchema(ma.SQLAlchemyAutoSchema):
group = fields.Nested(GroupSchema)
groups = fields.List(fields.Nested(GroupSchema))
class Meta:
model = Student

View File

@ -14,6 +14,7 @@ export interface Leader {
email: string
limit_group?: number
count_groups: number
year_group_id: number
}
export const getLeaders = (
@ -31,8 +32,14 @@ export const getLeaders = (
{ params },
)
export const createLeader = (payload: Partial<Leader>) =>
axiosInstance.post('coordinator/project_supervisor/', payload)
export const createLeader = (payload: Partial<Leader>) => {
const p = { ...payload }
delete p.year_group_id
return axiosInstance.post(
`coordinator/project_supervisor/${payload.year_group_id}/`,
p,
)
}
export const deleteLeader = (id: number) =>
axiosInstance.delete(`coordinator/project_supervisor/${id}/`)

View File

@ -12,6 +12,7 @@ interface TermOfDefences {
last_name: string
}[]
group: { name: string; students: Student[] }
chairman_of_committee: number
}
export const getTermsOfDefences = (scheduleId: number) => {
@ -29,12 +30,12 @@ export const getTermsOfDefencesWithGroups = (scheduleId: number) => {
export const getStudentsTermsOfDefences = (
scheduleId: number,
studentIndex: number,
studentId: number,
) => {
return axiosInstance.get<{
term_of_defences: TermOfDefences[]
}>(
`students/examination-schedule/${scheduleId}/enrollments?student_index=${studentIndex}`,
`students/examination-schedule/${scheduleId}/enrollments?student_id=${studentId}`,
)
}
@ -78,11 +79,13 @@ export const createEvent = ({
end_date,
scheduleId,
project_supervisors,
chairman_of_committee,
}: {
start_date: string
end_date: string
scheduleId: number
project_supervisors: number[]
chairman_of_committee: number
}) => {
return axiosInstance.post(
`coordinator/enrollments/${scheduleId}/add-term-of-defences/`,
@ -90,6 +93,7 @@ export const createEvent = ({
start_date,
end_date,
project_supervisors,
chairman_of_committee,
},
)
}
@ -122,16 +126,16 @@ export const setEventDate = ({
export const assignGroup = ({
scheduleId,
enrollmentId,
studentIndex,
studentId,
}: {
scheduleId: number
enrollmentId: number
studentIndex: number
studentId: number
}) => {
return axiosInstance.post(
`students/${scheduleId}/enrollments/${enrollmentId}/`,
{
student_index: studentIndex,
student_id: studentId,
},
)
}
@ -192,6 +196,18 @@ export const setDateOfExaminationSchedule = (
)
}
export const openEnrollments = (scheduleId: number) => {
return axiosInstance.put(
`coordinator/examination_schedule/${scheduleId}/open-enrollments/`,
)
}
export const closeEnrollments = (scheduleId: number) => {
return axiosInstance.put(
`coordinator/examination_schedule/${scheduleId}/close-enrollments/`,
)
}
export const generateTermsOfDefence = (scheduleId: number) => {
return axiosInstance.post(`coordinator/enrollments/${scheduleId}/generate`)
}

View File

@ -8,6 +8,7 @@ interface StudentResponse {
}
export interface Student {
id: number
first_name: string
last_name: string
index: number
@ -37,12 +38,12 @@ export const createStudent = (payload: Student & { year_group_id: number }) =>
export const uploadStudents = (payload: FormData, year_group_id: number) =>
axiosInstance.post(
`coordinator/students/upload/?id=${year_group_id}`,
`coordinator/students/upload/?year_group_id=${year_group_id}`,
payload,
)
export const deleteStudent = (index: number) =>
axiosInstance.delete(`coordinator/students/${index}/`)
export const deleteStudent = (id: number) =>
axiosInstance.delete(`coordinator/students/${id}/`)
export const downloadStudents = (mode: boolean, year_group_id: number) =>
axiosInstance.post(

View File

@ -22,8 +22,6 @@ const Login = () => {
const [userId, setUserId] = useLocalStorageState('userId', {
defaultValue: 0,
})
const [studentId, setStudentId] = useLocalStorageState('studentId')
const [userType, setUserType] = useLocalStorageState('userType', {
defaultValue: 'coordinator',
})
@ -54,9 +52,9 @@ const Login = () => {
{
onSuccess: (data) => {
setStudentOptions(
data?.data.students.map(({ first_name, last_name, index }) => {
data?.data.students.map(({ first_name, last_name, index, id }) => {
return {
value: index,
value: id,
label: `${first_name} ${last_name} (${index})`,
}
}),
@ -87,8 +85,7 @@ const Login = () => {
)
const onStudentChange = (v: any) => {
setStudentId(v.value)
// setUserId(v.value)
setUserId(v.value)
setUserType('student')
}

View File

@ -34,9 +34,9 @@ const AddGroup = () => {
setStudentOptions(
data?.data.students
.filter(({ groups }) => !groups?.length)
.map(({ first_name, last_name, index }) => {
.map(({ first_name, last_name, index, id }) => {
return {
value: index,
value: id,
label: `${first_name} ${last_name} (${index})`,
}
}),

View File

@ -19,31 +19,21 @@ const AddLeader = () => {
const { mutate: mutateCreateLeader } = useMutation(
'createLeader',
(payload: Leader) => {
delete payload.limit_group
return createLeader(payload)
},
{
onSuccess: (data) => {
setIsAlertVisible(true)
mutateAddToYearGroup({
id: data?.data?.id,
year_group_id: Number(yearGroupId),
limit_group: getValues('limit_group') ?? 0,
})
reset()
},
},
)
const { mutate: mutateAddToYearGroup } = useMutation(
'addLeaderToGroup',
(payload: { id: any; year_group_id: number; limit_group: number }) =>
addLeaderToGroup(payload.id, payload.year_group_id, {
limit_group: payload.limit_group,
}),
)
const onSubmit = (data: Leader) => {
mutateCreateLeader(data)
mutateCreateLeader({
...data,
year_group_id: Number(yearGroupId),
})
}
return (
@ -110,10 +100,13 @@ const AddLeader = () => {
id="limit_group"
type="text"
{...register('limit_group', {
required: false,
required: true,
pattern: /^[1-9][0-9]*$/,
})}
/>
{errors.limit_group?.type === 'required' && (
<InputError>Limit jest wymagany</InputError>
)}
{errors.limit_group?.type === 'pattern' && (
<InputError>Limit grup musi być liczbą dodatnią</InputError>
)}

View File

@ -54,12 +54,12 @@ const SupervisorSchedule = () => {
return (
<div className="flex flex-col">
<button
{/* <button
className="btn btn-success btn-xs md:btn-md self-end mb-4"
onClick={() => mutateGenerate()}
>
Generuj harmonogram
</button>
</button> */}
<Calendar
localizer={localizer}
startAccessor="start"

View File

@ -44,7 +44,7 @@ const Groups = () => {
const { mutate: mutateDelete } = useMutation(
'deleteGroup',
(index: number) => deleteGroup(index),
(id: number) => deleteGroup(id),
{
onSuccess: () => refetchGroups(),
},

View File

@ -67,6 +67,10 @@ const EditSchedule = ({
})
}
const chairman = eventData.resource?.members_of_committee?.find(
(m: any) => Number(m.id) === eventData.resource?.chairman_of_committee,
)
return (
<div style={{ width: '330px', height: '250px' }}>
<form className="w-full flex flex-col " onSubmit={handleSubmit(onSubmit)}>
@ -75,6 +79,9 @@ const EditSchedule = ({
{dayjs(eventData.start).format('YYYY-MM-DD HH:mm')} -{' '}
{dayjs(eventData.end).format('HH:mm')}
</p>
<p className="mb-2">
Przewodniczący: {chairman?.first_name} {chairman?.last_name}
</p>
{eventData.resource?.members_of_committee?.length ? (
<>
Komisja:{' '}

View File

@ -13,14 +13,14 @@ type SelectValue = {
}
const Group = () => {
const { id } = useParams<{ id: string }>()
const { id: groupId } = useParams<{ id: string }>()
const location = useLocation()
const {
data: groups,
refetch,
isLoading: isGroupLoading,
} = useQuery(['getGroup', id], () => getGroup(Number(id)))
} = useQuery(['getGroup', groupId], () => getGroup(Number(groupId)))
const { name, project_supervisor, students } = groups?.data || {}
const [yearGroupId] = useLocalStorageState('yearGroupId')
@ -33,9 +33,9 @@ const Group = () => {
setStudentOptions(
data?.data.students
.filter(({ groups }) => !groups?.length)
.map(({ first_name, last_name, index }) => {
.map(({ first_name, last_name, index, id }) => {
return {
value: index,
value: id,
label: `${first_name} ${last_name} (${index})`,
}
}),
@ -72,7 +72,7 @@ const Group = () => {
location.pathname.includes('coordinator')
? 'coordinator'
: 'supervisor'
}/groups/${id}/grade-card`}
}/groups/${groupId}/grade-card`}
>
<button className="btn btn-success mt-2">KARTA OCENY</button>
</Link>
@ -88,7 +88,7 @@ const Group = () => {
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{students?.map(({ first_name, last_name, index }) => (
{students?.map(({ first_name, last_name, index, id }) => (
<tr key={index}>
<td>{first_name}</td>
<td>{last_name}</td>
@ -98,13 +98,13 @@ const Group = () => {
onClick={() => {
if (students && name && project_supervisor)
mutateEditGroup({
id: Number(id),
id: Number(groupId),
payload: {
name,
project_supervisor_id: project_supervisor.id,
students: students
.map((student) => student.index)
.filter((i) => index !== i),
.map((student) => student.id)
.filter((i) => id !== i),
},
})
}}
@ -139,12 +139,12 @@ const Group = () => {
onClick={() => {
if (students && name && project_supervisor)
mutateEditGroup({
id: Number(id),
id: Number(groupId),
payload: {
name,
project_supervisor_id: project_supervisor.id,
students: [
...students.map((student) => student.index),
...students.map((student) => student.id),
newStudent,
],
},

View File

@ -41,7 +41,7 @@ const Leaders = () => {
const { mutate: mutateDelete } = useMutation(
'deleteLeader',
(index: number) => deleteLeader(index),
(id: number) => deleteLeader(id),
{
onSuccess: () => refetchLeaders(),
},

View File

@ -2,9 +2,11 @@ import { Calendar, Views, View } from 'react-big-calendar'
import { useCallback, useEffect, useState } from 'react'
import { useMutation, useQuery } from 'react-query'
import {
closeEnrollments,
createEvent,
downloadSchedule,
getTermsOfDefences,
openEnrollments,
setDateOfExaminationSchedule,
} from '../../api/schedule'
import { Link, useParams } from 'react-router-dom'
@ -28,7 +30,7 @@ const customStyles = {
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
width: '400px',
height: '400px',
height: '500px',
},
}
type SelectValue = {
@ -57,6 +59,12 @@ const Schedule = () => {
id: number
resource: any
}>()
const [scheduleData, setScheduleData] = useLocalStorageState<{
start_date: string
end_date: string
title: string
open_enrollments: 'i' | 'o' | 'c'
}>('scheduleData')
const [view, setView] = useState<View>(Views.WEEK)
const onView = useCallback((newView: any) => setView(newView), [setView])
@ -68,14 +76,9 @@ const Schedule = () => {
from: string
to: string
project_supervisors: NestedValue<any[]>
chairman_of_committee: number
}>({ mode: 'onBlur' })
const [committeeOptions, setCommitteeOptions] = useState<SelectValue[]>([])
const [startDate, setStartDate] = useState(new Date())
const [endDate, setEndDate] = useState(new Date())
const [scheduleData, setScheduleData] = useLocalStorageState<{
start_date_for_enrollment_students: string
end_date_for_enrollment_students: string
}>('scheduleData')
const { isLoading: areLeadersLoading } = useQuery(
'leaders',
@ -102,7 +105,14 @@ const Schedule = () => {
if (data) {
setEvents(
data.data.term_of_defences.map(
({ id, start_date, end_date, members_of_committee, group }) => {
({
id,
start_date,
end_date,
members_of_committee,
group,
chairman_of_committee,
}) => {
let initials = ''
let title = ''
members_of_committee.forEach((member) => {
@ -129,6 +139,7 @@ const Schedule = () => {
resource: {
members_of_committee,
group,
chairman_of_committee,
},
}
},
@ -146,6 +157,7 @@ const Schedule = () => {
start_date: string
end_date: string
scheduleId: number
chairman_of_committee: number
}) => createEvent(data),
)
@ -164,6 +176,36 @@ const Schedule = () => {
},
)
const { mutate: mutateOpenEnrollments } = useMutation(
['openEnrollments'],
() => openEnrollments(Number(id)),
{
onSuccess: () => {
if (scheduleData) {
setScheduleData({
...scheduleData,
open_enrollments: 'o',
})
}
},
},
)
const { mutate: mutateCloseEnrollments } = useMutation(
['openEnrollments'],
() => closeEnrollments(Number(id)),
{
onSuccess: () => {
if (scheduleData) {
setScheduleData({
...scheduleData,
open_enrollments: 'c',
})
}
},
},
)
const handleSelectSlot = async (event: any) => {
setSelectedDate(event)
setIsModalOpen(true)
@ -202,6 +244,7 @@ const Schedule = () => {
.format('YYYY-MM-DD HH:mm:ss'),
scheduleId: Number(id),
project_supervisors: data?.project_supervisors,
chairman_of_committee: data?.chairman_of_committee,
})
refetch()
reset()
@ -209,33 +252,6 @@ const Schedule = () => {
}
}
const { mutate: mutateSetDateOfExaminationSchedule } = useMutation(
['dateOfExaminationSchedule'],
(data: {
start_date_for_enrollment_students: string
end_date_for_enrollment_students: string
}) => setDateOfExaminationSchedule(Number(id), data),
{
onSuccess: () => {
setScheduleData({
...scheduleData,
start_date_for_enrollment_students: startDate.toISOString(),
end_date_for_enrollment_students: endDate.toISOString(),
})
},
},
)
useEffect(() => {
if (
scheduleData?.start_date_for_enrollment_students &&
scheduleData?.end_date_for_enrollment_students
) {
setStartDate(new Date(scheduleData.start_date_for_enrollment_students))
setEndDate(new Date(scheduleData.end_date_for_enrollment_students))
}
}, [scheduleData])
const eventGetter = (event: any) => {
return event?.resource?.group
? {
@ -252,54 +268,45 @@ const Schedule = () => {
return (
<div className="flex flex-col">
<div>
Start zapisów dla studentów:{' '}
<label className="ml-2" htmlFor="end_date">
Od{' '}
</label>
<DateTimePicker
onChange={setStartDate}
value={startDate}
format={'yyyy-MM-dd HH:mm:ss'}
locale={'pl'}
disableClock
/>
<label className="ml-2" htmlFor="end_date">
Do{' '}
</label>
<DateTimePicker
onChange={setEndDate}
value={endDate}
format={'yyyy-MM-dd HH:mm:ss'}
locale={'pl'}
disableClock
/>
<button
className="btn btn-success ml-2"
onClick={() =>
mutateSetDateOfExaminationSchedule({
start_date_for_enrollment_students: startDate.toISOString(),
end_date_for_enrollment_students: endDate.toISOString(),
})
}
>
ZAPISZ
</button>
</div>
<div className="flex self-end gap-4 mb-4">
<Link
className="underline font-bold self-center"
to={`/coordinator/schedule/${id}/workload`}
>
Statystyki
</Link>
<button
className="btn btn-success btn-xs md:btn-md "
onClick={() => mutateDownload(Number(id))}
>
Eksportuj harmonogram
</button>
<div className="flex justify-between">
<div>
Zapisy dla studentów:{' '}
<strong>
{scheduleData?.open_enrollments === 'o' ? 'Otwarte' : 'Zamknięte'}
</strong>
<button
className="btn btn-success ml-2"
onClick={() => {
mutateOpenEnrollments()
}}
>
ROZPOCZNIJ
</button>
<button
className="btn btn-success ml-2"
onClick={() => {
mutateCloseEnrollments()
}}
>
ZAKOŃCZ
</button>
</div>
<div className="flex self-end gap-4 mb-4">
<Link
className="underline font-bold self-center"
to={`/coordinator/schedule/${id}/workload`}
>
Statystyki
</Link>
<button
className="btn btn-success btn-xs md:btn-md "
onClick={() => mutateDownload(Number(id))}
>
Eksportuj harmonogram
</button>
</div>
</div>
<Calendar
localizer={localizer}
startAccessor="start"
@ -359,6 +366,29 @@ const Schedule = () => {
/>
)}
/>
<label className="label" htmlFor="chairman_of_committee">
Przewodniczący
</label>
<Controller
control={control}
name="chairman_of_committee"
rules={{ required: true }}
render={({ field: { onChange, onBlur } }) => (
<Select
options={committeeOptions}
placeholder="Wybierz przewodniczącego komisji"
onChange={(val) => onChange(val?.value)}
onBlur={onBlur}
styles={{
control: (styles) => ({
...styles,
padding: '0.3rem',
borderRadius: '0.5rem',
}),
}}
/>
)}
/>
</div>
<button className="btn btn-success mt-4">Dodaj termin</button>
</form>

View File

@ -76,7 +76,7 @@ const Students = () => {
const { mutate: mutateDelete } = useMutation(
'deleteStudent',
(index: number) => deleteStudent(index),
(id: number) => deleteStudent(id),
{
onSuccess: () => refetchStudents(),
},
@ -178,14 +178,14 @@ const Students = () => {
<tbody className="divide-y divide-gray-100">
{students?.data?.students
?.filter((st) => !st.groups?.length || !showGroupless)
.map(({ first_name, last_name, index, groups, mode }) => (
.map(({ first_name, last_name, index, groups, id }) => (
<tr key={index}>
<td>{first_name}</td>
<td>{last_name}</td>
<td>{index}</td>
<td>{groups?.length ? 'Tak' : 'Nie'}</td>
<td>
<button onClick={() => mutateDelete(index)}>
<button onClick={() => mutateDelete(id)}>
<IconRemove />
</button>
</td>

View File

@ -21,17 +21,14 @@ const ScheduleAddGroup = ({
closeModal: () => void
}) => {
const { register, handleSubmit, reset, control } = useForm<{
student_index: number
student_id: number
}>({ mode: 'onBlur' })
const [studentId] = useLocalStorageState('studentId')
const [studentId] = useLocalStorageState('userId')
const { mutate: mutateAssignGroup } = useMutation(
['assignGroup'],
(data: {
scheduleId: number
enrollmentId: number
studentIndex: number
}) => assignGroup(data),
(data: { scheduleId: number; enrollmentId: number; studentId: number }) =>
assignGroup(data),
{
onSuccess: () => {
closeModal()
@ -43,7 +40,7 @@ const ScheduleAddGroup = ({
mutateAssignGroup({
scheduleId: Number(scheduleId),
enrollmentId: eventData.id,
studentIndex: Number(studentId),
studentId: Number(studentId),
})
}

View File

@ -24,7 +24,7 @@ const customStyles = {
const localizer = dayjsLocalizer(dayjs)
const StudentSchedule = () => {
const [studentId] = useLocalStorageState('studentId')
const [studentId] = useLocalStorageState('userId')
const { id } = useParams<{ id: string }>()
const [events, setEvents] = useState<
{
@ -189,7 +189,10 @@ const StudentSchedule = () => {
<ScheduleAddGroup
eventData={selectedDate}
scheduleId={id}
closeModal={() => setIsEditModalOpen(false)}
closeModal={() => {
setIsEditModalOpen(false)
refetch()
}}
/>
) : null}
</Modal>

View File

@ -45,7 +45,7 @@ const SupervisorGroups = () => {
const { mutate: mutateDelete } = useMutation(
'deleteGroup',
(index: number) => deleteGroup(index),
(id: number) => deleteGroup(id),
{
onSuccess: () => refetchGroups(),
},