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
|
return query
|
||||||
|
|
||||||
|
|
||||||
def paginate_models(page: str, query: BaseQuery) -> PaginationResponse or Tuple[dict, int]:
|
def paginate_models(page: str, query: BaseQuery, per_page = 10) -> PaginationResponse or Tuple[dict, int]:
|
||||||
per_page = 10
|
|
||||||
default_page = 1
|
default_page = 1
|
||||||
|
|
||||||
if page is not None:
|
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_first_name = query.get('order_by_first_name')
|
||||||
order_by_last_name = query.get('order_by_last_name')
|
order_by_last_name = query.get('order_by_last_name')
|
||||||
page = query.get('page')
|
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,
|
student_query = Student.search_by_fullname_and_order_by_first_name_or_last_name(fullname, order_by_first_name,
|
||||||
order_by_last_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:
|
if (message := response.get('message')) is not None:
|
||||||
abort(response['status_code'], message)
|
abort(response['status_code'], message)
|
||||||
# print(get_debug_queries()[0])
|
# 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": "^18.0.9",
|
||||||
"@types/react-dom": "^18.0.4",
|
"@types/react-dom": "^18.0.4",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
"classnames": "^2.3.1",
|
||||||
"daisyui": "^2.15.2",
|
"daisyui": "^2.15.2",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^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",
|
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz",
|
||||||
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA=="
|
"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": {
|
"node_modules/clean-css": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz",
|
||||||
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA=="
|
"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": {
|
"clean-css": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz",
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"@types/react": "^18.0.9",
|
"@types/react": "^18.0.9",
|
||||||
"@types/react-dom": "^18.0.4",
|
"@types/react-dom": "^18.0.4",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
"classnames": "^2.3.1",
|
||||||
"daisyui": "^2.15.2",
|
"daisyui": "^2.15.2",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import axiosInstance from './axiosInstance'
|
import axiosInstance from './axiosInstance'
|
||||||
|
|
||||||
|
type OrderType = 'asc' | 'desc'
|
||||||
|
|
||||||
interface StudentResponse {
|
interface StudentResponse {
|
||||||
max_pages: number
|
max_pages: number
|
||||||
students: Student[]
|
students: Student[]
|
||||||
@ -13,9 +15,18 @@ export interface Student {
|
|||||||
group?: any
|
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>(
|
axiosInstance.get<StudentResponse>(
|
||||||
'http://127.0.0.1:5000/api/coordinator/students',
|
'http://127.0.0.1:5000/api/coordinator/students',
|
||||||
|
{ params },
|
||||||
)
|
)
|
||||||
|
|
||||||
export const createStudent = (payload: Student) =>
|
export const createStudent = (payload: Student) =>
|
||||||
|
@ -4,7 +4,7 @@ const TopBar = () => {
|
|||||||
const linkClass = ({ isActive }: { isActive: boolean }) =>
|
const linkClass = ({ isActive }: { isActive: boolean }) =>
|
||||||
isActive ? 'underline font-bold' : ''
|
isActive ? 'underline font-bold' : ''
|
||||||
return (
|
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>
|
<h1 className="text-xl font-bold">System PRI</h1>
|
||||||
<div className="flex ml-10 gap-3">
|
<div className="flex ml-10 gap-3">
|
||||||
<NavLink className={linkClass} to="/coordinator/groups">
|
<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 { useMutation, useQuery } from 'react-query'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { getStudents, uploadStudents } from '../../api/students'
|
import { getStudents, uploadStudents } from '../../api/students'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
const Students = () => {
|
const Students = () => {
|
||||||
let navigate = useNavigate()
|
let navigate = useNavigate()
|
||||||
const [showGroupless, setShowGroupless] = useState(false)
|
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 {
|
const {
|
||||||
isLoading: isStudentsLoading,
|
isLoading: isStudentsLoading,
|
||||||
data: students,
|
data: students,
|
||||||
refetch: refetchStudents,
|
refetch: refetchStudents,
|
||||||
} = useQuery('students', () => getStudents())
|
} = useQuery(['students', page, perPage], () =>
|
||||||
|
getStudents({ page, per_page: perPage }),
|
||||||
|
)
|
||||||
|
|
||||||
const { mutate: mutateUpload } = useMutation(
|
const { mutate: mutateUpload } = useMutation(
|
||||||
'uploadStudents',
|
'uploadStudents',
|
||||||
@ -28,12 +51,14 @@ const Students = () => {
|
|||||||
mutateUpload(payload)
|
mutateUpload(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isStudentsLoading) {
|
useEffect(() => {
|
||||||
return <div>Ładowanie</div>
|
setPage(1)
|
||||||
}
|
}, [perPage])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
<button
|
<button
|
||||||
className="btn btn-success"
|
className="btn btn-success"
|
||||||
onClick={() => navigate('/coordinator/add-student')}
|
onClick={() => navigate('/coordinator/add-student')}
|
||||||
@ -51,7 +76,14 @@ const Students = () => {
|
|||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={handleOnChange}
|
onChange={handleOnChange}
|
||||||
/>
|
/>
|
||||||
<label className="label ml-auto">
|
</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>
|
<span className="mr-2">Tylko niezapisani</span>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -60,7 +92,11 @@ const Students = () => {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{isStudentsLoading ? (
|
||||||
|
<div>Ładowanie</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<div className="flex mx-auto mt-5 overflow-hidden overflow-x-auto border border-gray-100 rounded">
|
<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">
|
<table className="min-w-full table table-compact">
|
||||||
<thead>
|
<thead>
|
||||||
@ -87,6 +123,43 @@ const Students = () => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es2015",
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"dom.iterable",
|
"dom.iterable",
|
||||||
|
Loading…
Reference in New Issue
Block a user