add project_supervisors endpoints for coordinator and add Leaders card in frontend
This commit is contained in:
parent
a4a2807fd2
commit
9dd72aeb20
103
backend/app/coordinator/routes/project_supervisor.py
Normal file
103
backend/app/coordinator/routes/project_supervisor.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
from flask import abort
|
||||||
|
from apiflask import APIBlueprint
|
||||||
|
from flask_sqlalchemy import get_debug_queries
|
||||||
|
|
||||||
|
from ...project_supervisor.models import ProjectSupervisor
|
||||||
|
from ..schemas import ProjectSupervisorSchema, ProjectSupervisorEditSchema, ProjectSupervisorsPaginationSchema, \
|
||||||
|
ProjectSupervisorCreateSchema, MessageSchema, FileSchema, ProjectSupervisorQuerySchema
|
||||||
|
from ...dependencies import db, ma
|
||||||
|
from ...base.utils import paginate_models
|
||||||
|
|
||||||
|
bp = APIBlueprint("project_supervisor", __name__, url_prefix="/project_supervisor")
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/", methods=["GET"])
|
||||||
|
@bp.input(ProjectSupervisorQuerySchema, location='query')
|
||||||
|
@bp.output(ProjectSupervisorsPaginationSchema)
|
||||||
|
def list_project_supervisors(query: dict) -> dict:
|
||||||
|
fullname = query.get('fullname')
|
||||||
|
order_by_first_name = query.get('order_by_first_name')
|
||||||
|
order_by_last_name = query.get('order_by_last_name')
|
||||||
|
mode = query.get('mode')
|
||||||
|
page = query.get('page')
|
||||||
|
per_page = query.get('per_page')
|
||||||
|
|
||||||
|
project_supervisor_query = ProjectSupervisor.search_by_fullname_and_mode_and_order_by_first_name_or_last_name(
|
||||||
|
fullname, mode, order_by_first_name, order_by_last_name)
|
||||||
|
|
||||||
|
response = paginate_models(page, project_supervisor_query, per_page)
|
||||||
|
if (message := response.get('message')) is not None:
|
||||||
|
abort(response['status_code'], message)
|
||||||
|
# print(get_debug_queries()[0])
|
||||||
|
return {
|
||||||
|
"project_supervisors": response['items'],
|
||||||
|
"max_pages": response['max_pages']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/", methods=["POST"])
|
||||||
|
@bp.input(ProjectSupervisorCreateSchema)
|
||||||
|
@bp.output(MessageSchema)
|
||||||
|
def create_project_supervisor(data: dict) -> dict:
|
||||||
|
first_name = data['first_name']
|
||||||
|
last_name = data['last_name']
|
||||||
|
email = data['email']
|
||||||
|
limit_group = data['limit_group']
|
||||||
|
mode = data['mode']
|
||||||
|
|
||||||
|
project_supervisor = ProjectSupervisor.query.filter(first_name=first_name).filter(last_name=last_name).first()
|
||||||
|
if project_supervisor is not None:
|
||||||
|
abort(400, "Project Supervisor has already exists!")
|
||||||
|
|
||||||
|
project_supervisor = ProjectSupervisor(first_name=first_name,
|
||||||
|
last_name = last_name,
|
||||||
|
email = email,
|
||||||
|
limit_group = limit_group,
|
||||||
|
mode = mode)
|
||||||
|
|
||||||
|
db.session.add(project_supervisor)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return {"message": "Project Supervisor was created!"}
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/<int:id>/", methods=["GET"])
|
||||||
|
@bp.output(ProjectSupervisorSchema)
|
||||||
|
def detail_project_supervisor(id: int) -> ProjectSupervisor:
|
||||||
|
project_supervisor = ProjectSupervisor.query.filter_by(id=id).first()
|
||||||
|
if project_supervisor is None:
|
||||||
|
abort(400, f"Project Supervisor with id {id} doesn't exist!")
|
||||||
|
return project_supervisor
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/<int:id>/", methods=["DELETE"])
|
||||||
|
@bp.output(MessageSchema, status_code=202)
|
||||||
|
def delete_project_supervisor(id: int) -> dict:
|
||||||
|
project_supervisor = ProjectSupervisor.query.filter_by(id=id).first()
|
||||||
|
if project_supervisor is None:
|
||||||
|
abort(400, f"Project Supervisor with id {id} doesn't exist!")
|
||||||
|
|
||||||
|
if project_supervisor.count_groups > 0:
|
||||||
|
abort(400, f"Project Supervisor with id {id} has gropus!")
|
||||||
|
|
||||||
|
db.session.delete(project_supervisor)
|
||||||
|
db.session.commit()
|
||||||
|
return {"message": "Project Supervisor was deleted!"}
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/<int:id>", methods=["PUT"])
|
||||||
|
@bp.input(ProjectSupervisorEditSchema)
|
||||||
|
@bp.output(MessageSchema)
|
||||||
|
def edit_project_supervisor(id: int, data: dict) -> dict:
|
||||||
|
if not data:
|
||||||
|
abort(400, 'You have passed empty data!')
|
||||||
|
|
||||||
|
project_supervisor_query = ProjectSupervisor.query.filter_by(id=id)
|
||||||
|
project_supervisor = project_supervisor_query.first()
|
||||||
|
|
||||||
|
if project_supervisor is None:
|
||||||
|
abort(400, f"Project Supervisor with id {id} doesn't exist!")
|
||||||
|
|
||||||
|
project_supervisor_query.update(project_supervisor)
|
||||||
|
db.session.commit()
|
||||||
|
return {"message": "Project Supervisor was updated!"}
|
@ -1,8 +1,7 @@
|
|||||||
from ..dependencies import ma
|
from ..dependencies import ma
|
||||||
from ..students.models import Student, Group
|
from ..students.models import Student, Group
|
||||||
|
from ..project_supervisor.models import ProjectSupervisor
|
||||||
from marshmallow import fields, validate, ValidationError
|
from marshmallow import fields, validate, ValidationError
|
||||||
from ..project_supervisor.schemas import ProjectSupervisorSchema
|
|
||||||
|
|
||||||
|
|
||||||
def validate_index(index):
|
def validate_index(index):
|
||||||
if len(str(index)) > 6:
|
if len(str(index)) > 6:
|
||||||
@ -10,6 +9,13 @@ def validate_index(index):
|
|||||||
elif len(str(index)) < 6:
|
elif len(str(index)) < 6:
|
||||||
raise ValidationError("Length of index is too short!")
|
raise ValidationError("Length of index is too short!")
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectSupervisorSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta:
|
||||||
|
model = ProjectSupervisor
|
||||||
|
include_relationships = False
|
||||||
|
|
||||||
|
|
||||||
class GroupSchema(ma.SQLAlchemyAutoSchema):
|
class GroupSchema(ma.SQLAlchemyAutoSchema):
|
||||||
project_supervisor = fields.Nested(ProjectSupervisorSchema)
|
project_supervisor = fields.Nested(ProjectSupervisorSchema)
|
||||||
|
|
||||||
@ -82,4 +88,35 @@ class GroupCreateSchema(ma.Schema):
|
|||||||
class GroupEditSchema(ma.Schema):
|
class GroupEditSchema(ma.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(validate=validate_index)
|
||||||
students = fields.List(fields.Nested(StudentSchema), validate=validate.Length(min=1, max=255))
|
students = fields.List(fields.Nested(StudentSchema), validate=validate.Length(min=1, max=255))
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectSupervisorQuerySchema(ma.Schema):
|
||||||
|
fullname = fields.Str()
|
||||||
|
order_by_first_name = fields.Str()
|
||||||
|
order_by_last_name = fields.Str()
|
||||||
|
page = fields.Integer()
|
||||||
|
per_page = fields.Integer()
|
||||||
|
mode = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectSupervisorsPaginationSchema(ma.Schema):
|
||||||
|
project_supervisors = fields.List(fields.Nested(ProjectSupervisorSchema))
|
||||||
|
max_pages = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectSupervisorCreateSchema(ma.Schema):
|
||||||
|
first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
||||||
|
last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
||||||
|
email = fields.Str(validate=validate.Length(min=0, max=11), required=True)
|
||||||
|
limit_group = fields.Integer(validate=validate_index)
|
||||||
|
mode = fields.Integer(required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectSupervisorEditSchema(ma.Schema):
|
||||||
|
first_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
||||||
|
last_name = fields.Str(validate=validate.Length(min=1, max=255), required=True)
|
||||||
|
email = fields.Str(validate=validate.Length(min=0, max=11), required=True)
|
||||||
|
limit_group = fields.Integer(validate=validate_index)
|
||||||
|
count_groups = fields.Integer(validate=validate_index)
|
||||||
|
mode = fields.Integer(required=True)
|
||||||
|
@ -26,7 +26,7 @@ def check_columns(df: pd.DataFrame) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def parse_csv(file, mode) -> Generator[Student, Any, None]:
|
def parse_csv(file, mode) -> Generator[Student, Any, None]:
|
||||||
df = pd.read_csv(file, mode)
|
df = pd.read_csv(file)
|
||||||
|
|
||||||
# if not check_columns(df):
|
# if not check_columns(df):
|
||||||
# raise InvalidNameOrTypeHeaderException
|
# raise InvalidNameOrTypeHeaderException
|
||||||
|
@ -1,10 +1,36 @@
|
|||||||
from ..dependencies import db
|
from ..dependencies import db
|
||||||
from ..base.models import Person, Base
|
from ..base.models import Person, Base
|
||||||
|
from flask_sqlalchemy import BaseQuery
|
||||||
|
|
||||||
|
from sqlalchemy import or_
|
||||||
|
from sqlalchemy.sql import text
|
||||||
|
from ..base.utils import order_by_column_name
|
||||||
|
|
||||||
class ProjectSupervisor(Base, Person):
|
class ProjectSupervisor(Base, Person):
|
||||||
__tablename__ = "project_supervisors"
|
__tablename__ = "project_supervisors"
|
||||||
|
|
||||||
limit_group = db.Column(db.Integer, default=1, nullable=False)
|
limit_group = db.Column(db.Integer, default=3, nullable=False)
|
||||||
count_groups = db.Column(db.Integer, default=1, nullable=False)
|
count_groups = db.Column(db.Integer, default=0, nullable=False)
|
||||||
mode = db.Column(db.Integer, default=0, nullable=False) # 0 - stationary, 1 - non-stationary, 2 - both
|
mode = db.Column(db.Integer, default=0, nullable=False) # 0 - stationary, 1 - non-stationary, 2 - both
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def search_by_fullname_and_mode_and_order_by_first_name_or_last_name(cls, fullname: str = None,
|
||||||
|
mode: int = None,
|
||||||
|
order_by_first_name: str = None,
|
||||||
|
order_by_last_name: str = None) -> BaseQuery:
|
||||||
|
project_supervisors_query = cls.query
|
||||||
|
|
||||||
|
if mode is not None:
|
||||||
|
project_supervisors_query = project_supervisors_query.filter(mode != 1-mode)
|
||||||
|
|
||||||
|
if fullname is not None:
|
||||||
|
"""This works only for sqlite3 database - concat function doesn't exist so i used builtin concat
|
||||||
|
operator specific only for sqlite db - || """
|
||||||
|
project_supervisors_query = project_supervisors_query.filter(
|
||||||
|
text("project_supervisorss_first_name || ' ' || project_supervisorss_last_name LIKE :fullname ")
|
||||||
|
).params(fullname=f'{fullname}%')
|
||||||
|
|
||||||
|
project_supervisors_query = order_by_column_name(project_supervisors_query, ProjectSupervisor.first_name, order_by_first_name)
|
||||||
|
project_supervisors_query = order_by_column_name(project_supervisors_query, ProjectSupervisor.last_name, order_by_last_name)
|
||||||
|
|
||||||
|
return project_supervisors_query
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
from ..dependencies import ma
|
|
||||||
from ..project_supervisor.models import ProjectSupervisor
|
|
||||||
|
|
||||||
class ProjectSupervisorSchema(ma.SQLAlchemyAutoSchema):
|
|
||||||
class Meta:
|
|
||||||
model = ProjectSupervisor
|
|
||||||
include_relationships = False
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { Route, Routes } from 'react-router-dom'
|
|||||||
import './App.css'
|
import './App.css'
|
||||||
import AddGroup from './views/coordinator/AddGroup'
|
import AddGroup from './views/coordinator/AddGroup'
|
||||||
import AddStudent from './views/coordinator/AddStudent'
|
import AddStudent from './views/coordinator/AddStudent'
|
||||||
|
import AddLeader from './views/coordinator/AddLeader'
|
||||||
import Coordinator from './views/coordinator/Coordinator'
|
import Coordinator from './views/coordinator/Coordinator'
|
||||||
import Groups from './views/coordinator/Groups'
|
import Groups from './views/coordinator/Groups'
|
||||||
import Leaders from './views/coordinator/Leaders'
|
import Leaders from './views/coordinator/Leaders'
|
||||||
@ -31,6 +32,7 @@ function App() {
|
|||||||
<Route path="leaders" element={<Leaders />} />
|
<Route path="leaders" element={<Leaders />} />
|
||||||
<Route path="add-student" element={<AddStudent />} />
|
<Route path="add-student" element={<AddStudent />} />
|
||||||
<Route path="add-group" element={<AddGroup />} />
|
<Route path="add-group" element={<AddGroup />} />
|
||||||
|
<Route path="add-leader" element={<AddLeader />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
41
frontend/src/api/leaders.ts
Normal file
41
frontend/src/api/leaders.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import axiosInstance from './axiosInstance'
|
||||||
|
|
||||||
|
type OrderType = 'asc' | 'desc'
|
||||||
|
|
||||||
|
interface LeaderResponse {
|
||||||
|
max_pages: number
|
||||||
|
project_supervisors: Leader[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Leader {
|
||||||
|
id: number
|
||||||
|
first_name: string
|
||||||
|
last_name: string
|
||||||
|
email: string
|
||||||
|
limit_group: number
|
||||||
|
count_group: number
|
||||||
|
mode: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLeaders = (
|
||||||
|
params: Partial<{
|
||||||
|
fullname: string
|
||||||
|
order_by_first_name: OrderType
|
||||||
|
order_by_last_name: OrderType
|
||||||
|
page: number
|
||||||
|
per_page: number
|
||||||
|
}> = {},
|
||||||
|
) =>
|
||||||
|
axiosInstance.get<LeaderResponse>(
|
||||||
|
'http://127.0.0.1:5000/api/coordinator/project_supervisors',
|
||||||
|
{ params },
|
||||||
|
)
|
||||||
|
|
||||||
|
export const createLeader = (payload: Leader) =>
|
||||||
|
axiosInstance.post('http://127.0.0.1:5000/api/coordinator/project_supervisors/', payload)
|
||||||
|
|
||||||
|
|
||||||
|
export const deleteLeader = (payload: Number) =>
|
||||||
|
axiosInstance.delete(
|
||||||
|
'http://127.0.0.1:5000/api/coordinator/project_supervisors/'+payload.toString()+'/',
|
||||||
|
)
|
151
frontend/src/views/coordinator/AddLeader.tsx
Normal file
151
frontend/src/views/coordinator/AddLeader.tsx
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useForm } from 'react-hook-form'
|
||||||
|
import { useMutation } from 'react-query'
|
||||||
|
import { createLeader, Leader } from '../../api/leaders'
|
||||||
|
import InputError from '../../components/InputError'
|
||||||
|
|
||||||
|
const AddLeader = () => {
|
||||||
|
const [isAlertVisible, setIsAlertVisible] = useState(false)
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
} = useForm<Leader>()
|
||||||
|
|
||||||
|
const { mutate: mutateCreateLeader } = useMutation(
|
||||||
|
'createLeader',
|
||||||
|
(payload: Leader) => createLeader(payload),
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
reset()
|
||||||
|
setIsAlertVisible(true)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const onSubmit = (data: Leader) => {
|
||||||
|
console.log(data)
|
||||||
|
mutateCreateLeader(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="w-full lg:w-1/4 flex flex-col mx-auto"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
{isAlertVisible && (
|
||||||
|
<div className="alert alert-success shadow-lg">
|
||||||
|
<span>Udało się dodać studenta!</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label" htmlFor="first_name">
|
||||||
|
Imię
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="input input-bordered"
|
||||||
|
id="first_name"
|
||||||
|
type="text"
|
||||||
|
{...register('first_name', { required: true })}
|
||||||
|
/>
|
||||||
|
{errors.first_name?.type === 'required' && (
|
||||||
|
<InputError>Imię jest wymagane</InputError>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label" htmlFor="last_name">
|
||||||
|
Nazwisko
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="input input-bordered"
|
||||||
|
id="last_name"
|
||||||
|
type="text"
|
||||||
|
{...register('last_name', { required: true })}
|
||||||
|
/>
|
||||||
|
{errors.last_name?.type === 'required' && (
|
||||||
|
<InputError>Nazwisko jest wymagane</InputError>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label" htmlFor="email">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="input input-bordered"
|
||||||
|
id="email"
|
||||||
|
type="text"
|
||||||
|
{...register('email', {
|
||||||
|
required: true,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{errors.email?.type === 'required' && (
|
||||||
|
<InputError>Email jest wymagany</InputError>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label" htmlFor="limit_group">
|
||||||
|
Limit grup
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="input input-bordered"
|
||||||
|
id="limit_group"
|
||||||
|
type="text"
|
||||||
|
{...register('limit_group', {
|
||||||
|
required: false,
|
||||||
|
pattern: /^[1-9][0-9]*$/,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{errors.limit_group?.type === 'pattern' && (
|
||||||
|
<InputError>Limit grup musi być liczbą dodatnią</InputError>
|
||||||
|
)}
|
||||||
|
</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="1"
|
||||||
|
/>
|
||||||
|
<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="0"
|
||||||
|
/>
|
||||||
|
<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="0"
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddLeader
|
@ -1,3 +1,137 @@
|
|||||||
const Leaders = () => <>Opiekunowie</>
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useMutation, useQuery } from 'react-query'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { getLeaders, deleteLeader } from '../../api/leaders'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
const Leaders = () => {
|
||||||
|
let navigate = useNavigate()
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [perPage, setPerPage] = useState(10)
|
||||||
|
|
||||||
|
const perPageOptions = [
|
||||||
|
{
|
||||||
|
value: 10,
|
||||||
|
label: '10 rekordów',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 20,
|
||||||
|
label: '20 rekordów',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 50,
|
||||||
|
label: '50 rekordów',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 1000,
|
||||||
|
label: 'Pokaż wszystkie',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const {
|
||||||
|
isLoading: isLeadersLoading,
|
||||||
|
data: leaders,
|
||||||
|
refetch: refetchLeaders,
|
||||||
|
} = useQuery(['project_supervisors', page, perPage], () =>
|
||||||
|
getLeaders({ page, per_page: perPage }),
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPage(1)
|
||||||
|
}, [perPage])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between flex-col gap-3 md:flex-row md:gap-0">
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
className="btn btn-success btn-xs md:btn-md"
|
||||||
|
onClick={() => navigate('/coordinator/add-leader')}
|
||||||
|
>
|
||||||
|
Dodaj nowego opiekuna
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<select className="select select-xs md:select-md select-bordered mr-3">
|
||||||
|
{perPageOptions.map(({ value, label }) => (
|
||||||
|
<option key={value} onClick={() => setPerPage(value)}>
|
||||||
|
{label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isLeadersLoading ? (
|
||||||
|
<div>Ładowanie</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<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>Email</th>
|
||||||
|
<th>Limit grup</th>
|
||||||
|
<th>Liczba grup</th>
|
||||||
|
<th>Tryb</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-100">
|
||||||
|
{leaders?.data?.project_supervisors
|
||||||
|
.map(({ id, first_name, last_name, email, limit_group, count_group, mode }) => (
|
||||||
|
<tr key={id}>
|
||||||
|
<td>{first_name}</td>
|
||||||
|
<td>{last_name}</td>
|
||||||
|
<td>{email}</td>
|
||||||
|
<td>{limit_group}</td>
|
||||||
|
<td>{mode==0 ? "Stacjonarny" : mode==1 ? "Niestacjonarny" : "Nie/stacjonarny"}</td>
|
||||||
|
<td><button onClick={() => deleteLeader(id).then(() => refetchLeaders())}>X</button></td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex items-center justify-center mt-2">
|
||||||
|
<div className="btn-group">
|
||||||
|
<button
|
||||||
|
className="btn btn-outline"
|
||||||
|
onClick={() => setPage(page - 1)}
|
||||||
|
disabled={page === 1}
|
||||||
|
>
|
||||||
|
«
|
||||||
|
</button>
|
||||||
|
{[
|
||||||
|
...Array(
|
||||||
|
leaders?.data?.max_pages && leaders?.data?.max_pages + 1,
|
||||||
|
).keys(),
|
||||||
|
]
|
||||||
|
.slice(1)
|
||||||
|
.map((p) => (
|
||||||
|
<button
|
||||||
|
key={p}
|
||||||
|
className={classNames('btn btn-outline', {
|
||||||
|
'bg-success': p === page,
|
||||||
|
})}
|
||||||
|
onClick={() => setPage(p)}
|
||||||
|
>
|
||||||
|
{p}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
className="btn btn-outline"
|
||||||
|
onClick={() => setPage(page + 1)}
|
||||||
|
disabled={page === leaders?.data?.max_pages}
|
||||||
|
>
|
||||||
|
»
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default Leaders;
|
export default Leaders;
|
Loading…
Reference in New Issue
Block a user