Update login, update api requests, add schedule view for supervisors
This commit is contained in:
parent
8dcb7528f9
commit
2120ab9690
@ -19,6 +19,7 @@ import Schedule from './views/coordinator/Schedule'
|
||||
import SupervisorSchedules from './views/supervisor/SupervisorSchedules'
|
||||
import StudentSchedules from './views/student/StudentSchedules'
|
||||
import StudentSchedule from './views/student/StudentSchedule'
|
||||
import SupervisorSchedule from './views/supervisor/SupervisorSchedule'
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@ -55,6 +56,7 @@ function App() {
|
||||
<Route index element={<Navigate to="groups" />} />
|
||||
<Route path="groups" element={<Groups />} />
|
||||
<Route path="schedule" element={<SupervisorSchedules />} />
|
||||
<Route path="schedule/:id" element={<SupervisorSchedule />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</QueryClientProvider>
|
||||
|
@ -14,7 +14,6 @@ export interface Leader {
|
||||
email: string
|
||||
limit_group: number
|
||||
count_groups: number
|
||||
mode: number
|
||||
}
|
||||
|
||||
export const getLeaders = (
|
||||
@ -32,7 +31,7 @@ export const getLeaders = (
|
||||
{ params },
|
||||
)
|
||||
|
||||
export const createLeader = (payload: Leader) =>
|
||||
export const createLeader = (payload: Partial<Leader>) =>
|
||||
axiosInstance.post('coordinator/project_supervisor/', payload)
|
||||
|
||||
export const deleteLeader = (id: number) =>
|
||||
|
@ -1,34 +1,45 @@
|
||||
import axiosInstance from './axiosInstance'
|
||||
|
||||
interface TermOfDefences {
|
||||
id: number
|
||||
start_date: string
|
||||
end_date: string
|
||||
title: string
|
||||
members_of_committee: {
|
||||
members: { first_name: string; last_name: string }[]
|
||||
}
|
||||
group: { name: string }
|
||||
}
|
||||
|
||||
export const getTermsOfDefences = (scheduleId: number) => {
|
||||
return axiosInstance.get<{
|
||||
term_of_defences: {
|
||||
id: number
|
||||
start_date: string
|
||||
end_date: string
|
||||
title: string
|
||||
members_of_committee: {
|
||||
members: { first_name: string; last_name: string }[]
|
||||
}
|
||||
group: { name: string }
|
||||
}[]
|
||||
term_of_defences: TermOfDefences[]
|
||||
}>(`coordinator/enrollments/${scheduleId}/term-of-defences/`)
|
||||
}
|
||||
|
||||
export const getStudentsTermsOfDefences = (scheduleId: number) => {
|
||||
export const getStudentsTermsOfDefences = (
|
||||
scheduleId: number,
|
||||
studentIndex: number,
|
||||
) => {
|
||||
return axiosInstance.get<{
|
||||
term_of_defences: {
|
||||
id: number
|
||||
start_date: string
|
||||
end_date: string
|
||||
title: string
|
||||
members_of_committee: {
|
||||
members: { first_name: string; last_name: string }[]
|
||||
term_of_defences: TermOfDefences[]
|
||||
}>(
|
||||
`students/examination-schedule/${scheduleId}/enrollments?student_index=${studentIndex}`,
|
||||
)
|
||||
}
|
||||
group: { name: string }
|
||||
}[]
|
||||
}>(`students/examination-schedule/${scheduleId}/enrollments/`)
|
||||
|
||||
export const getSupervisorTermsOfDefences = (scheduleId: number) => {
|
||||
return axiosInstance.get<{
|
||||
term_of_defences: TermOfDefences[]
|
||||
}>(`project_supervisor/${scheduleId}/term-of-defences?id=1`) //fix hardcode id
|
||||
}
|
||||
|
||||
export const getAvailabilityForSupervisor = (scheduleId: number) => {
|
||||
return axiosInstance.get<{
|
||||
free_times: { id: number; start_date: string; end_date: string }[]
|
||||
}>(`project_supervisor/${scheduleId}/temporary-availabilities?id=1`) //fix hardcode id
|
||||
}
|
||||
|
||||
export const getSchedules = (year_group_id: number = 1) => {
|
||||
return axiosInstance.get<{
|
||||
examination_schedules: {
|
||||
@ -125,3 +136,34 @@ export const downloadSchedule = (scheduleId: number) =>
|
||||
responseType: 'blob',
|
||||
},
|
||||
)
|
||||
|
||||
export const addAvailability = ({
|
||||
start_date,
|
||||
end_date,
|
||||
scheduleId,
|
||||
project_supervisor_id,
|
||||
}: {
|
||||
start_date: string
|
||||
end_date: string
|
||||
scheduleId: number
|
||||
project_supervisor_id: number
|
||||
}) => {
|
||||
return axiosInstance.post(`project_supervisor/${scheduleId}/enrollments/`, {
|
||||
start_date,
|
||||
end_date,
|
||||
project_supervisor_id,
|
||||
})
|
||||
}
|
||||
|
||||
export const setDateOfExaminationSchedule = (
|
||||
scheduleId: number,
|
||||
payload: {
|
||||
start_date_for_enrollment_students: string
|
||||
end_date_for_enrollment_students: string
|
||||
},
|
||||
) => {
|
||||
return axiosInstance.put(
|
||||
`coordinator/examination_schedule/${scheduleId}/date`,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
25
frontend/src/api/yearGroups.ts
Normal file
25
frontend/src/api/yearGroups.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import axiosInstance from './axiosInstance'
|
||||
|
||||
export interface CreateYearGroup {
|
||||
name: string
|
||||
mode: string
|
||||
}
|
||||
|
||||
export interface YearGroup {
|
||||
id: number
|
||||
mode: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export const getYearGroups = (
|
||||
params: Partial<{
|
||||
page: number
|
||||
per_page: number
|
||||
}>,
|
||||
) =>
|
||||
axiosInstance.get<{ max_pages: number; year_groups: YearGroup[] }>(
|
||||
`coordinator/year-group`,
|
||||
{
|
||||
params,
|
||||
},
|
||||
)
|
@ -1,10 +1,71 @@
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { useState } from 'react'
|
||||
import { useQuery } from 'react-query'
|
||||
import { NavLink, useNavigate } from 'react-router-dom'
|
||||
import { InputActionMeta } from 'react-select'
|
||||
import Select from 'react-select'
|
||||
import useLocalStorageState from 'use-local-storage-state'
|
||||
import { getStudents } from '../api/students'
|
||||
import { getYearGroups } from '../api/yearGroups'
|
||||
|
||||
type SelectValue = {
|
||||
value: string | number
|
||||
label: string
|
||||
}
|
||||
|
||||
const Login = () => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [yearGroupId, setYearGroupId] = useLocalStorageState('yearGroupId', {
|
||||
defaultValue: 1,
|
||||
})
|
||||
const [studentId, setStudentId] = useLocalStorageState('studentId')
|
||||
const [supervisorId, setSupervisorId] = useLocalStorageState('supervisorId', {
|
||||
defaultValue: 1,
|
||||
})
|
||||
const [studentOptions, setStudentOptions] = useState<SelectValue[]>([])
|
||||
const [yearGroupOptions, setYearGroupOptions] = useState<SelectValue[]>([])
|
||||
const [selectedYear, setSelectedYear] = useState<any>()
|
||||
const [selectedRole, setSelectedRole] = useState<any>()
|
||||
|
||||
useQuery(
|
||||
'students',
|
||||
() => getStudents({ year_group_id: yearGroupId, per_page: 1000 }),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setStudentOptions(
|
||||
data?.data.students.map(({ first_name, last_name, index }) => {
|
||||
return {
|
||||
value: index,
|
||||
label: `${first_name} ${last_name} (${index})`,
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
useQuery('year_groups', () => getYearGroups({ per_page: 100 }), {
|
||||
onSuccess: (data) => {
|
||||
setYearGroupOptions(
|
||||
data?.data.year_groups.map(({ name, id }) => {
|
||||
return {
|
||||
value: id,
|
||||
label: name,
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const onStudentChange = (v: any) => {
|
||||
setSelectedRole(v?.value)
|
||||
setStudentId(v.value)
|
||||
}
|
||||
|
||||
const onYearChange = (v: any) => {
|
||||
setSelectedYear(v?.value)
|
||||
setYearGroupId(v?.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -12,7 +73,36 @@ const Login = () => {
|
||||
<h1 className="text-xl font-bold mx-auto">System PRI</h1>
|
||||
</div>
|
||||
<div className="w-full lg:w-1/4 flex flex-col mx-auto mt-20 px-10 py-5 bg-gray-300 rounded-lg shadow">
|
||||
<div className="form-control">
|
||||
<label className="label">Rok</label>
|
||||
<Select
|
||||
closeMenuOnSelect={true}
|
||||
options={yearGroupOptions}
|
||||
placeholder="Wybierz rok"
|
||||
onChange={onYearChange}
|
||||
styles={{
|
||||
control: (styles) => ({
|
||||
...styles,
|
||||
padding: '0.3rem',
|
||||
borderRadius: '0.5rem',
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<label className="label">Student</label>
|
||||
<Select
|
||||
closeMenuOnSelect={true}
|
||||
options={studentOptions}
|
||||
placeholder="Wybierz studenta"
|
||||
onChange={onStudentChange}
|
||||
styles={{
|
||||
control: (styles) => ({
|
||||
...styles,
|
||||
padding: '0.3rem',
|
||||
borderRadius: '0.5rem',
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* <div className="form-control">
|
||||
<label className="label" htmlFor="login">
|
||||
Login
|
||||
</label>
|
||||
@ -23,11 +113,17 @@ const Login = () => {
|
||||
Hasło
|
||||
</label>
|
||||
<input className="input input-bordered" id="password" type="text" />
|
||||
</div>
|
||||
<button className="btn mt-5 text-lg">Zaloguj</button>
|
||||
</div> */}
|
||||
<button
|
||||
className="btn mt-5 text-lg"
|
||||
disabled={!selectedRole || !selectedYear}
|
||||
onClick={() => navigate('/student')}
|
||||
>
|
||||
Zaloguj
|
||||
</button>
|
||||
<div className="flex flex-col mt-3">
|
||||
<NavLink to="/coordinator">Koordynator</NavLink>
|
||||
<NavLink to="/student">Student</NavLink>
|
||||
{/* <NavLink to="/student">Student</NavLink> */}
|
||||
<NavLink to="/supervisor">Opiekun</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -28,7 +28,7 @@ const AddGroup = () => {
|
||||
|
||||
const { isLoading: areStudentsLoading } = useQuery(
|
||||
'students',
|
||||
() => getStudents({ per_page: 1000 }),
|
||||
() => getStudents({ year_group_id: Number(yearGroupId), per_page: 1000 }),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setStudentOptions(
|
||||
@ -46,7 +46,7 @@ const AddGroup = () => {
|
||||
)
|
||||
const { isLoading: areLeadersLoading } = useQuery(
|
||||
'leaders',
|
||||
() => getLeaders({ per_page: 1000 }),
|
||||
() => getLeaders({ year_group_id: Number(yearGroupId), per_page: 1000 }),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setSupervisorOptions(
|
||||
|
@ -83,7 +83,7 @@ const AddLeader = () => {
|
||||
<InputError>Email jest wymagany</InputError>
|
||||
)}
|
||||
</div>
|
||||
<div className="form-control">
|
||||
{/* <div className="form-control">
|
||||
<label className="label" htmlFor="limit_group">
|
||||
Limit grup
|
||||
</label>
|
||||
@ -99,49 +99,7 @@ const AddLeader = () => {
|
||||
{errors.limit_group?.type === 'pattern' && (
|
||||
<InputError>Limit grup musi być liczbą dodatnią</InputError>
|
||||
)}
|
||||
</div>
|
||||
<div className="form-control gap-2">
|
||||
<label className="label">Tryb studiów</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
className="radio"
|
||||
id="mode-1"
|
||||
type="radio"
|
||||
{...register('mode', {
|
||||
required: true,
|
||||
})}
|
||||
value="0"
|
||||
/>
|
||||
<label htmlFor="mode-1">Stacjonarny</label>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
className="radio"
|
||||
id="mode-0"
|
||||
type="radio"
|
||||
{...register('mode', {
|
||||
required: true,
|
||||
})}
|
||||
value="1"
|
||||
/>
|
||||
<label htmlFor="mode-0">Niestacjonarny</label>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
className="radio"
|
||||
id="mode-2"
|
||||
type="radio"
|
||||
{...register('mode', {
|
||||
required: true,
|
||||
})}
|
||||
value="2"
|
||||
/>
|
||||
<label htmlFor="mode-2">Oba</label>
|
||||
</div>
|
||||
{errors.mode?.type === 'required' && (
|
||||
<InputError>Wybierz tryb studiów</InputError>
|
||||
)}
|
||||
</div>
|
||||
</div> */}
|
||||
<button className="btn btn-success mt-4">Dodaj opiekuna</button>
|
||||
</form>
|
||||
)
|
||||
|
@ -73,11 +73,11 @@ const EditSchedule = ({
|
||||
{DateTime.fromJSDate(eventData.start).toFormat('yyyy-LL-dd HH:mm:ss')}{' '}
|
||||
- {DateTime.fromJSDate(eventData.end).toFormat('yyyy-LL-dd HH:mm:ss')}
|
||||
</p>
|
||||
{eventData.resource.committee.members.length ? (
|
||||
{eventData.resource?.members_of_committee?.length ? (
|
||||
<>
|
||||
Komisja:{' '}
|
||||
<ul className="list-disc">
|
||||
{eventData.resource.committee.members.map((member: any) => (
|
||||
{eventData.resource.members_of_committee.map((member: any) => (
|
||||
<li
|
||||
key={`${member.first_name} ${member.last_name}`}
|
||||
className="ml-4"
|
||||
|
@ -85,7 +85,7 @@ const Leaders = () => {
|
||||
<th>Email</th>
|
||||
<th>Limit grup</th>
|
||||
<th>Liczba grup</th>
|
||||
<th>Tryb</th>
|
||||
{/* <th>Tryb</th> */}
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -98,7 +98,6 @@ const Leaders = () => {
|
||||
email,
|
||||
limit_group,
|
||||
count_groups,
|
||||
mode,
|
||||
}) => (
|
||||
<tr key={id}>
|
||||
<td>{first_name}</td>
|
||||
@ -106,13 +105,13 @@ const Leaders = () => {
|
||||
<td>{email}</td>
|
||||
<td>{limit_group}</td>
|
||||
<td>{count_groups}</td>
|
||||
<td>
|
||||
{/* <td>
|
||||
{mode === 0
|
||||
? 'Stacjonarny'
|
||||
: mode === 1
|
||||
? 'Niestacjonarny'
|
||||
: 'Nie/stacjonarny'}
|
||||
</td>
|
||||
</td> */}
|
||||
<td>
|
||||
<button onClick={() => mutateDelete(id)}>
|
||||
<IconRemove />
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { Calendar, luxonLocalizer, Views } from 'react-big-calendar'
|
||||
import { DateTime, Settings } from 'luxon'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useMutation, useQuery } from 'react-query'
|
||||
import {
|
||||
createEvent,
|
||||
downloadSchedule,
|
||||
getTermsOfDefences,
|
||||
setDateOfExaminationSchedule,
|
||||
} from '../../api/schedule'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import Modal from 'react-modal'
|
||||
@ -15,6 +16,7 @@ import Select from 'react-select'
|
||||
import { getLeaders } from '../../api/leaders'
|
||||
import useLocalStorageState from 'use-local-storage-state'
|
||||
import bigCalendarTranslations from '../../utils/bigCalendarTranslations'
|
||||
import DatePicker from 'react-date-picker'
|
||||
|
||||
const customStyles = {
|
||||
content: {
|
||||
@ -66,6 +68,12 @@ const Schedule = () => {
|
||||
project_supervisors: NestedValue<any[]>
|
||||
}>({ 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',
|
||||
@ -194,6 +202,30 @@ 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) {
|
||||
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
|
||||
? {
|
||||
@ -210,6 +242,32 @@ const Schedule = () => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div>
|
||||
Start zapisów dla studentów: <label htmlFor="end_date">Od </label>
|
||||
<DatePicker
|
||||
onChange={setStartDate}
|
||||
value={startDate}
|
||||
format={'yyyy-MM-dd'}
|
||||
/>
|
||||
<label htmlFor="end_date">Do </label>
|
||||
<DatePicker
|
||||
onChange={setEndDate}
|
||||
value={endDate}
|
||||
format={'yyyy-MM-dd'}
|
||||
/>
|
||||
<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>
|
||||
|
||||
<button
|
||||
className="btn btn-success btn-xs md:btn-md self-end mb-4"
|
||||
onClick={() => mutateDownload(Number(id))}
|
||||
|
@ -13,6 +13,7 @@ const Schedules = () => {
|
||||
mode: 'onBlur',
|
||||
})
|
||||
const [yearGroupId] = useLocalStorageState('yearGroupId')
|
||||
const [scheduleData, setScheduleData] = useLocalStorageState('scheduleData')
|
||||
const [startDate, setStartDate] = useState(new Date())
|
||||
const [endDate, setEndDate] = useState(new Date())
|
||||
|
||||
@ -59,13 +60,21 @@ const Schedules = () => {
|
||||
Od
|
||||
</label>
|
||||
|
||||
<DatePicker onChange={setStartDate} value={startDate} />
|
||||
<DatePicker
|
||||
onChange={setStartDate}
|
||||
value={startDate}
|
||||
format={'yyyy-MM-dd'}
|
||||
/>
|
||||
|
||||
<label className="label" htmlFor="end_date">
|
||||
Do
|
||||
</label>
|
||||
|
||||
<DatePicker onChange={setEndDate} value={endDate} />
|
||||
<DatePicker
|
||||
onChange={setEndDate}
|
||||
value={endDate}
|
||||
format={'yyyy-MM-dd'}
|
||||
/>
|
||||
</div>
|
||||
<button className="btn btn-success mt-4">Stwórz zapisy</button>
|
||||
</form>
|
||||
@ -74,9 +83,15 @@ const Schedules = () => {
|
||||
schedules?.data?.examination_schedules.map((schedule) => (
|
||||
<h3 className="text-xl " key={schedule.title}>
|
||||
-{' '}
|
||||
<Link to={`/coordinator/schedule/${schedule.id}`}>
|
||||
<Link
|
||||
to={`/coordinator/schedule/${schedule.id}`}
|
||||
onClick={() => {
|
||||
setScheduleData(schedule)
|
||||
}}
|
||||
>
|
||||
{schedule.title}
|
||||
</Link>
|
||||
1
|
||||
</h3>
|
||||
))}
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@ import { DateTime } from 'luxon'
|
||||
import React, { useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { useMutation } from 'react-query'
|
||||
import useLocalStorageState from 'use-local-storage-state'
|
||||
import { assignGroup } from '../../api/schedule'
|
||||
|
||||
const ScheduleAddGroup = ({
|
||||
@ -20,6 +21,7 @@ const ScheduleAddGroup = ({
|
||||
const { register, handleSubmit, reset, control } = useForm<{
|
||||
student_index: number
|
||||
}>({ mode: 'onBlur' })
|
||||
const [studentId] = useLocalStorageState('studentId')
|
||||
|
||||
const { mutate: mutateAssignGroup } = useMutation(
|
||||
['assignGroup'],
|
||||
@ -34,7 +36,7 @@ const ScheduleAddGroup = ({
|
||||
mutateAssignGroup({
|
||||
scheduleId: Number(scheduleId),
|
||||
enrollmentId: eventData.id,
|
||||
studentIndex: data.student_index,
|
||||
studentIndex: Number(studentId),
|
||||
})
|
||||
}
|
||||
|
||||
@ -46,11 +48,11 @@ const ScheduleAddGroup = ({
|
||||
{DateTime.fromJSDate(eventData.start).toFormat('yyyy-LL-dd HH:mm:ss')}{' '}
|
||||
- {DateTime.fromJSDate(eventData.end).toFormat('yyyy-LL-dd HH:mm:ss')}
|
||||
</h3>
|
||||
{eventData.resource.committee.members.length > 0 && (
|
||||
{eventData.resource?.members_of_committee?.length > 0 && (
|
||||
<>
|
||||
Komisja:{' '}
|
||||
<ul className="list-disc">
|
||||
{eventData.resource.committee.members.map((member: any) => (
|
||||
{eventData.resource.members_of_committee.map((member: any) => (
|
||||
<li
|
||||
key={`${member.first_name} ${member.last_name}`}
|
||||
className="ml-4"
|
||||
@ -66,17 +68,6 @@ const ScheduleAddGroup = ({
|
||||
)}
|
||||
{!eventData.resource.group && (
|
||||
<>
|
||||
<div className="form-control">
|
||||
<label className="label" htmlFor="student_index">
|
||||
Indeks
|
||||
</label>
|
||||
<input
|
||||
className="input input-bordered"
|
||||
id="to"
|
||||
type="text"
|
||||
{...register('student_index', { required: true })}
|
||||
/>
|
||||
</div>
|
||||
<button className="btn btn-success mt-4">ZAPISZ</button>
|
||||
</>
|
||||
)}
|
||||
|
@ -8,6 +8,7 @@ import Modal from 'react-modal'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import ScheduleAddGroup from './ScheduleAddGroup'
|
||||
import bigCalendarTranslations from '../../utils/bigCalendarTranslations'
|
||||
import useLocalStorageState from 'use-local-storage-state'
|
||||
|
||||
const customStyles = {
|
||||
content: {
|
||||
@ -24,6 +25,7 @@ const StudentSchedule = () => {
|
||||
Settings.defaultZone = DateTime.local().zoneName
|
||||
Settings.defaultLocale = 'pl'
|
||||
|
||||
const [studentId] = useLocalStorageState('studentId')
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const [events, setEvents] = useState<
|
||||
{
|
||||
@ -55,7 +57,7 @@ const StudentSchedule = () => {
|
||||
|
||||
const { refetch } = useQuery(
|
||||
['studentSchedules'],
|
||||
() => getStudentsTermsOfDefences(Number(id)),
|
||||
() => getStudentsTermsOfDefences(Number(id), Number(studentId)),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setEvents(
|
||||
|
256
frontend/src/views/supervisor/SupervisorSchedule.tsx
Normal file
256
frontend/src/views/supervisor/SupervisorSchedule.tsx
Normal file
@ -0,0 +1,256 @@
|
||||
import { Calendar, luxonLocalizer, Views } from 'react-big-calendar'
|
||||
import { DateTime, Settings } from 'luxon'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useMutation, useQuery } from 'react-query'
|
||||
import {
|
||||
addAvailability,
|
||||
getAvailabilityForSupervisor,
|
||||
getSupervisorTermsOfDefences,
|
||||
} from '../../api/schedule'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import Modal from 'react-modal'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import EditSchedule from '../coordinator/EditSchedule'
|
||||
import useLocalStorageState from 'use-local-storage-state'
|
||||
import bigCalendarTranslations from '../../utils/bigCalendarTranslations'
|
||||
|
||||
const customStyles = {
|
||||
content: {
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
right: 'auto',
|
||||
bottom: 'auto',
|
||||
marginRight: '-50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
},
|
||||
}
|
||||
type SelectValue = {
|
||||
value: string | number
|
||||
label: string
|
||||
}
|
||||
|
||||
const SupervisorSchedule = () => {
|
||||
Settings.defaultZone = DateTime.local().zoneName
|
||||
Settings.defaultLocale = 'pl'
|
||||
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const [yearGroupId] = useLocalStorageState('yearGroupId')
|
||||
const [supervisorId] = useLocalStorageState('supervisorId')
|
||||
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.MONTH)
|
||||
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, isSuccess } = useQuery(
|
||||
['schedules'],
|
||||
() => getSupervisorTermsOfDefences(Number(id)),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setEvents(
|
||||
data.data.term_of_defences.map(
|
||||
({
|
||||
id,
|
||||
start_date,
|
||||
end_date,
|
||||
title = 'Obrona',
|
||||
members_of_committee,
|
||||
group,
|
||||
}) => {
|
||||
return {
|
||||
id,
|
||||
title: `${group?.name ?? '-'}`,
|
||||
start: new Date(start_date),
|
||||
end: new Date(end_date),
|
||||
resource: {
|
||||
members_of_committee,
|
||||
},
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const { refetch: refetchAvailability } = useQuery(
|
||||
['availability'],
|
||||
() => getAvailabilityForSupervisor(Number(id)),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setEvents([
|
||||
...events,
|
||||
...data.data.free_times.map(({ id, start_date, end_date }) => {
|
||||
return {
|
||||
id,
|
||||
title: 'Moja dostępność',
|
||||
start: new Date(start_date),
|
||||
end: new Date(end_date),
|
||||
resource: {},
|
||||
}
|
||||
}),
|
||||
])
|
||||
},
|
||||
enabled: isSuccess,
|
||||
},
|
||||
)
|
||||
const { mutateAsync: mutateAddAvailability } = useMutation(
|
||||
['createEvent'],
|
||||
(data: {
|
||||
project_supervisor_id: number
|
||||
start_date: string
|
||||
end_date: string
|
||||
scheduleId: number
|
||||
}) => addAvailability(data),
|
||||
)
|
||||
|
||||
const handleSelectSlot = async (event: any) => {
|
||||
setSelectedDate(event)
|
||||
if (view === Views.MONTH) {
|
||||
setIsModalOpen(true)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectEvent = useCallback(
|
||||
(event: {
|
||||
id: number
|
||||
title: string
|
||||
start: Date
|
||||
end: Date
|
||||
resource: any
|
||||
}) => {
|
||||
setSelectedDate(event)
|
||||
setIsEditModalOpen(true)
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
function closeModal() {
|
||||
setIsModalOpen(false)
|
||||
}
|
||||
const onSubmit = async (data: any) => {
|
||||
if (selectedDate && view === Views.MONTH) {
|
||||
const from = data.from.split(':')
|
||||
const to = data.to.split(':')
|
||||
await mutateAddAvailability({
|
||||
start_date: DateTime.fromJSDate(selectedDate.start)
|
||||
.set({ hour: from[0], minute: from[1] })
|
||||
.toFormat('yyyy-LL-dd HH:mm:ss'),
|
||||
end_date: DateTime.fromJSDate(selectedDate.start)
|
||||
.set({ hour: to[0], minute: to[1] })
|
||||
.toFormat('yyyy-LL-dd HH:mm:ss'),
|
||||
scheduleId: Number(id),
|
||||
project_supervisor_id: Number(supervisorId),
|
||||
})
|
||||
setEvents([])
|
||||
refetch()
|
||||
refetchAvailability()
|
||||
reset()
|
||||
closeModal()
|
||||
}
|
||||
}
|
||||
|
||||
const eventGetter = (event: any) => {
|
||||
return event?.title === '-'
|
||||
? {
|
||||
style: {
|
||||
backgroundColor: '#3174ad',
|
||||
},
|
||||
}
|
||||
: {
|
||||
style: {
|
||||
backgroundColor: '#329f32',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<Calendar
|
||||
localizer={luxonLocalizer(DateTime)}
|
||||
startAccessor="start"
|
||||
endAccessor="end"
|
||||
selectable
|
||||
style={{ height: '80vh' }}
|
||||
onSelectEvent={handleSelectEvent}
|
||||
onSelectSlot={handleSelectSlot}
|
||||
events={events}
|
||||
onView={onView}
|
||||
view={view}
|
||||
eventPropGetter={eventGetter}
|
||||
min={DateTime.fromObject({ hour: 8, minute: 0 }).toJSDate()}
|
||||
max={DateTime.fromObject({ hour: 16, minute: 0 }).toJSDate()}
|
||||
messages={bigCalendarTranslations}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={closeModal}
|
||||
contentLabel="modal"
|
||||
style={customStyles}
|
||||
>
|
||||
<form
|
||||
className="w-full flex flex-col "
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<h3>Dostępne godziny</h3>
|
||||
<div className="form-control">
|
||||
<label className="label" htmlFor="from">
|
||||
Od
|
||||
</label>
|
||||
<input
|
||||
className="input input-bordered"
|
||||
id="from"
|
||||
type="text"
|
||||
{...register('from', { required: true })}
|
||||
/>
|
||||
<label className="label" htmlFor="to">
|
||||
Do
|
||||
</label>
|
||||
<input
|
||||
className="input input-bordered"
|
||||
id="to"
|
||||
type="text"
|
||||
{...register('to', { required: true })}
|
||||
/>
|
||||
</div>
|
||||
<button className="btn btn-success mt-4">Dodaj dostępność</button>
|
||||
</form>
|
||||
</Modal>
|
||||
<Modal
|
||||
isOpen={isEditModalOpen}
|
||||
onRequestClose={() => setIsEditModalOpen(false)}
|
||||
contentLabel="modal"
|
||||
style={customStyles}
|
||||
>
|
||||
{selectedDate && id ? (
|
||||
<EditSchedule eventData={selectedDate} scheduleId={id} />
|
||||
) : null}
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SupervisorSchedule
|
@ -12,7 +12,7 @@ const SupervisorSchedules = () => {
|
||||
schedules?.data?.examination_schedules.map((schedule) => (
|
||||
<h3 className="text-xl" key={schedule.title}>
|
||||
-{' '}
|
||||
<Link to={`/coordinator/schedule/${schedule.id}`}>
|
||||
<Link to={`/supervisor/schedule/${schedule.id}`}>
|
||||
{schedule.title}
|
||||
</Link>
|
||||
</h3>
|
||||
|
Loading…
Reference in New Issue
Block a user