Add frontend pagination, add per_page query param

This commit is contained in:
adam-skowronek 2022-06-08 21:48:49 +02:00
parent b550fea998
commit 1afd4f8f51
8 changed files with 157 additions and 61 deletions

View File

@ -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:

View File

@ -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])

View File

@ -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",

View File

@ -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",

View File

@ -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) =>

View File

@ -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">

View File

@ -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>
)
}

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es5",
"target": "es2015",
"lib": [
"dom",
"dom.iterable",