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 SupervisorSchedules from './views/supervisor/SupervisorSchedules'
|
||||||
import StudentSchedules from './views/student/StudentSchedules'
|
import StudentSchedules from './views/student/StudentSchedules'
|
||||||
import StudentSchedule from './views/student/StudentSchedule'
|
import StudentSchedule from './views/student/StudentSchedule'
|
||||||
|
import SupervisorSchedule from './views/supervisor/SupervisorSchedule'
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
@ -55,6 +56,7 @@ function App() {
|
|||||||
<Route index element={<Navigate to="groups" />} />
|
<Route index element={<Navigate to="groups" />} />
|
||||||
<Route path="groups" element={<Groups />} />
|
<Route path="groups" element={<Groups />} />
|
||||||
<Route path="schedule" element={<SupervisorSchedules />} />
|
<Route path="schedule" element={<SupervisorSchedules />} />
|
||||||
|
<Route path="schedule/:id" element={<SupervisorSchedule />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
@ -14,7 +14,6 @@ export interface Leader {
|
|||||||
email: string
|
email: string
|
||||||
limit_group: number
|
limit_group: number
|
||||||
count_groups: number
|
count_groups: number
|
||||||
mode: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getLeaders = (
|
export const getLeaders = (
|
||||||
@ -32,7 +31,7 @@ export const getLeaders = (
|
|||||||
{ params },
|
{ params },
|
||||||
)
|
)
|
||||||
|
|
||||||
export const createLeader = (payload: Leader) =>
|
export const createLeader = (payload: Partial<Leader>) =>
|
||||||
axiosInstance.post('coordinator/project_supervisor/', payload)
|
axiosInstance.post('coordinator/project_supervisor/', payload)
|
||||||
|
|
||||||
export const deleteLeader = (id: number) =>
|
export const deleteLeader = (id: number) =>
|
||||||
|
@ -1,34 +1,45 @@
|
|||||||
import axiosInstance from './axiosInstance'
|
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) => {
|
export const getTermsOfDefences = (scheduleId: number) => {
|
||||||
return axiosInstance.get<{
|
return axiosInstance.get<{
|
||||||
term_of_defences: {
|
term_of_defences: TermOfDefences[]
|
||||||
id: number
|
|
||||||
start_date: string
|
|
||||||
end_date: string
|
|
||||||
title: string
|
|
||||||
members_of_committee: {
|
|
||||||
members: { first_name: string; last_name: string }[]
|
|
||||||
}
|
|
||||||
group: { name: string }
|
|
||||||
}[]
|
|
||||||
}>(`coordinator/enrollments/${scheduleId}/term-of-defences/`)
|
}>(`coordinator/enrollments/${scheduleId}/term-of-defences/`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getStudentsTermsOfDefences = (scheduleId: number) => {
|
export const getStudentsTermsOfDefences = (
|
||||||
|
scheduleId: number,
|
||||||
|
studentIndex: number,
|
||||||
|
) => {
|
||||||
return axiosInstance.get<{
|
return axiosInstance.get<{
|
||||||
term_of_defences: {
|
term_of_defences: TermOfDefences[]
|
||||||
id: number
|
}>(
|
||||||
start_date: string
|
`students/examination-schedule/${scheduleId}/enrollments?student_index=${studentIndex}`,
|
||||||
end_date: string
|
)
|
||||||
title: string
|
|
||||||
members_of_committee: {
|
|
||||||
members: { first_name: string; last_name: string }[]
|
|
||||||
}
|
|
||||||
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) => {
|
export const getSchedules = (year_group_id: number = 1) => {
|
||||||
return axiosInstance.get<{
|
return axiosInstance.get<{
|
||||||
examination_schedules: {
|
examination_schedules: {
|
||||||
@ -125,3 +136,34 @@ export const downloadSchedule = (scheduleId: number) =>
|
|||||||
responseType: 'blob',
|
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 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 Login = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const [yearGroupId, setYearGroupId] = useLocalStorageState('yearGroupId', {
|
const [yearGroupId, setYearGroupId] = useLocalStorageState('yearGroupId', {
|
||||||
defaultValue: 1,
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -12,7 +73,36 @@ const Login = () => {
|
|||||||
<h1 className="text-xl font-bold mx-auto">System PRI</h1>
|
<h1 className="text-xl font-bold mx-auto">System PRI</h1>
|
||||||
</div>
|
</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="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">
|
<label className="label" htmlFor="login">
|
||||||
Login
|
Login
|
||||||
</label>
|
</label>
|
||||||
@ -23,11 +113,17 @@ const Login = () => {
|
|||||||
Hasło
|
Hasło
|
||||||
</label>
|
</label>
|
||||||
<input className="input input-bordered" id="password" type="text" />
|
<input className="input input-bordered" id="password" type="text" />
|
||||||
</div>
|
</div> */}
|
||||||
<button className="btn mt-5 text-lg">Zaloguj</button>
|
<button
|
||||||
|
className="btn mt-5 text-lg"
|
||||||
|
disabled={!selectedRole || !selectedYear}
|
||||||
|
onClick={() => navigate('/student')}
|
||||||
|
>
|
||||||
|
Zaloguj
|
||||||
|
</button>
|
||||||
<div className="flex flex-col mt-3">
|
<div className="flex flex-col mt-3">
|
||||||
<NavLink to="/coordinator">Koordynator</NavLink>
|
<NavLink to="/coordinator">Koordynator</NavLink>
|
||||||
<NavLink to="/student">Student</NavLink>
|
{/* <NavLink to="/student">Student</NavLink> */}
|
||||||
<NavLink to="/supervisor">Opiekun</NavLink>
|
<NavLink to="/supervisor">Opiekun</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,7 +28,7 @@ const AddGroup = () => {
|
|||||||
|
|
||||||
const { isLoading: areStudentsLoading } = useQuery(
|
const { isLoading: areStudentsLoading } = useQuery(
|
||||||
'students',
|
'students',
|
||||||
() => getStudents({ per_page: 1000 }),
|
() => getStudents({ year_group_id: Number(yearGroupId), per_page: 1000 }),
|
||||||
{
|
{
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
setStudentOptions(
|
setStudentOptions(
|
||||||
@ -46,7 +46,7 @@ const AddGroup = () => {
|
|||||||
)
|
)
|
||||||
const { isLoading: areLeadersLoading } = useQuery(
|
const { isLoading: areLeadersLoading } = useQuery(
|
||||||
'leaders',
|
'leaders',
|
||||||
() => getLeaders({ per_page: 1000 }),
|
() => getLeaders({ year_group_id: Number(yearGroupId), per_page: 1000 }),
|
||||||
{
|
{
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
setSupervisorOptions(
|
setSupervisorOptions(
|
||||||
|
@ -83,7 +83,7 @@ const AddLeader = () => {
|
|||||||
<InputError>Email jest wymagany</InputError>
|
<InputError>Email jest wymagany</InputError>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="form-control">
|
{/* <div className="form-control">
|
||||||
<label className="label" htmlFor="limit_group">
|
<label className="label" htmlFor="limit_group">
|
||||||
Limit grup
|
Limit grup
|
||||||
</label>
|
</label>
|
||||||
@ -99,49 +99,7 @@ const AddLeader = () => {
|
|||||||
{errors.limit_group?.type === 'pattern' && (
|
{errors.limit_group?.type === 'pattern' && (
|
||||||
<InputError>Limit grup musi być liczbą dodatnią</InputError>
|
<InputError>Limit grup musi być liczbą dodatnią</InputError>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
|
||||||
<button className="btn btn-success mt-4">Dodaj opiekuna</button>
|
<button className="btn btn-success mt-4">Dodaj opiekuna</button>
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
|
@ -73,11 +73,11 @@ const EditSchedule = ({
|
|||||||
{DateTime.fromJSDate(eventData.start).toFormat('yyyy-LL-dd HH:mm:ss')}{' '}
|
{DateTime.fromJSDate(eventData.start).toFormat('yyyy-LL-dd HH:mm:ss')}{' '}
|
||||||
- {DateTime.fromJSDate(eventData.end).toFormat('yyyy-LL-dd HH:mm:ss')}
|
- {DateTime.fromJSDate(eventData.end).toFormat('yyyy-LL-dd HH:mm:ss')}
|
||||||
</p>
|
</p>
|
||||||
{eventData.resource.committee.members.length ? (
|
{eventData.resource?.members_of_committee?.length ? (
|
||||||
<>
|
<>
|
||||||
Komisja:{' '}
|
Komisja:{' '}
|
||||||
<ul className="list-disc">
|
<ul className="list-disc">
|
||||||
{eventData.resource.committee.members.map((member: any) => (
|
{eventData.resource.members_of_committee.map((member: any) => (
|
||||||
<li
|
<li
|
||||||
key={`${member.first_name} ${member.last_name}`}
|
key={`${member.first_name} ${member.last_name}`}
|
||||||
className="ml-4"
|
className="ml-4"
|
||||||
|
@ -85,7 +85,7 @@ const Leaders = () => {
|
|||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>Limit grup</th>
|
<th>Limit grup</th>
|
||||||
<th>Liczba grup</th>
|
<th>Liczba grup</th>
|
||||||
<th>Tryb</th>
|
{/* <th>Tryb</th> */}
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -98,7 +98,6 @@ const Leaders = () => {
|
|||||||
email,
|
email,
|
||||||
limit_group,
|
limit_group,
|
||||||
count_groups,
|
count_groups,
|
||||||
mode,
|
|
||||||
}) => (
|
}) => (
|
||||||
<tr key={id}>
|
<tr key={id}>
|
||||||
<td>{first_name}</td>
|
<td>{first_name}</td>
|
||||||
@ -106,13 +105,13 @@ const Leaders = () => {
|
|||||||
<td>{email}</td>
|
<td>{email}</td>
|
||||||
<td>{limit_group}</td>
|
<td>{limit_group}</td>
|
||||||
<td>{count_groups}</td>
|
<td>{count_groups}</td>
|
||||||
<td>
|
{/* <td>
|
||||||
{mode === 0
|
{mode === 0
|
||||||
? 'Stacjonarny'
|
? 'Stacjonarny'
|
||||||
: mode === 1
|
: mode === 1
|
||||||
? 'Niestacjonarny'
|
? 'Niestacjonarny'
|
||||||
: 'Nie/stacjonarny'}
|
: 'Nie/stacjonarny'}
|
||||||
</td>
|
</td> */}
|
||||||
<td>
|
<td>
|
||||||
<button onClick={() => mutateDelete(id)}>
|
<button onClick={() => mutateDelete(id)}>
|
||||||
<IconRemove />
|
<IconRemove />
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { Calendar, luxonLocalizer, Views } from 'react-big-calendar'
|
import { Calendar, luxonLocalizer, Views } from 'react-big-calendar'
|
||||||
import { DateTime, Settings } from 'luxon'
|
import { DateTime, Settings } from 'luxon'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useMutation, useQuery } from 'react-query'
|
import { useMutation, useQuery } from 'react-query'
|
||||||
import {
|
import {
|
||||||
createEvent,
|
createEvent,
|
||||||
downloadSchedule,
|
downloadSchedule,
|
||||||
getTermsOfDefences,
|
getTermsOfDefences,
|
||||||
|
setDateOfExaminationSchedule,
|
||||||
} from '../../api/schedule'
|
} from '../../api/schedule'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import Modal from 'react-modal'
|
import Modal from 'react-modal'
|
||||||
@ -15,6 +16,7 @@ import Select from 'react-select'
|
|||||||
import { getLeaders } from '../../api/leaders'
|
import { getLeaders } from '../../api/leaders'
|
||||||
import useLocalStorageState from 'use-local-storage-state'
|
import useLocalStorageState from 'use-local-storage-state'
|
||||||
import bigCalendarTranslations from '../../utils/bigCalendarTranslations'
|
import bigCalendarTranslations from '../../utils/bigCalendarTranslations'
|
||||||
|
import DatePicker from 'react-date-picker'
|
||||||
|
|
||||||
const customStyles = {
|
const customStyles = {
|
||||||
content: {
|
content: {
|
||||||
@ -66,6 +68,12 @@ const Schedule = () => {
|
|||||||
project_supervisors: NestedValue<any[]>
|
project_supervisors: NestedValue<any[]>
|
||||||
}>({ mode: 'onBlur' })
|
}>({ mode: 'onBlur' })
|
||||||
const [committeeOptions, setCommitteeOptions] = useState<SelectValue[]>([])
|
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(
|
const { isLoading: areLeadersLoading } = useQuery(
|
||||||
'leaders',
|
'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) => {
|
const eventGetter = (event: any) => {
|
||||||
return event?.resource?.group
|
return event?.resource?.group
|
||||||
? {
|
? {
|
||||||
@ -210,6 +242,32 @@ const Schedule = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<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
|
<button
|
||||||
className="btn btn-success btn-xs md:btn-md self-end mb-4"
|
className="btn btn-success btn-xs md:btn-md self-end mb-4"
|
||||||
onClick={() => mutateDownload(Number(id))}
|
onClick={() => mutateDownload(Number(id))}
|
||||||
|
@ -13,6 +13,7 @@ const Schedules = () => {
|
|||||||
mode: 'onBlur',
|
mode: 'onBlur',
|
||||||
})
|
})
|
||||||
const [yearGroupId] = useLocalStorageState('yearGroupId')
|
const [yearGroupId] = useLocalStorageState('yearGroupId')
|
||||||
|
const [scheduleData, setScheduleData] = useLocalStorageState('scheduleData')
|
||||||
const [startDate, setStartDate] = useState(new Date())
|
const [startDate, setStartDate] = useState(new Date())
|
||||||
const [endDate, setEndDate] = useState(new Date())
|
const [endDate, setEndDate] = useState(new Date())
|
||||||
|
|
||||||
@ -59,13 +60,21 @@ const Schedules = () => {
|
|||||||
Od
|
Od
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<DatePicker onChange={setStartDate} value={startDate} />
|
<DatePicker
|
||||||
|
onChange={setStartDate}
|
||||||
|
value={startDate}
|
||||||
|
format={'yyyy-MM-dd'}
|
||||||
|
/>
|
||||||
|
|
||||||
<label className="label" htmlFor="end_date">
|
<label className="label" htmlFor="end_date">
|
||||||
Do
|
Do
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<DatePicker onChange={setEndDate} value={endDate} />
|
<DatePicker
|
||||||
|
onChange={setEndDate}
|
||||||
|
value={endDate}
|
||||||
|
format={'yyyy-MM-dd'}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-success mt-4">Stwórz zapisy</button>
|
<button className="btn btn-success mt-4">Stwórz zapisy</button>
|
||||||
</form>
|
</form>
|
||||||
@ -74,9 +83,15 @@ const Schedules = () => {
|
|||||||
schedules?.data?.examination_schedules.map((schedule) => (
|
schedules?.data?.examination_schedules.map((schedule) => (
|
||||||
<h3 className="text-xl " key={schedule.title}>
|
<h3 className="text-xl " key={schedule.title}>
|
||||||
-{' '}
|
-{' '}
|
||||||
<Link to={`/coordinator/schedule/${schedule.id}`}>
|
<Link
|
||||||
|
to={`/coordinator/schedule/${schedule.id}`}
|
||||||
|
onClick={() => {
|
||||||
|
setScheduleData(schedule)
|
||||||
|
}}
|
||||||
|
>
|
||||||
{schedule.title}
|
{schedule.title}
|
||||||
</Link>
|
</Link>
|
||||||
|
1
|
||||||
</h3>
|
</h3>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,7 @@ import { DateTime } from 'luxon'
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
import { useMutation } from 'react-query'
|
import { useMutation } from 'react-query'
|
||||||
|
import useLocalStorageState from 'use-local-storage-state'
|
||||||
import { assignGroup } from '../../api/schedule'
|
import { assignGroup } from '../../api/schedule'
|
||||||
|
|
||||||
const ScheduleAddGroup = ({
|
const ScheduleAddGroup = ({
|
||||||
@ -20,6 +21,7 @@ const ScheduleAddGroup = ({
|
|||||||
const { register, handleSubmit, reset, control } = useForm<{
|
const { register, handleSubmit, reset, control } = useForm<{
|
||||||
student_index: number
|
student_index: number
|
||||||
}>({ mode: 'onBlur' })
|
}>({ mode: 'onBlur' })
|
||||||
|
const [studentId] = useLocalStorageState('studentId')
|
||||||
|
|
||||||
const { mutate: mutateAssignGroup } = useMutation(
|
const { mutate: mutateAssignGroup } = useMutation(
|
||||||
['assignGroup'],
|
['assignGroup'],
|
||||||
@ -34,7 +36,7 @@ const ScheduleAddGroup = ({
|
|||||||
mutateAssignGroup({
|
mutateAssignGroup({
|
||||||
scheduleId: Number(scheduleId),
|
scheduleId: Number(scheduleId),
|
||||||
enrollmentId: eventData.id,
|
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.start).toFormat('yyyy-LL-dd HH:mm:ss')}{' '}
|
||||||
- {DateTime.fromJSDate(eventData.end).toFormat('yyyy-LL-dd HH:mm:ss')}
|
- {DateTime.fromJSDate(eventData.end).toFormat('yyyy-LL-dd HH:mm:ss')}
|
||||||
</h3>
|
</h3>
|
||||||
{eventData.resource.committee.members.length > 0 && (
|
{eventData.resource?.members_of_committee?.length > 0 && (
|
||||||
<>
|
<>
|
||||||
Komisja:{' '}
|
Komisja:{' '}
|
||||||
<ul className="list-disc">
|
<ul className="list-disc">
|
||||||
{eventData.resource.committee.members.map((member: any) => (
|
{eventData.resource.members_of_committee.map((member: any) => (
|
||||||
<li
|
<li
|
||||||
key={`${member.first_name} ${member.last_name}`}
|
key={`${member.first_name} ${member.last_name}`}
|
||||||
className="ml-4"
|
className="ml-4"
|
||||||
@ -66,17 +68,6 @@ const ScheduleAddGroup = ({
|
|||||||
)}
|
)}
|
||||||
{!eventData.resource.group && (
|
{!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>
|
<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 { useForm } from 'react-hook-form'
|
||||||
import ScheduleAddGroup from './ScheduleAddGroup'
|
import ScheduleAddGroup from './ScheduleAddGroup'
|
||||||
import bigCalendarTranslations from '../../utils/bigCalendarTranslations'
|
import bigCalendarTranslations from '../../utils/bigCalendarTranslations'
|
||||||
|
import useLocalStorageState from 'use-local-storage-state'
|
||||||
|
|
||||||
const customStyles = {
|
const customStyles = {
|
||||||
content: {
|
content: {
|
||||||
@ -24,6 +25,7 @@ const StudentSchedule = () => {
|
|||||||
Settings.defaultZone = DateTime.local().zoneName
|
Settings.defaultZone = DateTime.local().zoneName
|
||||||
Settings.defaultLocale = 'pl'
|
Settings.defaultLocale = 'pl'
|
||||||
|
|
||||||
|
const [studentId] = useLocalStorageState('studentId')
|
||||||
const { id } = useParams<{ id: string }>()
|
const { id } = useParams<{ id: string }>()
|
||||||
const [events, setEvents] = useState<
|
const [events, setEvents] = useState<
|
||||||
{
|
{
|
||||||
@ -55,7 +57,7 @@ const StudentSchedule = () => {
|
|||||||
|
|
||||||
const { refetch } = useQuery(
|
const { refetch } = useQuery(
|
||||||
['studentSchedules'],
|
['studentSchedules'],
|
||||||
() => getStudentsTermsOfDefences(Number(id)),
|
() => getStudentsTermsOfDefences(Number(id), Number(studentId)),
|
||||||
{
|
{
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
setEvents(
|
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) => (
|
schedules?.data?.examination_schedules.map((schedule) => (
|
||||||
<h3 className="text-xl" key={schedule.title}>
|
<h3 className="text-xl" key={schedule.title}>
|
||||||
-{' '}
|
-{' '}
|
||||||
<Link to={`/coordinator/schedule/${schedule.id}`}>
|
<Link to={`/supervisor/schedule/${schedule.id}`}>
|
||||||
{schedule.title}
|
{schedule.title}
|
||||||
</Link>
|
</Link>
|
||||||
</h3>
|
</h3>
|
||||||
|
Loading…
Reference in New Issue
Block a user