Fixes after tests
This commit is contained in:
parent
339ba42fb4
commit
2029f6c343
@ -142,6 +142,8 @@ def list_term_of_defences(examination_schedule_id: int, data: dict) -> dict:
|
||||
if student is None:
|
||||
abort(404, "Not found student!")
|
||||
################
|
||||
show_available = data.get('show_available')
|
||||
|
||||
term_of_defences = (
|
||||
TermOfDefence.query.filter(
|
||||
TermOfDefence.examination_schedule_id == examination_schedule_id
|
||||
@ -150,13 +152,14 @@ def list_term_of_defences(examination_schedule_id: int, data: dict) -> dict:
|
||||
.all()
|
||||
)
|
||||
|
||||
if show_available is True:
|
||||
term_of_defences = list(
|
||||
filter(
|
||||
lambda n: len(
|
||||
[
|
||||
d.id
|
||||
for d in n.members_of_committee
|
||||
if d.id == student.groups[0].project_supervisor.id
|
||||
if len(student.groups) > 0 and d.id == student.groups[0].project_supervisor.id
|
||||
]
|
||||
)
|
||||
> 0,
|
||||
|
@ -24,6 +24,7 @@ class ProjectSupervisorQuerySchema(Schema):
|
||||
|
||||
class TemporaryStudentSchema(Schema):
|
||||
student_id = fields.Integer(required=True)
|
||||
show_available = fields.Boolean(required=False)
|
||||
|
||||
|
||||
class ExaminationScheduleStudentSchema(Schema):
|
||||
|
@ -31,6 +31,8 @@ import WorkloadStatistics from './views/coordinator/WorkloadStatistics'
|
||||
import { MainContext } from './context/MainContext'
|
||||
import WithAxios from './context/WithAxios'
|
||||
import BaseModal, { ModalData } from './components/BaseModal'
|
||||
import StudentGroup from './views/student/StudentGroup'
|
||||
import StudentGradeCard from './views/student/StudentGradeCard'
|
||||
|
||||
require('dayjs/locale/pl')
|
||||
dayjs.locale('pl')
|
||||
@ -85,6 +87,11 @@ function App() {
|
||||
<Route path="enrollment" element={<Enrollment />} />
|
||||
<Route path="schedule" element={<StudentSchedules />} />
|
||||
<Route path="schedule/:id" element={<StudentSchedule />} />
|
||||
<Route path="groups/:id" element={<StudentGroup />} />
|
||||
<Route
|
||||
path="groups/:id/grade-card"
|
||||
element={<StudentGradeCard />}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="supervisor" element={<Supervisor />}>
|
||||
<Route index element={<Navigate to="groups" />} />
|
||||
|
@ -31,3 +31,13 @@ export const getGradesForSupervisor = (
|
||||
`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}`,
|
||||
)
|
||||
}
|
||||
|
@ -31,24 +31,33 @@ export const getTermsOfDefencesWithGroups = (scheduleId: number) => {
|
||||
export const getStudentsTermsOfDefences = (
|
||||
scheduleId: number,
|
||||
studentId: number,
|
||||
showAvailable: boolean,
|
||||
) => {
|
||||
return axiosInstance.get<{
|
||||
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<{
|
||||
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<{
|
||||
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) => {
|
||||
@ -221,3 +230,18 @@ export const geWorkloadStatistics = (scheduleId: number) => {
|
||||
}[]
|
||||
}>(`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,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -54,3 +54,6 @@ export const downloadStudents = (mode: boolean, year_group_id: number) =>
|
||||
responseType: 'blob',
|
||||
},
|
||||
)
|
||||
|
||||
export const getStudentDetails = (id: number) =>
|
||||
axiosInstance.get<Student>(`coordinator/students/${id}/detail/`)
|
||||
|
40
frontend/src/components/ConfirmationModal.tsx
Normal file
40
frontend/src/components/ConfirmationModal.tsx
Normal 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
|
@ -16,44 +16,77 @@ const GradeCard = () => {
|
||||
const { handleSubmit, setValue, control, watch, getValues } = useForm()
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const [supervisorId] = useLocalStorageState('userId')
|
||||
const [userType] = useLocalStorageState('userType')
|
||||
|
||||
const {
|
||||
data: groupDetails,
|
||||
refetch: refetchGroup,
|
||||
isLoading: isGroupLoading,
|
||||
isSuccess,
|
||||
} = useQuery(['getGroup', id], () => getGroup(Number(id)))
|
||||
|
||||
useQuery(
|
||||
['getGradesFirst'],
|
||||
() => getGradesForSupervisor(Number(id), 1, Number(supervisorId)),
|
||||
() =>
|
||||
getGradesForSupervisor(
|
||||
Number(id),
|
||||
1,
|
||||
userType === 'coordinator'
|
||||
? Number(groupDetails?.data?.project_supervisor?.id)
|
||||
: Number(supervisorId),
|
||||
),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
for (const [key, value] of Object.entries(data.data)) {
|
||||
if (key !== 'id') setValue(key, value)
|
||||
}
|
||||
},
|
||||
enabled: isSuccess,
|
||||
},
|
||||
)
|
||||
|
||||
useQuery(
|
||||
['getGradesSecond'],
|
||||
() => getGradesForSupervisor(Number(id), 2, Number(supervisorId)),
|
||||
() =>
|
||||
getGradesForSupervisor(
|
||||
Number(id),
|
||||
2,
|
||||
userType === 'coordinator'
|
||||
? Number(groupDetails?.data?.project_supervisor?.id)
|
||||
: Number(supervisorId),
|
||||
),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
for (const [key, value] of Object.entries(data.data)) {
|
||||
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(
|
||||
'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(
|
||||
'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) => {
|
||||
|
@ -37,7 +37,7 @@ const SupervisorSchedule = () => {
|
||||
({ id, start_date, end_date, project_supervisor }) => {
|
||||
return {
|
||||
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),
|
||||
end: new Date(end_date),
|
||||
resource: {},
|
||||
|
@ -6,6 +6,7 @@ import useLocalStorageState from 'use-local-storage-state'
|
||||
import { deleteGroup, getGroups } from '../../api/groups'
|
||||
import { ReactComponent as IconRemove } from '../../assets/svg/icon-remove.svg'
|
||||
import { Link } from 'react-router-dom'
|
||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||
|
||||
const Groups = () => {
|
||||
let navigate = useNavigate()
|
||||
@ -14,6 +15,8 @@ const Groups = () => {
|
||||
const [page, setPage] = useState(1)
|
||||
const [perPage, setPerPage] = useState(10)
|
||||
const [yearGroupId] = useLocalStorageState('yearGroupId')
|
||||
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false)
|
||||
const [groupToDeleteId, setGroupToDeleteId] = useState(0)
|
||||
|
||||
const perPageOptions = [
|
||||
{
|
||||
@ -135,7 +138,12 @@ const Groups = () => {
|
||||
KARTA OCENY
|
||||
</button>
|
||||
</Link>
|
||||
<button onClick={() => mutateDelete(id)}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setGroupToDeleteId(id)
|
||||
setIsConfirmModalOpen(true)
|
||||
}}
|
||||
>
|
||||
<IconRemove />
|
||||
</button>
|
||||
</div>
|
||||
@ -181,6 +189,14 @@ const Groups = () => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmationModal
|
||||
isOpen={isConfirmModalOpen}
|
||||
close={() => setIsConfirmModalOpen(false)}
|
||||
confirmFn={() => {
|
||||
mutateDelete(groupToDeleteId)
|
||||
setIsConfirmModalOpen(false)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import useLocalStorageState from 'use-local-storage-state'
|
||||
import { CreateGroup, editGroup, getGroup } from '../../api/groups'
|
||||
import { getStudents } from '../../api/students'
|
||||
import { ReactComponent as IconRemove } from '../../assets/svg/icon-remove.svg'
|
||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||
|
||||
type SelectValue = {
|
||||
value: string | number
|
||||
@ -44,6 +45,8 @@ const Group = () => {
|
||||
},
|
||||
)
|
||||
const [newStudent, setNewStudent] = useState(0)
|
||||
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false)
|
||||
const [studentsToDelete, setStudentsToDelete] = useState<any[]>([])
|
||||
const { mutate: mutateEditGroup } = useMutation(
|
||||
['editGroup'],
|
||||
(data: { id: number; payload: CreateGroup }) =>
|
||||
@ -96,17 +99,14 @@ const Group = () => {
|
||||
<td>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (students && name && project_supervisor)
|
||||
mutateEditGroup({
|
||||
id: Number(groupId),
|
||||
payload: {
|
||||
name,
|
||||
project_supervisor_id: project_supervisor.id,
|
||||
students: students
|
||||
if (students && name && project_supervisor) {
|
||||
setStudentsToDelete(
|
||||
students
|
||||
.map((student) => student.id)
|
||||
.filter((i) => id !== i),
|
||||
},
|
||||
})
|
||||
)
|
||||
setIsConfirmModalOpen(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IconRemove />
|
||||
@ -154,6 +154,23 @@ const Group = () => {
|
||||
Dodaj
|
||||
</button>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
@ -283,7 +283,7 @@ const Schedule = () => {
|
||||
ROZPOCZNIJ
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-success ml-2"
|
||||
className="btn ml-2"
|
||||
onClick={() => {
|
||||
mutateCloseEnrollments()
|
||||
}}
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
import classNames from 'classnames'
|
||||
import { ReactComponent as IconRemove } from '../../assets/svg/icon-remove.svg'
|
||||
import useLocalStorageState from 'use-local-storage-state'
|
||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||
|
||||
const Students = () => {
|
||||
let navigate = useNavigate()
|
||||
@ -38,6 +39,9 @@ const Students = () => {
|
||||
},
|
||||
]
|
||||
|
||||
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false)
|
||||
const [studentToDeleteId, setStudenToDeleteId] = useState(0)
|
||||
|
||||
const {
|
||||
isLoading: isStudentsLoading,
|
||||
data: students,
|
||||
@ -106,8 +110,9 @@ const Students = () => {
|
||||
<label
|
||||
className="btn btn-success btn-xs md:btn-md btn-outline"
|
||||
htmlFor="file"
|
||||
title="Format: NAZWISKO, IMIE, INDEKS, PESEL, EMAIL"
|
||||
>
|
||||
Importuj
|
||||
Importuj CSV
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
@ -132,24 +137,6 @@ const Students = () => {
|
||||
</option>
|
||||
))}
|
||||
</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">
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -185,7 +172,12 @@ const Students = () => {
|
||||
<td>{index}</td>
|
||||
<td>{groups?.length ? 'Tak' : 'Nie'}</td>
|
||||
<td>
|
||||
<button onClick={() => mutateDelete(id)}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setStudenToDeleteId(id)
|
||||
setIsConfirmModalOpen(true)
|
||||
}}
|
||||
>
|
||||
<IconRemove />
|
||||
</button>
|
||||
</td>
|
||||
@ -228,6 +220,14 @@ const Students = () => {
|
||||
»
|
||||
</button>
|
||||
</div>
|
||||
<ConfirmationModal
|
||||
isOpen={isConfirmModalOpen}
|
||||
close={() => setIsConfirmModalOpen(false)}
|
||||
confirmFn={() => {
|
||||
mutateDelete(studentToDeleteId)
|
||||
setIsConfirmModalOpen(false)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
@ -62,7 +62,6 @@ const Enrollment = () => {
|
||||
<th>Imię</th>
|
||||
<th>Nazwisko</th>
|
||||
<th>Email</th>
|
||||
<th>Tryb</th>
|
||||
<th>Wolne miejsca</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -76,7 +75,6 @@ const Enrollment = () => {
|
||||
<td>{first_name}</td>
|
||||
<td>{last_name}</td>
|
||||
<td>{email}</td>
|
||||
<td>{mode ? 'Stacjonarny' : 'Niestacjonarny'}</td>
|
||||
<td>{available_groups}</td>
|
||||
</tr>
|
||||
))}
|
||||
|
@ -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'
|
||||
|
||||
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 (
|
||||
<>
|
||||
<TopBar
|
||||
routes={[
|
||||
{ name: 'Zapisy', path: '/student/enrollment' },
|
||||
{ name: 'Harmonogram', path: '/student/schedule' },
|
||||
]}
|
||||
color="bg-gray-300"
|
||||
/>
|
||||
<TopBar routes={routes} color="bg-gray-300" />
|
||||
<div className="m-10">
|
||||
<Outlet />
|
||||
</div>
|
||||
|
1331
frontend/src/views/student/StudentGradeCard.tsx
Normal file
1331
frontend/src/views/student/StudentGradeCard.tsx
Normal file
File diff suppressed because it is too large
Load Diff
85
frontend/src/views/student/StudentGroup.tsx
Normal file
85
frontend/src/views/student/StudentGroup.tsx
Normal 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
|
@ -42,6 +42,7 @@ const StudentSchedule = () => {
|
||||
id: number
|
||||
resource: any
|
||||
}>()
|
||||
const [showAvailable, setShowAvailable] = useState(false)
|
||||
const [view, setView] = useState(Views.WEEK)
|
||||
const onView = useCallback((newView: any) => setView(newView), [setView])
|
||||
|
||||
@ -55,23 +56,35 @@ const StudentSchedule = () => {
|
||||
}>({ mode: 'onBlur' })
|
||||
|
||||
const { refetch } = useQuery(
|
||||
['studentSchedules'],
|
||||
() => getStudentsTermsOfDefences(Number(id), Number(studentId)),
|
||||
['studentSchedules', showAvailable],
|
||||
() =>
|
||||
getStudentsTermsOfDefences(Number(id), Number(studentId), showAvailable),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setEvents(
|
||||
data.data.term_of_defences.map(
|
||||
({
|
||||
id,
|
||||
start_date,
|
||||
end_date,
|
||||
title = 'Obrona',
|
||||
members_of_committee,
|
||||
group,
|
||||
}) => {
|
||||
({ id, start_date, end_date, members_of_committee, group }) => {
|
||||
let initials = ''
|
||||
let title = ''
|
||||
members_of_committee.forEach((member) => {
|
||||
initials +=
|
||||
`${member.first_name} ${member.last_name}`
|
||||
.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 {
|
||||
id,
|
||||
title: `${group?.name ?? '-'}`,
|
||||
title: title,
|
||||
start: new Date(start_date),
|
||||
end: new Date(end_date),
|
||||
resource: {
|
||||
@ -98,7 +111,6 @@ const StudentSchedule = () => {
|
||||
end: Date
|
||||
resource: any
|
||||
}) => {
|
||||
console.log(event)
|
||||
setSelectedDate(event)
|
||||
setIsEditModalOpen(true)
|
||||
},
|
||||
@ -129,11 +141,23 @@ const StudentSchedule = () => {
|
||||
<h1 className="text-2xl font-bold mb-2 text-center">
|
||||
Wybierz i zatwierdź termin obrony dla swojej grupy
|
||||
</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
|
||||
localizer={localizer}
|
||||
startAccessor="start"
|
||||
endAccessor="end"
|
||||
style={{ height: '85vh' }}
|
||||
style={{ height: '85vh', fontSize: '14px' }}
|
||||
onSelectEvent={handleSelectEvent}
|
||||
onSelectSlot={handleSelectSlot}
|
||||
events={events}
|
||||
|
@ -91,7 +91,7 @@ const SupervisorGroups = () => {
|
||||
<tr className="bg-gray-50">
|
||||
<th>Nazwa</th>
|
||||
<th>Opiekun</th>
|
||||
<th>Semestr 1</th>
|
||||
<th>Punkty Semestr 1</th>
|
||||
<th>Semestr 2</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@ -125,8 +125,8 @@ const SupervisorGroups = () => {
|
||||
<td>
|
||||
{`${project_supervisor.first_name} ${project_supervisor.last_name}`}
|
||||
</td>
|
||||
<td>{points_for_first_term}</td>
|
||||
<td>{points_for_second_term}</td>
|
||||
<td>{points_for_first_term}%</td>
|
||||
<td>{points_for_second_term}%</td>
|
||||
<td>
|
||||
<div className="flex align-center gap-2">
|
||||
<Link
|
||||
|
@ -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 { useMutation, useQuery } from 'react-query'
|
||||
import {
|
||||
addAvailability,
|
||||
getAvailabilityForSupervisor,
|
||||
getSupervisorTermsOfDefences,
|
||||
supervisorDeleteAvailability,
|
||||
} from '../../api/schedule'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import Modal from 'react-modal'
|
||||
@ -23,6 +24,8 @@ const customStyles = {
|
||||
bottom: 'auto',
|
||||
marginRight: '-50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: '300px',
|
||||
height: 'auto',
|
||||
},
|
||||
}
|
||||
type SelectValue = {
|
||||
@ -51,10 +54,11 @@ const SupervisorSchedule = () => {
|
||||
id: number
|
||||
resource: any
|
||||
}>()
|
||||
const [view, setView] = useState(Views.MONTH)
|
||||
const [view, setView] = useState<View>(Views.WEEK)
|
||||
const onView = useCallback((newView: any) => setView(newView), [setView])
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const [isWeekModalOpen, setIsWeekModalOpen] = useState(false)
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false)
|
||||
Modal.setAppElement('#root')
|
||||
|
||||
@ -65,7 +69,7 @@ const SupervisorSchedule = () => {
|
||||
|
||||
const { refetch, isFetching } = useQuery(
|
||||
['schedules'],
|
||||
() => getSupervisorTermsOfDefences(Number(id)),
|
||||
() => getSupervisorTermsOfDefences(Number(id), Number(supervisorId)),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setEvents(
|
||||
@ -96,7 +100,7 @@ const SupervisorSchedule = () => {
|
||||
|
||||
const { refetch: refetchAvailability } = useQuery(
|
||||
['availability'],
|
||||
() => getAvailabilityForSupervisor(Number(id)),
|
||||
() => getAvailabilityForSupervisor(Number(id), Number(supervisorId)),
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
setEvents([
|
||||
@ -125,9 +129,32 @@ const SupervisorSchedule = () => {
|
||||
}) => 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) => {
|
||||
setSelectedDate(event)
|
||||
if (view === Views.MONTH) {
|
||||
if (view === Views.WEEK || view === Views.DAY) {
|
||||
setIsWeekModalOpen(true)
|
||||
} else if (view === Views.MONTH) {
|
||||
setIsModalOpen(true)
|
||||
}
|
||||
}
|
||||
@ -149,6 +176,7 @@ const SupervisorSchedule = () => {
|
||||
function closeModal() {
|
||||
setIsModalOpen(false)
|
||||
}
|
||||
|
||||
const onSubmit = async (data: any) => {
|
||||
if (selectedDate && view === Views.MONTH) {
|
||||
const from = data.from.split(':')
|
||||
@ -166,12 +194,26 @@ const SupervisorSchedule = () => {
|
||||
project_supervisor_id: Number(supervisorId),
|
||||
})
|
||||
setEvents([])
|
||||
refetch()
|
||||
refetchAvailability()
|
||||
await refetch()
|
||||
await refetchAvailability()
|
||||
reset()
|
||||
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) => {
|
||||
return event?.title === '-'
|
||||
@ -205,7 +247,26 @@ const SupervisorSchedule = () => {
|
||||
max={dayjs().set('hour', 16).set('minute', 0).toDate()}
|
||||
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
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={closeModal}
|
||||
@ -240,16 +301,34 @@ const SupervisorSchedule = () => {
|
||||
<button className="btn btn-success mt-4">Dodaj dostępność</button>
|
||||
</form>
|
||||
</Modal>
|
||||
{/* <Modal
|
||||
<Modal
|
||||
isOpen={isEditModalOpen}
|
||||
onRequestClose={() => setIsEditModalOpen(false)}
|
||||
contentLabel="modal"
|
||||
style={customStyles}
|
||||
>
|
||||
{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}
|
||||
</Modal> */}
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user