Fixes after tests

This commit is contained in:
adam-skowronek 2023-01-18 00:35:58 +01:00
parent 339ba42fb4
commit 2029f6c343
20 changed files with 1797 additions and 96 deletions

View File

@ -142,6 +142,8 @@ def list_term_of_defences(examination_schedule_id: int, data: dict) -> dict:
if student is None: if student is None:
abort(404, "Not found student!") abort(404, "Not found student!")
################ ################
show_available = data.get('show_available')
term_of_defences = ( term_of_defences = (
TermOfDefence.query.filter( TermOfDefence.query.filter(
TermOfDefence.examination_schedule_id == examination_schedule_id TermOfDefence.examination_schedule_id == examination_schedule_id
@ -150,18 +152,19 @@ def list_term_of_defences(examination_schedule_id: int, data: dict) -> dict:
.all() .all()
) )
term_of_defences = list( if show_available is True:
filter( term_of_defences = list(
lambda n: len( filter(
[ lambda n: len(
d.id [
for d in n.members_of_committee d.id
if d.id == student.groups[0].project_supervisor.id for d in n.members_of_committee
] if len(student.groups) > 0 and d.id == student.groups[0].project_supervisor.id
]
)
> 0,
term_of_defences,
) )
> 0,
term_of_defences,
) )
)
return {"term_of_defences": term_of_defences} return {"term_of_defences": term_of_defences}

View File

@ -24,6 +24,7 @@ class ProjectSupervisorQuerySchema(Schema):
class TemporaryStudentSchema(Schema): class TemporaryStudentSchema(Schema):
student_id = fields.Integer(required=True) student_id = fields.Integer(required=True)
show_available = fields.Boolean(required=False)
class ExaminationScheduleStudentSchema(Schema): class ExaminationScheduleStudentSchema(Schema):

View File

@ -31,6 +31,8 @@ import WorkloadStatistics from './views/coordinator/WorkloadStatistics'
import { MainContext } from './context/MainContext' import { MainContext } from './context/MainContext'
import WithAxios from './context/WithAxios' import WithAxios from './context/WithAxios'
import BaseModal, { ModalData } from './components/BaseModal' import BaseModal, { ModalData } from './components/BaseModal'
import StudentGroup from './views/student/StudentGroup'
import StudentGradeCard from './views/student/StudentGradeCard'
require('dayjs/locale/pl') require('dayjs/locale/pl')
dayjs.locale('pl') dayjs.locale('pl')
@ -85,6 +87,11 @@ function App() {
<Route path="enrollment" element={<Enrollment />} /> <Route path="enrollment" element={<Enrollment />} />
<Route path="schedule" element={<StudentSchedules />} /> <Route path="schedule" element={<StudentSchedules />} />
<Route path="schedule/:id" element={<StudentSchedule />} /> <Route path="schedule/:id" element={<StudentSchedule />} />
<Route path="groups/:id" element={<StudentGroup />} />
<Route
path="groups/:id/grade-card"
element={<StudentGradeCard />}
/>
</Route> </Route>
<Route path="supervisor" element={<Supervisor />}> <Route path="supervisor" element={<Supervisor />}>
<Route index element={<Navigate to="groups" />} /> <Route index element={<Navigate to="groups" />} />

View File

@ -31,3 +31,13 @@ export const getGradesForSupervisor = (
`project_supervisor/project-grade-sheet/group/${groupId}/?term=${term}&id=${supervisorId}`, `project_supervisor/project-grade-sheet/group/${groupId}/?term=${term}&id=${supervisorId}`,
) )
} }
export const getGradesForStudent = (
yearGroupId: number,
term: number,
studentId: number,
) => {
return axiosInstance.get<{ [key: string]: any }>(
`students/project-grade-sheet/year-group/${yearGroupId}/?term=${term}&student_id=${studentId}`,
)
}

View File

@ -31,24 +31,33 @@ export const getTermsOfDefencesWithGroups = (scheduleId: number) => {
export const getStudentsTermsOfDefences = ( export const getStudentsTermsOfDefences = (
scheduleId: number, scheduleId: number,
studentId: number, studentId: number,
showAvailable: boolean,
) => { ) => {
return axiosInstance.get<{ return axiosInstance.get<{
term_of_defences: TermOfDefences[] term_of_defences: TermOfDefences[]
}>( }>(
`students/examination-schedule/${scheduleId}/enrollments?student_id=${studentId}`, `students/examination-schedule/${scheduleId}/enrollments?student_id=${studentId}&show_available=${showAvailable}`,
) )
} }
export const getSupervisorTermsOfDefences = (scheduleId: number) => { export const getSupervisorTermsOfDefences = (
scheduleId: number,
supervisorId: number,
) => {
return axiosInstance.get<{ return axiosInstance.get<{
term_of_defences: TermOfDefences[] term_of_defences: TermOfDefences[]
}>(`project_supervisor/${scheduleId}/term-of-defences?id=1`) //fix hardcode id }>(`project_supervisor/${scheduleId}/term-of-defences?id=${supervisorId}`)
} }
export const getAvailabilityForSupervisor = (scheduleId: number) => { export const getAvailabilityForSupervisor = (
scheduleId: number,
supervisorId: number,
) => {
return axiosInstance.get<{ return axiosInstance.get<{
free_times: { id: number; start_date: string; end_date: string }[] free_times: { id: number; start_date: string; end_date: string }[]
}>(`project_supervisor/${scheduleId}/temporary-availabilities?id=1`) //fix hardcode id }>(
`project_supervisor/${scheduleId}/temporary-availabilities?id=${supervisorId}`,
)
} }
export const getAvailabilityForCoordinator = (scheduleId: number) => { export const getAvailabilityForCoordinator = (scheduleId: number) => {
@ -221,3 +230,18 @@ export const geWorkloadStatistics = (scheduleId: number) => {
}[] }[]
}>(`coordinator/examination_schedule/${scheduleId}/workloads/`) }>(`coordinator/examination_schedule/${scheduleId}/workloads/`)
} }
export const supervisorDeleteAvailability = (
scheduleId: number,
availabilityId: number,
supervisorId: number,
) => {
return axiosInstance.delete(
`project_supervisor/${scheduleId}/enrollments/${availabilityId}/`,
{
data: {
id: supervisorId,
},
},
)
}

View File

@ -54,3 +54,6 @@ export const downloadStudents = (mode: boolean, year_group_id: number) =>
responseType: 'blob', responseType: 'blob',
}, },
) )
export const getStudentDetails = (id: number) =>
axiosInstance.get<Student>(`coordinator/students/${id}/detail/`)

View File

@ -0,0 +1,40 @@
import Modal from 'react-modal'
const customStyles = {
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
},
}
const ConfirmationModal = ({
isOpen,
close,
confirmFn,
}: {
isOpen: boolean
close: () => void
confirmFn: () => void
}) => {
return (
<Modal
isOpen={isOpen}
onRequestClose={close}
contentLabel="modal"
style={customStyles}
>
<div className="flex flex-col justify-center">
<p>Czy na pewno chcesz usunąć?</p>
<button className="btn btn-error mt-4" onClick={confirmFn}>
Usuń
</button>
</div>
</Modal>
)
}
export default ConfirmationModal

View File

@ -16,44 +16,77 @@ const GradeCard = () => {
const { handleSubmit, setValue, control, watch, getValues } = useForm() const { handleSubmit, setValue, control, watch, getValues } = useForm()
const { id } = useParams<{ id: string }>() const { id } = useParams<{ id: string }>()
const [supervisorId] = useLocalStorageState('userId') const [supervisorId] = useLocalStorageState('userId')
const [userType] = useLocalStorageState('userType')
const {
data: groupDetails,
refetch: refetchGroup,
isLoading: isGroupLoading,
isSuccess,
} = useQuery(['getGroup', id], () => getGroup(Number(id)))
useQuery( useQuery(
['getGradesFirst'], ['getGradesFirst'],
() => getGradesForSupervisor(Number(id), 1, Number(supervisorId)), () =>
getGradesForSupervisor(
Number(id),
1,
userType === 'coordinator'
? Number(groupDetails?.data?.project_supervisor?.id)
: Number(supervisorId),
),
{ {
onSuccess: (data) => { onSuccess: (data) => {
for (const [key, value] of Object.entries(data.data)) { for (const [key, value] of Object.entries(data.data)) {
if (key !== 'id') setValue(key, value) if (key !== 'id') setValue(key, value)
} }
}, },
enabled: isSuccess,
}, },
) )
useQuery( useQuery(
['getGradesSecond'], ['getGradesSecond'],
() => getGradesForSupervisor(Number(id), 2, Number(supervisorId)), () =>
getGradesForSupervisor(
Number(id),
2,
userType === 'coordinator'
? Number(groupDetails?.data?.project_supervisor?.id)
: Number(supervisorId),
),
{ {
onSuccess: (data) => { onSuccess: (data) => {
for (const [key, value] of Object.entries(data.data)) { for (const [key, value] of Object.entries(data.data)) {
if (key !== 'id') setValue(key, value) if (key !== 'id') setValue(key, value)
} }
}, },
enabled: isSuccess,
}, },
) )
const {
data: groupDetails,
refetch: refetchGroup,
isLoading: isGroupLoading,
} = useQuery(['getGroup', id], () => getGroup(Number(id)))
const { mutateAsync: mutateGradesFirstTerm } = useMutation( const { mutateAsync: mutateGradesFirstTerm } = useMutation(
'update_grades_first', 'update_grades_first',
(payload: any) => updateGradesFirstTerm(1, 1, payload), (payload: any) =>
updateGradesFirstTerm(
Number(id),
userType === 'coordinator'
? Number(groupDetails?.data?.project_supervisor?.id)
: Number(supervisorId),
payload,
),
) )
const { mutateAsync: mutateGradesSecondTerm } = useMutation( const { mutateAsync: mutateGradesSecondTerm } = useMutation(
'update_grades_second', 'update_grades_second',
(payload: any) => updateGradesSecondTerm(1, 1, payload), (payload: any) =>
updateGradesSecondTerm(
Number(id),
userType === 'coordinator'
? Number(groupDetails?.data?.project_supervisor?.id)
: Number(supervisorId),
payload,
),
) )
const onSubmit = async (data: any) => { const onSubmit = async (data: any) => {

View File

@ -37,7 +37,7 @@ const SupervisorSchedule = () => {
({ id, start_date, end_date, project_supervisor }) => { ({ id, start_date, end_date, project_supervisor }) => {
return { return {
id, id,
title: `${project_supervisor.first_name} ${project_supervisor.last_name}`, title: `${project_supervisor.first_name[0]}${project_supervisor.last_name[0]}`,
start: new Date(start_date), start: new Date(start_date),
end: new Date(end_date), end: new Date(end_date),
resource: {}, resource: {},

View File

@ -6,6 +6,7 @@ import useLocalStorageState from 'use-local-storage-state'
import { deleteGroup, getGroups } from '../../api/groups' import { deleteGroup, getGroups } from '../../api/groups'
import { ReactComponent as IconRemove } from '../../assets/svg/icon-remove.svg' import { ReactComponent as IconRemove } from '../../assets/svg/icon-remove.svg'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import ConfirmationModal from '../../components/ConfirmationModal'
const Groups = () => { const Groups = () => {
let navigate = useNavigate() let navigate = useNavigate()
@ -14,6 +15,8 @@ const Groups = () => {
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [perPage, setPerPage] = useState(10) const [perPage, setPerPage] = useState(10)
const [yearGroupId] = useLocalStorageState('yearGroupId') const [yearGroupId] = useLocalStorageState('yearGroupId')
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false)
const [groupToDeleteId, setGroupToDeleteId] = useState(0)
const perPageOptions = [ const perPageOptions = [
{ {
@ -135,7 +138,12 @@ const Groups = () => {
KARTA OCENY KARTA OCENY
</button> </button>
</Link> </Link>
<button onClick={() => mutateDelete(id)}> <button
onClick={() => {
setGroupToDeleteId(id)
setIsConfirmModalOpen(true)
}}
>
<IconRemove /> <IconRemove />
</button> </button>
</div> </div>
@ -181,6 +189,14 @@ const Groups = () => {
</button> </button>
</div> </div>
</div> </div>
<ConfirmationModal
isOpen={isConfirmModalOpen}
close={() => setIsConfirmModalOpen(false)}
confirmFn={() => {
mutateDelete(groupToDeleteId)
setIsConfirmModalOpen(false)
}}
/>
</div> </div>
) )
} }

View File

@ -6,6 +6,7 @@ import useLocalStorageState from 'use-local-storage-state'
import { CreateGroup, editGroup, getGroup } from '../../api/groups' import { CreateGroup, editGroup, getGroup } from '../../api/groups'
import { getStudents } from '../../api/students' import { getStudents } from '../../api/students'
import { ReactComponent as IconRemove } from '../../assets/svg/icon-remove.svg' import { ReactComponent as IconRemove } from '../../assets/svg/icon-remove.svg'
import ConfirmationModal from '../../components/ConfirmationModal'
type SelectValue = { type SelectValue = {
value: string | number value: string | number
@ -44,6 +45,8 @@ const Group = () => {
}, },
) )
const [newStudent, setNewStudent] = useState(0) const [newStudent, setNewStudent] = useState(0)
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false)
const [studentsToDelete, setStudentsToDelete] = useState<any[]>([])
const { mutate: mutateEditGroup } = useMutation( const { mutate: mutateEditGroup } = useMutation(
['editGroup'], ['editGroup'],
(data: { id: number; payload: CreateGroup }) => (data: { id: number; payload: CreateGroup }) =>
@ -96,17 +99,14 @@ const Group = () => {
<td> <td>
<button <button
onClick={() => { onClick={() => {
if (students && name && project_supervisor) if (students && name && project_supervisor) {
mutateEditGroup({ setStudentsToDelete(
id: Number(groupId), students
payload: { .map((student) => student.id)
name, .filter((i) => id !== i),
project_supervisor_id: project_supervisor.id, )
students: students setIsConfirmModalOpen(true)
.map((student) => student.id) }
.filter((i) => id !== i),
},
})
}} }}
> >
<IconRemove /> <IconRemove />
@ -154,6 +154,23 @@ const Group = () => {
Dodaj Dodaj
</button> </button>
</div> </div>
<ConfirmationModal
isOpen={isConfirmModalOpen}
close={() => setIsConfirmModalOpen(false)}
confirmFn={() => {
if (name && project_supervisor) {
mutateEditGroup({
id: Number(groupId),
payload: {
name,
project_supervisor_id: project_supervisor.id,
students: studentsToDelete,
},
})
setIsConfirmModalOpen(false)
}
}}
/>
</div> </div>
) )
} }

View File

@ -283,7 +283,7 @@ const Schedule = () => {
ROZPOCZNIJ ROZPOCZNIJ
</button> </button>
<button <button
className="btn btn-success ml-2" className="btn ml-2"
onClick={() => { onClick={() => {
mutateCloseEnrollments() mutateCloseEnrollments()
}} }}

View File

@ -10,6 +10,7 @@ import {
import classNames from 'classnames' import classNames from 'classnames'
import { ReactComponent as IconRemove } from '../../assets/svg/icon-remove.svg' import { ReactComponent as IconRemove } from '../../assets/svg/icon-remove.svg'
import useLocalStorageState from 'use-local-storage-state' import useLocalStorageState from 'use-local-storage-state'
import ConfirmationModal from '../../components/ConfirmationModal'
const Students = () => { const Students = () => {
let navigate = useNavigate() let navigate = useNavigate()
@ -38,6 +39,9 @@ const Students = () => {
}, },
] ]
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false)
const [studentToDeleteId, setStudenToDeleteId] = useState(0)
const { const {
isLoading: isStudentsLoading, isLoading: isStudentsLoading,
data: students, data: students,
@ -106,8 +110,9 @@ const Students = () => {
<label <label
className="btn btn-success btn-xs md:btn-md btn-outline" className="btn btn-success btn-xs md:btn-md btn-outline"
htmlFor="file" htmlFor="file"
title="Format: NAZWISKO, IMIE, INDEKS, PESEL, EMAIL"
> >
Importuj Importuj CSV
</label> </label>
<input <input
type="file" type="file"
@ -132,24 +137,6 @@ const Students = () => {
</option> </option>
))} ))}
</select> </select>
{/* <label className="label justify-start gap-2">
<input
type="radio"
className="radio radio-xs md:radio-md"
onChange={() => setMode(true)}
checked={mode}
/>
<span className="text-xs md:text-base">Stacjonarni</span>
</label>
<label className="label justify-start gap-2">
<input
type="radio"
className="radio radio-xs md:radio-md"
onChange={() => setMode(false)}
checked={!mode}
/>
<span className="text-xs md:text-base">Niestacjonarni</span>
</label> */}
<label className="label"> <label className="label">
<input <input
type="checkbox" type="checkbox"
@ -185,7 +172,12 @@ const Students = () => {
<td>{index}</td> <td>{index}</td>
<td>{groups?.length ? 'Tak' : 'Nie'}</td> <td>{groups?.length ? 'Tak' : 'Nie'}</td>
<td> <td>
<button onClick={() => mutateDelete(id)}> <button
onClick={() => {
setStudenToDeleteId(id)
setIsConfirmModalOpen(true)
}}
>
<IconRemove /> <IconRemove />
</button> </button>
</td> </td>
@ -228,6 +220,14 @@ const Students = () => {
» »
</button> </button>
</div> </div>
<ConfirmationModal
isOpen={isConfirmModalOpen}
close={() => setIsConfirmModalOpen(false)}
confirmFn={() => {
mutateDelete(studentToDeleteId)
setIsConfirmModalOpen(false)
}}
/>
</div> </div>
</> </>
)} )}

View File

@ -62,7 +62,6 @@ const Enrollment = () => {
<th>Imię</th> <th>Imię</th>
<th>Nazwisko</th> <th>Nazwisko</th>
<th>Email</th> <th>Email</th>
<th>Tryb</th>
<th>Wolne miejsca</th> <th>Wolne miejsca</th>
</tr> </tr>
</thead> </thead>
@ -76,7 +75,6 @@ const Enrollment = () => {
<td>{first_name}</td> <td>{first_name}</td>
<td>{last_name}</td> <td>{last_name}</td>
<td>{email}</td> <td>{email}</td>
<td>{mode ? 'Stacjonarny' : 'Niestacjonarny'}</td>
<td>{available_groups}</td> <td>{available_groups}</td>
</tr> </tr>
))} ))}

View File

@ -1,16 +1,46 @@
import { Outlet } from 'react-router-dom' import { useState } from 'react'
import { useQuery } from 'react-query'
import { Outlet, useNavigate } from 'react-router-dom'
import useLocalStorageState from 'use-local-storage-state'
import { getStudentDetails } from '../../api/students'
import TopBar from '../../components/TopBar' import TopBar from '../../components/TopBar'
const Student = () => { const Student = () => {
const [studentId] = useLocalStorageState('userId')
const [routes, setRoutes] = useState<
{
name: string
path: string
}[]
>([{ name: 'Zapisy', path: '/student/enrollment' }])
let navigate = useNavigate()
useQuery(
['getStudent', studentId],
() => getStudentDetails(Number(studentId)),
{
onSuccess: (data) => {
if (data?.data.groups?.length) {
navigate(`/student/groups/${data?.data.groups[0].id}`)
}
setRoutes(
data?.data.groups?.length
? [
{
name: 'Grupa',
path: `/student/groups/${data?.data.groups[0].id}`,
},
{ name: 'Harmonogram', path: '/student/schedule' },
]
: [{ name: 'Zapisy', path: '/student/enrollment' }],
)
},
},
)
return ( return (
<> <>
<TopBar <TopBar routes={routes} color="bg-gray-300" />
routes={[
{ name: 'Zapisy', path: '/student/enrollment' },
{ name: 'Harmonogram', path: '/student/schedule' },
]}
color="bg-gray-300"
/>
<div className="m-10"> <div className="m-10">
<Outlet /> <Outlet />
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,85 @@
import { useState } from 'react'
import { useQuery } from 'react-query'
import { Link, useLocation, useParams } from 'react-router-dom'
import ReactSelect from 'react-select'
import useLocalStorageState from 'use-local-storage-state'
import { getGroup } from '../../api/groups'
import { getStudents } from '../../api/students'
import { ReactComponent as IconRemove } from '../../assets/svg/icon-remove.svg'
type SelectValue = {
value: string | number
label: string
}
const StudentGroup = () => {
const { id: groupId } = useParams<{ id: string }>()
const location = useLocation()
const {
data: groups,
refetch,
isLoading: isGroupLoading,
} = useQuery(['getGroup', groupId], () => getGroup(Number(groupId)))
const { name, project_supervisor, students } = groups?.data || {}
const [yearGroupId] = useLocalStorageState('yearGroupId')
const [studentOptions, setStudentOptions] = useState<SelectValue[]>([])
const { isLoading: areStudentsLoading, refetch: refetchStudents } = useQuery(
'students',
() => getStudents({ year_group_id: Number(yearGroupId), per_page: 1000 }),
{
onSuccess: (data) => {
setStudentOptions(
data?.data.students
.filter(({ groups }) => !groups?.length)
.map(({ first_name, last_name, index, id }) => {
return {
value: id,
label: `${first_name} ${last_name} (${index})`,
}
}),
)
},
},
)
if (isGroupLoading || areStudentsLoading) {
return <div>Ładowanie</div>
}
return (
<div>
<h1 className="font-bold text-xl">{name}</h1>
<h2 className="">
Opiekun: {project_supervisor?.first_name}{' '}
{project_supervisor?.last_name}
</h2>
<Link to={`/student/groups/${groupId}/grade-card`}>
<button className="btn btn-success mt-2">KARTA OCENY</button>
</Link>
<div className="flex w-[600px] mt-5 overflow-hidden overflow-x-auto border border-gray-100 rounded">
<table className="min-w-full table table-compact">
<thead>
<tr className="bg-gray-50">
<th>Imię</th>
<th>Nazwisko</th>
<th>Indeks</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{students?.map(({ first_name, last_name, index, id }) => (
<tr key={index}>
<td>{first_name}</td>
<td>{last_name}</td>
<td>{index}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}
export default StudentGroup

View File

@ -42,6 +42,7 @@ const StudentSchedule = () => {
id: number id: number
resource: any resource: any
}>() }>()
const [showAvailable, setShowAvailable] = useState(false)
const [view, setView] = useState(Views.WEEK) const [view, setView] = useState(Views.WEEK)
const onView = useCallback((newView: any) => setView(newView), [setView]) const onView = useCallback((newView: any) => setView(newView), [setView])
@ -55,23 +56,35 @@ const StudentSchedule = () => {
}>({ mode: 'onBlur' }) }>({ mode: 'onBlur' })
const { refetch } = useQuery( const { refetch } = useQuery(
['studentSchedules'], ['studentSchedules', showAvailable],
() => getStudentsTermsOfDefences(Number(id), Number(studentId)), () =>
getStudentsTermsOfDefences(Number(id), Number(studentId), showAvailable),
{ {
onSuccess: (data) => { onSuccess: (data) => {
setEvents( setEvents(
data.data.term_of_defences.map( data.data.term_of_defences.map(
({ ({ id, start_date, end_date, members_of_committee, group }) => {
id, let initials = ''
start_date, let title = ''
end_date, members_of_committee.forEach((member) => {
title = 'Obrona', initials +=
members_of_committee, `${member.first_name} ${member.last_name}`
group, .split(' ')
}) => { .map((n) => n[0])
.join('') + ' '
})
if (group?.name && initials) {
title = `${group.name} | ${initials}`
} else if (group?.name) {
title = group.name
} else if (initials) {
title = `- | ${initials}`
} else {
title = '-'
}
return { return {
id, id,
title: `${group?.name ?? '-'}`, title: title,
start: new Date(start_date), start: new Date(start_date),
end: new Date(end_date), end: new Date(end_date),
resource: { resource: {
@ -98,7 +111,6 @@ const StudentSchedule = () => {
end: Date end: Date
resource: any resource: any
}) => { }) => {
console.log(event)
setSelectedDate(event) setSelectedDate(event)
setIsEditModalOpen(true) setIsEditModalOpen(true)
}, },
@ -129,11 +141,23 @@ const StudentSchedule = () => {
<h1 className="text-2xl font-bold mb-2 text-center"> <h1 className="text-2xl font-bold mb-2 text-center">
Wybierz i zatwierdź termin obrony dla swojej grupy Wybierz i zatwierdź termin obrony dla swojej grupy
</h1> </h1>
<div className="flex justify-end">
<label className="label">
<input
type="checkbox"
className="checkbox checkbox-xs md:checkbox-md"
onChange={() => setShowAvailable(!showAvailable)}
/>
<span className="ml-2 text-xs md:text-base">
Pokaż termin z moim opiekunem
</span>
</label>
</div>
<Calendar <Calendar
localizer={localizer} localizer={localizer}
startAccessor="start" startAccessor="start"
endAccessor="end" endAccessor="end"
style={{ height: '85vh' }} style={{ height: '85vh', fontSize: '14px' }}
onSelectEvent={handleSelectEvent} onSelectEvent={handleSelectEvent}
onSelectSlot={handleSelectSlot} onSelectSlot={handleSelectSlot}
events={events} events={events}

View File

@ -91,7 +91,7 @@ const SupervisorGroups = () => {
<tr className="bg-gray-50"> <tr className="bg-gray-50">
<th>Nazwa</th> <th>Nazwa</th>
<th>Opiekun</th> <th>Opiekun</th>
<th>Semestr 1</th> <th>Punkty Semestr 1</th>
<th>Semestr 2</th> <th>Semestr 2</th>
<th></th> <th></th>
</tr> </tr>
@ -125,8 +125,8 @@ const SupervisorGroups = () => {
<td> <td>
{`${project_supervisor.first_name} ${project_supervisor.last_name}`} {`${project_supervisor.first_name} ${project_supervisor.last_name}`}
</td> </td>
<td>{points_for_first_term}</td> <td>{points_for_first_term}%</td>
<td>{points_for_second_term}</td> <td>{points_for_second_term}%</td>
<td> <td>
<div className="flex align-center gap-2"> <div className="flex align-center gap-2">
<Link <Link

View File

@ -1,10 +1,11 @@
import { Calendar, Views } from 'react-big-calendar' import { Calendar, View, Views } from 'react-big-calendar'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { useMutation, useQuery } from 'react-query' import { useMutation, useQuery } from 'react-query'
import { import {
addAvailability, addAvailability,
getAvailabilityForSupervisor, getAvailabilityForSupervisor,
getSupervisorTermsOfDefences, getSupervisorTermsOfDefences,
supervisorDeleteAvailability,
} 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'
@ -23,6 +24,8 @@ const customStyles = {
bottom: 'auto', bottom: 'auto',
marginRight: '-50%', marginRight: '-50%',
transform: 'translate(-50%, -50%)', transform: 'translate(-50%, -50%)',
width: '300px',
height: 'auto',
}, },
} }
type SelectValue = { type SelectValue = {
@ -51,10 +54,11 @@ const SupervisorSchedule = () => {
id: number id: number
resource: any resource: any
}>() }>()
const [view, setView] = useState(Views.MONTH) const [view, setView] = useState<View>(Views.WEEK)
const onView = useCallback((newView: any) => setView(newView), [setView]) const onView = useCallback((newView: any) => setView(newView), [setView])
const [isModalOpen, setIsModalOpen] = useState(false) const [isModalOpen, setIsModalOpen] = useState(false)
const [isWeekModalOpen, setIsWeekModalOpen] = useState(false)
const [isEditModalOpen, setIsEditModalOpen] = useState(false) const [isEditModalOpen, setIsEditModalOpen] = useState(false)
Modal.setAppElement('#root') Modal.setAppElement('#root')
@ -65,7 +69,7 @@ const SupervisorSchedule = () => {
const { refetch, isFetching } = useQuery( const { refetch, isFetching } = useQuery(
['schedules'], ['schedules'],
() => getSupervisorTermsOfDefences(Number(id)), () => getSupervisorTermsOfDefences(Number(id), Number(supervisorId)),
{ {
onSuccess: (data) => { onSuccess: (data) => {
setEvents( setEvents(
@ -96,7 +100,7 @@ const SupervisorSchedule = () => {
const { refetch: refetchAvailability } = useQuery( const { refetch: refetchAvailability } = useQuery(
['availability'], ['availability'],
() => getAvailabilityForSupervisor(Number(id)), () => getAvailabilityForSupervisor(Number(id), Number(supervisorId)),
{ {
onSuccess: (data) => { onSuccess: (data) => {
setEvents([ setEvents([
@ -125,9 +129,32 @@ const SupervisorSchedule = () => {
}) => addAvailability(data), }) => addAvailability(data),
) )
const { mutate: mutateDeleteAvailability } = useMutation(
['deleteAvailability'],
(data: {
availabilityId: number
scheduleId: number
supervisorId: number
}) =>
supervisorDeleteAvailability(
data.scheduleId,
data.availabilityId,
data.supervisorId,
),
{
onSuccess: async () => {
setIsEditModalOpen(false)
await refetch()
await refetchAvailability()
},
},
)
const handleSelectSlot = async (event: any) => { const handleSelectSlot = async (event: any) => {
setSelectedDate(event) setSelectedDate(event)
if (view === Views.MONTH) { if (view === Views.WEEK || view === Views.DAY) {
setIsWeekModalOpen(true)
} else if (view === Views.MONTH) {
setIsModalOpen(true) setIsModalOpen(true)
} }
} }
@ -149,6 +176,7 @@ const SupervisorSchedule = () => {
function closeModal() { function closeModal() {
setIsModalOpen(false) setIsModalOpen(false)
} }
const onSubmit = async (data: any) => { const onSubmit = async (data: any) => {
if (selectedDate && view === Views.MONTH) { if (selectedDate && view === Views.MONTH) {
const from = data.from.split(':') const from = data.from.split(':')
@ -166,12 +194,26 @@ const SupervisorSchedule = () => {
project_supervisor_id: Number(supervisorId), project_supervisor_id: Number(supervisorId),
}) })
setEvents([]) setEvents([])
refetch() await refetch()
refetchAvailability() await refetchAvailability()
reset() reset()
closeModal() closeModal()
} }
} }
const submitSlot = async () => {
if (selectedDate && (view === Views.WEEK || view === Views.DAY)) {
await mutateAddAvailability({
start_date: dayjs(selectedDate.start).format('YYYY-MM-DD HH:mm:ss'),
end_date: dayjs(selectedDate.end).format('YYYY-MM-DD HH:mm:ss'),
scheduleId: Number(id),
project_supervisor_id: Number(supervisorId),
})
setEvents([])
await refetch()
await refetchAvailability()
setIsWeekModalOpen(false)
}
}
const eventGetter = (event: any) => { const eventGetter = (event: any) => {
return event?.title === '-' return event?.title === '-'
@ -205,7 +247,26 @@ const SupervisorSchedule = () => {
max={dayjs().set('hour', 16).set('minute', 0).toDate()} max={dayjs().set('hour', 16).set('minute', 0).toDate()}
messages={bigCalendarTranslations} messages={bigCalendarTranslations}
/> />
<Modal
isOpen={isWeekModalOpen}
onRequestClose={() => setIsWeekModalOpen(false)}
contentLabel="modal"
style={customStyles}
>
<h3>Dostępne godziny</h3>
<strong> {dayjs(selectedDate?.start).format('YYYY-MM-DD')}</strong>
<div className="flex flex-col gap-2 mt-3">
<p>
Od <strong>{dayjs(selectedDate?.start).format('HH:mm')}</strong>
</p>
<p>
Do <strong>{dayjs(selectedDate?.end).format('HH:mm')}</strong>
</p>
</div>
<button className="btn btn-success mt-4" onClick={submitSlot}>
Dodaj dostępność
</button>
</Modal>
<Modal <Modal
isOpen={isModalOpen} isOpen={isModalOpen}
onRequestClose={closeModal} onRequestClose={closeModal}
@ -240,16 +301,34 @@ const SupervisorSchedule = () => {
<button className="btn btn-success mt-4">Dodaj dostępność</button> <button className="btn btn-success mt-4">Dodaj dostępność</button>
</form> </form>
</Modal> </Modal>
{/* <Modal <Modal
isOpen={isEditModalOpen} isOpen={isEditModalOpen}
onRequestClose={() => setIsEditModalOpen(false)} onRequestClose={() => setIsEditModalOpen(false)}
contentLabel="modal" contentLabel="modal"
style={customStyles} style={customStyles}
> >
{selectedDate && id ? ( {selectedDate && id ? (
<EditSchedule eventData={selectedDate} scheduleId={id} /> <div className="w-full flex flex-col">
<h3>Dostępność</h3>
<p className="mb-2">
{dayjs(selectedDate.start).format('YYYY-MM-DD HH:mm')} -{' '}
{dayjs(selectedDate.end).format('HH:mm')}
</p>
<button
className="btn btn-error mt-4"
onClick={() =>
mutateDeleteAvailability({
availabilityId: selectedDate.id,
scheduleId: Number(id),
supervisorId: Number(supervisorId),
})
}
>
Usuń dostępność
</button>
</div>
) : null} ) : null}
</Modal> */} </Modal>
</div> </div>
) )
} }