Add frontend pagination, add per_page query param
This commit is contained in:
parent
b550fea998
commit
1afd4f8f51
@ -20,8 +20,7 @@ def order_by_column_name(query: BaseQuery, model_field: str, order_by_col_name:
|
||||
return query
|
||||
|
||||
|
||||
def paginate_models(page: str, query: BaseQuery) -> PaginationResponse or Tuple[dict, int]:
|
||||
per_page = 10
|
||||
def paginate_models(page: str, query: BaseQuery, per_page = 10) -> PaginationResponse or Tuple[dict, int]:
|
||||
default_page = 1
|
||||
|
||||
if page is not None:
|
||||
|
@ -26,11 +26,12 @@ def list_students(query: dict) -> dict:
|
||||
order_by_first_name = query.get('order_by_first_name')
|
||||
order_by_last_name = query.get('order_by_last_name')
|
||||
page = query.get('page')
|
||||
per_page = query.get('per_page')
|
||||
|
||||
student_query = Student.search_by_fullname_and_order_by_first_name_or_last_name(fullname, order_by_first_name,
|
||||
order_by_last_name)
|
||||
|
||||
response = paginate_models(page, student_query)
|
||||
response = paginate_models(page, student_query, per_page)
|
||||
if (message := response.get('message')) is not None:
|
||||
abort(response['status_code'], message)
|
||||
# print(get_debug_queries()[0])
|
||||
|
11
frontend/package-lock.json
generated
11
frontend/package-lock.json
generated
@ -16,6 +16,7 @@
|
||||
"@types/react": "^18.0.9",
|
||||
"@types/react-dom": "^18.0.4",
|
||||
"axios": "^0.27.2",
|
||||
"classnames": "^2.3.1",
|
||||
"daisyui": "^2.15.2",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
@ -5260,6 +5261,11 @@
|
||||
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz",
|
||||
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA=="
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||
},
|
||||
"node_modules/clean-css": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz",
|
||||
@ -20237,6 +20243,11 @@
|
||||
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz",
|
||||
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA=="
|
||||
},
|
||||
"classnames": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||
},
|
||||
"clean-css": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz",
|
||||
|
@ -11,6 +11,7 @@
|
||||
"@types/react": "^18.0.9",
|
||||
"@types/react-dom": "^18.0.4",
|
||||
"axios": "^0.27.2",
|
||||
"classnames": "^2.3.1",
|
||||
"daisyui": "^2.15.2",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
|
@ -1,5 +1,7 @@
|
||||
import axiosInstance from './axiosInstance'
|
||||
|
||||
type OrderType = 'asc' | 'desc'
|
||||
|
||||
interface StudentResponse {
|
||||
max_pages: number
|
||||
students: Student[]
|
||||
@ -13,9 +15,18 @@ export interface Student {
|
||||
group?: any
|
||||
}
|
||||
|
||||
export const getStudents = () =>
|
||||
export const getStudents = (
|
||||
params: Partial<{
|
||||
fullname: string
|
||||
order_by_first_name: OrderType
|
||||
order_by_last_name: OrderType
|
||||
page: number
|
||||
per_page: number
|
||||
}> = {},
|
||||
) =>
|
||||
axiosInstance.get<StudentResponse>(
|
||||
'http://127.0.0.1:5000/api/coordinator/students',
|
||||
{ params },
|
||||
)
|
||||
|
||||
export const createStudent = (payload: Student) =>
|
||||
|
@ -4,7 +4,7 @@ const TopBar = () => {
|
||||
const linkClass = ({ isActive }: { isActive: boolean }) =>
|
||||
isActive ? 'underline font-bold' : ''
|
||||
return (
|
||||
<div className="flex items-center bg-gray-300 p-6">
|
||||
<div className="flex items-center bg-gray-300 py-6 px-10 shadow">
|
||||
<h1 className="text-xl font-bold">System PRI</h1>
|
||||
<div className="flex ml-10 gap-3">
|
||||
<NavLink className={linkClass} to="/coordinator/groups">
|
||||
|
@ -1,17 +1,40 @@
|
||||
import React, { useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useMutation, useQuery } from 'react-query'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { getStudents, uploadStudents } from '../../api/students'
|
||||
import classNames from 'classnames'
|
||||
|
||||
const Students = () => {
|
||||
let navigate = useNavigate()
|
||||
const [showGroupless, setShowGroupless] = useState(false)
|
||||
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: isStudentsLoading,
|
||||
data: students,
|
||||
refetch: refetchStudents,
|
||||
} = useQuery('students', () => getStudents())
|
||||
} = useQuery(['students', page, perPage], () =>
|
||||
getStudents({ page, per_page: perPage }),
|
||||
)
|
||||
|
||||
const { mutate: mutateUpload } = useMutation(
|
||||
'uploadStudents',
|
||||
@ -28,65 +51,115 @@ const Students = () => {
|
||||
mutateUpload(payload)
|
||||
}
|
||||
|
||||
if (isStudentsLoading) {
|
||||
return <div>Ładowanie</div>
|
||||
}
|
||||
useEffect(() => {
|
||||
setPage(1)
|
||||
}, [perPage])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
className="btn btn-success"
|
||||
onClick={() => navigate('/coordinator/add-student')}
|
||||
>
|
||||
Dodaj nowego studenta
|
||||
</button>
|
||||
<label className="ml-4 btn btn-success btn-outline" htmlFor="file">
|
||||
Importuj
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
id="file"
|
||||
name="avatar"
|
||||
accept=".csv"
|
||||
className="hidden"
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
<label className="label ml-auto">
|
||||
<span className="mr-2">Tylko niezapisani</span>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<button
|
||||
className="btn btn-success"
|
||||
onClick={() => navigate('/coordinator/add-student')}
|
||||
>
|
||||
Dodaj nowego studenta
|
||||
</button>
|
||||
<label className="ml-4 btn btn-success btn-outline" htmlFor="file">
|
||||
Importuj
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
onChange={() => setShowGroupless(!showGroupless)}
|
||||
type="file"
|
||||
id="file"
|
||||
name="avatar"
|
||||
accept=".csv"
|
||||
className="hidden"
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<select className="select select-bordered mr-3">
|
||||
{perPageOptions.map(({ value, label }) => (
|
||||
<option onClick={() => setPerPage(value)}>{label}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="label">
|
||||
<span className="mr-2">Tylko niezapisani</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
onChange={() => setShowGroupless(!showGroupless)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</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>Indeks</th>
|
||||
<th>Zapisany</th>
|
||||
<th>Tryb</th>
|
||||
</tr>
|
||||
</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 }) => (
|
||||
<tr key={index}>
|
||||
<td>{first_name}</td>
|
||||
<td>{last_name}</td>
|
||||
<td>{index}</td>
|
||||
<td>{group === null ? 'Nie' : 'Tak'}</td>
|
||||
<td>{mode ? 'stacjonarny' : 'niestacjonarny'}</td>
|
||||
{isStudentsLoading ? (
|
||||
<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>Indeks</th>
|
||||
<th>Zapisany</th>
|
||||
<th>Tryb</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</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 }) => (
|
||||
<tr key={index}>
|
||||
<td>{first_name}</td>
|
||||
<td>{last_name}</td>
|
||||
<td>{index}</td>
|
||||
<td>{group === null ? 'Nie' : 'Tak'}</td>
|
||||
<td>{mode ? 'stacjonarny' : 'niestacjonarny'}</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(
|
||||
students?.data?.max_pages && students?.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 === students?.data?.max_pages}
|
||||
>
|
||||
»
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"target": "es2015",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
|
Loading…
Reference in New Issue
Block a user