Add more details in group view, add editing group students
This commit is contained in:
parent
4f42e69a5d
commit
213a16743d
@ -5,7 +5,7 @@ from flask_sqlalchemy import get_debug_queries
|
|||||||
from ...students.models import Group, Student, YearGroup, ProjectGradeSheet
|
from ...students.models import Group, Student, YearGroup, ProjectGradeSheet
|
||||||
from ...project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors
|
from ...project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors
|
||||||
from ..schemas import GroupSchema, GroupEditSchema, GroupsPaginationSchema, \
|
from ..schemas import GroupSchema, GroupEditSchema, GroupsPaginationSchema, \
|
||||||
GroupCreateSchema, MessageSchema, GroupQuerySchema
|
GroupCreateSchema, MessageSchema, GroupQuerySchema, DetailGroupSchema
|
||||||
from ...dependencies import db
|
from ...dependencies import db
|
||||||
from ...base.utils import paginate_models
|
from ...base.utils import paginate_models
|
||||||
from ..utils import load_weight_for_project_grade_sheet, calculate_points_for_one_term
|
from ..utils import load_weight_for_project_grade_sheet, calculate_points_for_one_term
|
||||||
@ -101,7 +101,7 @@ def create_group(year_group_id: int, data: dict) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
@bp.get("/<int:id>/detail/")
|
@bp.get("/<int:id>/detail/")
|
||||||
@bp.output(GroupSchema)
|
@bp.output(DetailGroupSchema)
|
||||||
def detail_group(id: int) -> Group:
|
def detail_group(id: int) -> Group:
|
||||||
group = Group.query.filter_by(id=id).first()
|
group = Group.query.filter_by(id=id).first()
|
||||||
if group is None:
|
if group is None:
|
||||||
@ -135,6 +135,10 @@ def edit_group(id: int, data: dict) -> dict:
|
|||||||
if group is None:
|
if group is None:
|
||||||
abort(400, f"Group with id {id} doesn't exist!")
|
abort(400, f"Group with id {id} doesn't exist!")
|
||||||
|
|
||||||
group_query.update(data)
|
students = db.session.query(Student).filter(Student.index.in_(data['students'])).all()
|
||||||
|
group.students = students
|
||||||
|
group.name = data['name']
|
||||||
|
group.project_supervisor_id = data['project_supervisor_id']
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return {"message": "Group was updated!"}
|
return {"message": "Group was updated!"}
|
||||||
|
@ -7,5 +7,5 @@ from .project_supervisor import ProjectSupervisorQuerySchema, ProjectSupervisors
|
|||||||
ProjectSupervisorCreateSchema, ProjectSupervisorEditSchema, ProjectSupervisorYearGroupSchema
|
ProjectSupervisorCreateSchema, ProjectSupervisorEditSchema, ProjectSupervisorYearGroupSchema
|
||||||
from .students import ProjectSupervisorSchema, GroupSchema, StudentSchema, StudentsPaginationSchema, \
|
from .students import ProjectSupervisorSchema, GroupSchema, StudentSchema, StudentsPaginationSchema, \
|
||||||
StudentListFileDownloaderSchema, StudentCreateSchema, StudentEditSchema, MessageSchema, FileSchema, \
|
StudentListFileDownloaderSchema, StudentCreateSchema, StudentEditSchema, MessageSchema, FileSchema, \
|
||||||
StudentQuerySchema, YearGroupInfoQuery
|
StudentQuerySchema, YearGroupInfoQuery, DetailGroupSchema
|
||||||
from .year_group import YearGroupSchema, YearGroupPaginationSchema, YearGroupQuerySchema
|
from .year_group import YearGroupSchema, YearGroupPaginationSchema, YearGroupQuerySchema
|
||||||
|
@ -23,8 +23,8 @@ class GroupCreateSchema(Schema):
|
|||||||
|
|
||||||
class GroupEditSchema(Schema):
|
class GroupEditSchema(Schema):
|
||||||
name = fields.Str(validate=validate.Length(min=1, max=255))
|
name = fields.Str(validate=validate.Length(min=1, max=255))
|
||||||
project_supervisor_id = fields.Integer(validate=validate_index)
|
project_supervisor_id = fields.Integer()
|
||||||
students = fields.List(fields.Nested(StudentSchema), validate=validate.Length(min=1, max=255))
|
students = fields.List(fields.Integer(validate=validate_index))
|
||||||
|
|
||||||
|
|
||||||
class GroupIdSchema(Schema):
|
class GroupIdSchema(Schema):
|
||||||
|
@ -68,3 +68,9 @@ class StudentQuerySchema(ma.Schema):
|
|||||||
|
|
||||||
class YearGroupInfoQuery(Schema):
|
class YearGroupInfoQuery(Schema):
|
||||||
id = fields.Integer(required=True)
|
id = fields.Integer(required=True)
|
||||||
|
|
||||||
|
class DetailGroupSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
project_supervisor = fields.Nested(ProjectSupervisorSchema)
|
||||||
|
students = fields.List(fields.Nested(StudentSchema), validate=validate.Length(min=1, max=255))
|
||||||
|
class Meta:
|
||||||
|
model = Group
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import axiosInstance from './axiosInstance'
|
import axiosInstance from './axiosInstance'
|
||||||
import { Leader } from './leaders'
|
import { Leader } from './leaders'
|
||||||
|
import { Student } from './students'
|
||||||
|
|
||||||
export interface CreateGroup {
|
export interface CreateGroup {
|
||||||
name: string
|
name: string
|
||||||
@ -16,6 +17,7 @@ export interface Group {
|
|||||||
project_supervisor: Leader
|
project_supervisor: Leader
|
||||||
prz_kod: string
|
prz_kod: string
|
||||||
tzaj_kod: string
|
tzaj_kod: string
|
||||||
|
students: Student[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getGroups = (
|
export const getGroups = (
|
||||||
@ -41,3 +43,6 @@ export const deleteGroup = (id: number) =>
|
|||||||
|
|
||||||
export const getGroup = (id: number) =>
|
export const getGroup = (id: number) =>
|
||||||
axiosInstance.get<Group>(`coordinator/groups/${id}/detail/`)
|
axiosInstance.get<Group>(`coordinator/groups/${id}/detail/`)
|
||||||
|
|
||||||
|
export const editGroup = (id: number, payload: CreateGroup) =>
|
||||||
|
axiosInstance.put(`coordinator/groups/${id}/`, payload)
|
||||||
|
@ -13,7 +13,7 @@ export interface Student {
|
|||||||
index: number
|
index: number
|
||||||
pesel: string
|
pesel: string
|
||||||
mode?: boolean
|
mode?: boolean
|
||||||
group?: any
|
groups?: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getStudents = (
|
export const getStudents = (
|
||||||
|
@ -33,7 +33,7 @@ const AddGroup = () => {
|
|||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
setStudentOptions(
|
setStudentOptions(
|
||||||
data?.data.students
|
data?.data.students
|
||||||
// .filter((st) => st.group === null)
|
.filter(({ groups }) => !groups?.length)
|
||||||
.map(({ first_name, last_name, index }) => {
|
.map(({ first_name, last_name, index }) => {
|
||||||
return {
|
return {
|
||||||
value: index,
|
value: index,
|
||||||
|
@ -1,14 +1,69 @@
|
|||||||
import { useQuery } from 'react-query'
|
import { useState } from 'react'
|
||||||
|
import { useMutation, useQuery } from 'react-query'
|
||||||
import { Link, useLocation, useParams } from 'react-router-dom'
|
import { Link, useLocation, useParams } from 'react-router-dom'
|
||||||
import { getGroup } from '../../api/groups'
|
import ReactSelect from 'react-select'
|
||||||
|
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'
|
||||||
|
|
||||||
|
type SelectValue = {
|
||||||
|
value: string | number
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
const Group = () => {
|
const Group = () => {
|
||||||
const { id } = useParams<{ id: string }>()
|
const { id } = useParams<{ id: string }>()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
const { data: groups } = useQuery(['getGroup'], () => getGroup(Number(id)))
|
const {
|
||||||
const { name, project_supervisor } = groups?.data || {}
|
data: groups,
|
||||||
|
refetch,
|
||||||
|
isLoading: isGroupLoading,
|
||||||
|
} = useQuery(['getGroup', id], () => getGroup(Number(id)))
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
project_supervisor,
|
||||||
|
students,
|
||||||
|
points_for_first_term,
|
||||||
|
points_for_second_term,
|
||||||
|
} = groups?.data || {}
|
||||||
|
const [yearGroupId] = useLocalStorageState('yearGroupId')
|
||||||
|
|
||||||
|
const [studentOptions, setStudentOptions] = useState<SelectValue[]>([])
|
||||||
|
const { isLoading: areStudentsLoading } = 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 }) => {
|
||||||
|
return {
|
||||||
|
value: index,
|
||||||
|
label: `${first_name} ${last_name} (${index})`,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const [newStudent, setNewStudent] = useState(0)
|
||||||
|
const { mutate: mutateEditGroup } = useMutation(
|
||||||
|
['editGroup'],
|
||||||
|
(data: { id: number; payload: CreateGroup }) =>
|
||||||
|
editGroup(data.id, data.payload),
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
refetch()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isGroupLoading || areStudentsLoading) {
|
||||||
|
return <div>Ładowanie</div>
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="font-bold text-xl">{name}</h1>
|
<h1 className="font-bold text-xl">{name}</h1>
|
||||||
@ -16,6 +71,9 @@ const Group = () => {
|
|||||||
Opiekun: {project_supervisor?.first_name}{' '}
|
Opiekun: {project_supervisor?.first_name}{' '}
|
||||||
{project_supervisor?.last_name}
|
{project_supervisor?.last_name}
|
||||||
</h2>
|
</h2>
|
||||||
|
<p>Punkty % za semestr 1: {points_for_first_term}</p>
|
||||||
|
<p>Punkty % za semestr 2: {points_for_second_term}</p>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to={`/${
|
to={`/${
|
||||||
location.pathname.includes('coordinator')
|
location.pathname.includes('coordinator')
|
||||||
@ -25,6 +83,84 @@ const Group = () => {
|
|||||||
>
|
>
|
||||||
<button className="btn btn-success mt-2">KARTA OCENY</button>
|
<button className="btn btn-success mt-2">KARTA OCENY</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
<div className="flex mx-auto 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>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-100">
|
||||||
|
{students?.map(({ first_name, last_name, index }) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>{first_name}</td>
|
||||||
|
<td>{last_name}</td>
|
||||||
|
<td>{index}</td>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (students && name && project_supervisor)
|
||||||
|
mutateEditGroup({
|
||||||
|
id: Number(id),
|
||||||
|
payload: {
|
||||||
|
name,
|
||||||
|
project_supervisor_id: project_supervisor.id,
|
||||||
|
students: students
|
||||||
|
.map((student) => student.index)
|
||||||
|
.filter((i) => index !== i),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconRemove />
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 mt-2">
|
||||||
|
<ReactSelect
|
||||||
|
closeMenuOnSelect={true}
|
||||||
|
options={studentOptions}
|
||||||
|
placeholder="Wybierz studenta"
|
||||||
|
styles={{
|
||||||
|
control: (styles) => ({
|
||||||
|
...styles,
|
||||||
|
width: '270px',
|
||||||
|
padding: '0.3rem',
|
||||||
|
borderRadius: '0.5rem',
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
onChange={(val) => {
|
||||||
|
if (val?.value) setNewStudent(Number(val.value))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="btn btn-success"
|
||||||
|
onClick={() => {
|
||||||
|
if (students && name && project_supervisor)
|
||||||
|
mutateEditGroup({
|
||||||
|
id: Number(id),
|
||||||
|
payload: {
|
||||||
|
name,
|
||||||
|
project_supervisor_id: project_supervisor.id,
|
||||||
|
students: [
|
||||||
|
...students.map((student) => student.index),
|
||||||
|
newStudent,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Dodaj
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -177,13 +177,13 @@ const Students = () => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-100">
|
<tbody className="divide-y divide-gray-100">
|
||||||
{students?.data?.students
|
{students?.data?.students
|
||||||
?.filter((st) => st.group === null || !showGroupless)
|
?.filter((st) => !st.groups?.length || !showGroupless)
|
||||||
.map(({ first_name, last_name, index, group, mode }) => (
|
.map(({ first_name, last_name, index, groups, mode }) => (
|
||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
<td>{first_name}</td>
|
<td>{first_name}</td>
|
||||||
<td>{last_name}</td>
|
<td>{last_name}</td>
|
||||||
<td>{index}</td>
|
<td>{index}</td>
|
||||||
<td>{group === null ? 'Nie' : 'Tak'}</td>
|
<td>{groups?.length ? 'Tak' : 'Nie'}</td>
|
||||||
<td>
|
<td>
|
||||||
<button onClick={() => mutateDelete(index)}>
|
<button onClick={() => mutateDelete(index)}>
|
||||||
<IconRemove />
|
<IconRemove />
|
||||||
|
Loading…
Reference in New Issue
Block a user