diff --git a/api/app/application/patients_repository.py b/api/app/application/patients_repository.py index a1ae985..2962867 100644 --- a/api/app/application/patients_repository.py +++ b/api/app/application/patients_repository.py @@ -1,6 +1,6 @@ -from typing import Sequence, Optional +from typing import Sequence, Optional, Tuple -from sqlalchemy import select +from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession from app.domain.models import Patient @@ -10,10 +10,16 @@ class PatientsRepository: def __init__(self, db: AsyncSession): self.db = db - async def get_all(self) -> Sequence[Patient]: - stmt = select(Patient) + async def get_all(self, skip: int = 0, limit: int = 10) -> Tuple[Sequence[Patient], int]: + stmt = select(Patient).offset(skip).limit(limit) result = await self.db.execute(stmt) - return result.scalars().all() + patients = result.scalars().all() + + count_stmt = select(func.count()).select_from(Patient) + count_result = await self.db.execute(count_stmt) + total_count = count_result.scalar() + + return patients, total_count async def get_by_id(self, patient_id: int) -> Optional[Patient]: stmt = select(Patient).filter_by(id=patient_id) diff --git a/api/app/controllers/patients_router.py b/api/app/controllers/patients_router.py index fbcfa7e..5ff7b86 100644 --- a/api/app/controllers/patients_router.py +++ b/api/app/controllers/patients_router.py @@ -1,8 +1,9 @@ -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, Query from sqlalchemy.ext.asyncio import AsyncSession from app.database.session import get_db from app.domain.entities.patient import PatientEntity +from app.domain.entities.responses.paginated_patient import PaginatedPatientsResponseEntity from app.infrastructure.dependencies import get_current_user from app.infrastructure.patients_service import PatientsService @@ -11,16 +12,19 @@ router = APIRouter() @router.get( "/", - response_model=list[PatientEntity], - summary="Get all patients", - description="Returns a list of all patients", + response_model=PaginatedPatientsResponseEntity, + summary="Get all patients with pagination", + description="Returns a paginated list of patients and total count", ) async def get_all_patients( + page: int = Query(1, ge=1, description="Page number"), + page_size: int = Query(10, ge=1, le=100, description="Number of patients per page"), db: AsyncSession = Depends(get_db), user=Depends(get_current_user), ): patients_service = PatientsService(db) - return await patients_service.get_all_patients() + patients, total_count = await patients_service.get_all_patients(page, page_size) + return {"patients": patients, "total_count": total_count} @router.post( diff --git a/api/app/domain/entities/responses/paginated_patient.py b/api/app/domain/entities/responses/paginated_patient.py new file mode 100644 index 0000000..4b9ef15 --- /dev/null +++ b/api/app/domain/entities/responses/paginated_patient.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel + +from app.domain.entities.patient import PatientEntity + + +class PaginatedPatientsResponseEntity(BaseModel): + patients: list[PatientEntity] + total_count: int diff --git a/api/app/infrastructure/patients_service.py b/api/app/infrastructure/patients_service.py index bb56f29..cb66830 100644 --- a/api/app/infrastructure/patients_service.py +++ b/api/app/infrastructure/patients_service.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Tuple from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession @@ -13,12 +13,13 @@ class PatientsService: def __init__(self, db: AsyncSession): self.patient_repository = PatientsRepository(db) - async def get_all_patients(self) -> list[PatientEntity]: - patients = await self.patient_repository.get_all() - return [ - self.model_to_entity(patient) - for patient in patients - ] + async def get_all_patients(self, page: int = 1, page_size: int = 10) -> Tuple[list[PatientEntity], int]: + skip = (page - 1) * page_size + patients, total_count = await self.patient_repository.get_all(skip=skip, limit=page_size) + return ( + [self.model_to_entity(patient) for patient in patients], + total_count + ) async def create_patient(self, patient: PatientEntity) -> PatientEntity: patient_model = self.entity_to_model(patient) diff --git a/web-app/src/Api/patientsApi.js b/web-app/src/Api/patientsApi.js index 2350417..a740d97 100644 --- a/web-app/src/Api/patientsApi.js +++ b/web-app/src/Api/patientsApi.js @@ -7,9 +7,11 @@ export const patientsApi = createApi({ tagTypes: ['Patient'], endpoints: (builder) => ({ getPatients: builder.query({ - query: () => '/patients/', - providesTags: ['Patient'], - refetchOnMountOrArgChange: 5 + query: ({ page, pageSize }) => ({ + url: '/patients/', + params: { page, page_size: pageSize }, + }), + providesTags: ['Patients'], }), addPatient: builder.mutation({ query: (patient) => ({ diff --git a/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx b/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx index 153de88..0f818b0 100644 --- a/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx +++ b/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx @@ -25,39 +25,36 @@ import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.js import usePatients from "./usePatients.js"; import usePatientsUI from "./usePatientsUI.js"; -const {Option} = Select; -const {Title} = Typography; +const { Option } = Select; +const { Title } = Typography; const PatientsPage = () => { const patientsData = usePatients(); - const patientsUI = usePatientsUI(patientsData.patients); + const patientsUI = usePatientsUI(patientsData.patients, patientsData.totalCount); const columns = [ { title: "Фамилия", dataIndex: "last_name", key: "last_name", - sorter: (a, b) => a.last_name.localeCompare(b.last_name), - sortDirections: ["ascend", "descend"], + sorter: true, // Сортировка будет на сервере, если добавить }, { title: "Имя", dataIndex: "first_name", key: "first_name", - sorter: (a, b) => a.first_name.localeCompare(b.first_name), - sortDirections: ["ascend", "descend"], + sorter: true, }, { title: "Отчество", dataIndex: "patronymic", key: "patronymic", - sortDirections: ["ascend", "descend"], + sorter: true, }, { title: "Дата рождения", dataIndex: "birthday", - sorter: (a, b) => new Date(a.birthday).getTime() - new Date(b.birthday).getTime(), - sortDirections: ["ascend", "descend"], + sorter: true, render: patientsUI.formatDate, }, { @@ -76,7 +73,6 @@ const PatientsPage = () => { - { /> ); + console.log(patientsData.isLoading) + return (
<TeamOutlined/> Пациенты @@ -126,7 +124,6 @@ const PatientsPage = () => { allowClear /> - {patientsUI.viewMode === "tile" && ( @@ -141,7 +138,6 @@ const PatientsPage = () => { )} - { {patientsData.isLoading ? : patientsUI.viewMode === "tile" ? ( ( diff --git a/web-app/src/Components/Pages/PatientsPage/usePatients.js b/web-app/src/Components/Pages/PatientsPage/usePatients.js index 9ffd920..c5419e3 100644 --- a/web-app/src/Components/Pages/PatientsPage/usePatients.js +++ b/web-app/src/Components/Pages/PatientsPage/usePatients.js @@ -1,13 +1,21 @@ -import {notification} from "antd"; +import { notification } from "antd"; import { useDeletePatientMutation, useGetPatientsQuery, } from "../../../Api/patientsApi.js"; +import { useSelector } from "react-redux"; const usePatients = () => { - const {data: patients = [], isLoading, isError} = useGetPatientsQuery(undefined, { - pollingInterval: 20000, - }); + const { currentPage, pageSize } = useSelector(state => state.patientsUI); + + const { data = { patients: [], total_count: 0 }, isLoading, isError } = useGetPatientsQuery( + { page: currentPage, pageSize }, + { + pollingInterval: 20000, + } + ); + + console.log(data); const [deletePatient] = useDeletePatientMutation(); @@ -29,7 +37,8 @@ const usePatients = () => { }; return { - patients, + patients: data.patients, + totalCount: data.total_count, isLoading, isError, handleDeletePatient, diff --git a/web-app/src/Components/Pages/PatientsPage/usePatientsUI.js b/web-app/src/Components/Pages/PatientsPage/usePatientsUI.js index 29c3154..e5ce8cb 100644 --- a/web-app/src/Components/Pages/PatientsPage/usePatientsUI.js +++ b/web-app/src/Components/Pages/PatientsPage/usePatientsUI.js @@ -12,7 +12,7 @@ import { } from "../../../Redux/Slices/patientsSlice.js"; import { getCachedInfo } from "../../../Utils/cachedInfoUtils.js"; -const usePatientsUI = (patients) => { +const usePatientsUI = (patients, totalCount) => { const dispatch = useDispatch(); const { searchText, @@ -56,25 +56,12 @@ const usePatientsUI = (patients) => { dispatch(openModal()); }; - const filteredPatients = useMemo(() => { - return patients - .filter(patient => - Object.values(patient) - .filter(value => typeof value === "string") - .some(value => value.toLowerCase().includes(searchText.toLowerCase())) - ) - .sort((a, b) => { - const fullNameA = `${a.last_name} ${a.first_name}`; - const fullNameB = `${b.last_name} ${b.first_name}`; - return sortOrder === "asc" ? fullNameA.localeCompare(fullNameB) : fullNameB.localeCompare(fullNameA); - }); - }, [patients, searchText, sortOrder]); - const formatDate = (date) => new Date(date).toLocaleDateString(); const pagination = { - currentPage: currentPage, + current: currentPage, pageSize: pageSize, + total: totalCount, showSizeChanger: true, pageSizeOptions: ["5", "10", "20", "50"], onChange: (page, newPageSize) => { @@ -94,7 +81,7 @@ const usePatientsUI = (patients) => { formItemStyle, viewModIconStyle, pagination, - filteredPatients: filteredPatients.map(p => ({ ...p, key: p.id })), + filteredPatients: patients.map(p => ({ ...p, key: p.id })), handleSetSearchText, handleSetSortOrder, handleSetViewMode, @@ -106,4 +93,4 @@ const usePatientsUI = (patients) => { }; }; -export default usePatientsUI; +export default usePatientsUI; \ No newline at end of file