Add more details in group view, add editing group students

This commit is contained in:
adam-skowronek 2023-01-06 19:24:02 +01:00
parent 4f42e69a5d
commit 213a16743d
9 changed files with 166 additions and 15 deletions

View File

@ -5,7 +5,7 @@ from flask_sqlalchemy import get_debug_queries
from ...students.models import Group, Student, YearGroup, ProjectGradeSheet
from ...project_supervisor.models import ProjectSupervisor, YearGroupProjectSupervisors
from ..schemas import GroupSchema, GroupEditSchema, GroupsPaginationSchema, \
GroupCreateSchema, MessageSchema, GroupQuerySchema
GroupCreateSchema, MessageSchema, GroupQuerySchema, DetailGroupSchema
from ...dependencies import db
from ...base.utils import paginate_models
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.output(GroupSchema)
@bp.output(DetailGroupSchema)
def detail_group(id: int) -> Group:
group = Group.query.filter_by(id=id).first()
if group is None:
@ -135,6 +135,10 @@ def edit_group(id: int, data: dict) -> dict:
if group is None:
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()
return {"message": "Group was updated!"}

View File

@ -7,5 +7,5 @@ from .project_supervisor import ProjectSupervisorQuerySchema, ProjectSupervisors
ProjectSupervisorCreateSchema, ProjectSupervisorEditSchema, ProjectSupervisorYearGroupSchema
from .students import ProjectSupervisorSchema, GroupSchema, StudentSchema, StudentsPaginationSchema, \
StudentListFileDownloaderSchema, StudentCreateSchema, StudentEditSchema, MessageSchema, FileSchema, \
StudentQuerySchema, YearGroupInfoQuery
StudentQuerySchema, YearGroupInfoQuery, DetailGroupSchema
from .year_group import YearGroupSchema, YearGroupPaginationSchema, YearGroupQuerySchema

View File

@ -23,8 +23,8 @@ class GroupCreateSchema(Schema):
class GroupEditSchema(Schema):
name = fields.Str(validate=validate.Length(min=1, max=255))
project_supervisor_id = fields.Integer(validate=validate_index)
students = fields.List(fields.Nested(StudentSchema), validate=validate.Length(min=1, max=255))
project_supervisor_id = fields.Integer()
students = fields.List(fields.Integer(validate=validate_index))
class GroupIdSchema(Schema):

View File

@ -68,3 +68,9 @@ class StudentQuerySchema(ma.Schema):
class YearGroupInfoQuery(Schema):
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

View File

@ -1,5 +1,6 @@
import axiosInstance from './axiosInstance'
import { Leader } from './leaders'
import { Student } from './students'
export interface CreateGroup {
name: string
@ -16,6 +17,7 @@ export interface Group {
project_supervisor: Leader
prz_kod: string
tzaj_kod: string
students: Student[]
}
export const getGroups = (
@ -41,3 +43,6 @@ export const deleteGroup = (id: number) =>
export const getGroup = (id: number) =>
axiosInstance.get<Group>(`coordinator/groups/${id}/detail/`)
export const editGroup = (id: number, payload: CreateGroup) =>
axiosInstance.put(`coordinator/groups/${id}/`, payload)

View File

@ -13,7 +13,7 @@ export interface Student {
index: number
pesel: string
mode?: boolean
group?: any
groups?: any[]
}
export const getStudents = (

View File

@ -33,7 +33,7 @@ const AddGroup = () => {
onSuccess: (data) => {
setStudentOptions(
data?.data.students
// .filter((st) => st.group === null)
.filter(({ groups }) => !groups?.length)
.map(({ first_name, last_name, index }) => {
return {
value: index,

View File

@ -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 { 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 { id } = useParams<{ id: string }>()
const location = useLocation()
const { data: groups } = useQuery(['getGroup'], () => getGroup(Number(id)))
const { name, project_supervisor } = groups?.data || {}
const {
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 (
<div>
<h1 className="font-bold text-xl">{name}</h1>
@ -16,6 +71,9 @@ const Group = () => {
Opiekun: {project_supervisor?.first_name}{' '}
{project_supervisor?.last_name}
</h2>
<p>Punkty % za semestr 1: {points_for_first_term}</p>
<p>Punkty % za semestr 2: {points_for_second_term}</p>
<Link
to={`/${
location.pathname.includes('coordinator')
@ -25,6 +83,84 @@ const Group = () => {
>
<button className="btn btn-success mt-2">KARTA OCENY</button>
</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>
)
}

View File

@ -177,13 +177,13 @@ const Students = () => {
</thead>
<tbody className="divide-y divide-gray-100">
{students?.data?.students
?.filter((st) => st.group === null || !showGroupless)
.map(({ first_name, last_name, index, group, mode }) => (
?.filter((st) => !st.groups?.length || !showGroupless)
.map(({ first_name, last_name, index, groups, mode }) => (
<tr key={index}>
<td>{first_name}</td>
<td>{last_name}</td>
<td>{index}</td>
<td>{group === null ? 'Nie' : 'Tak'}</td>
<td>{groups?.length ? 'Tak' : 'Nie'}</td>
<td>
<button onClick={() => mutateDelete(index)}>
<IconRemove />