feat: Пациенты: Добавлена пагинация на backend и frontend

This commit is contained in:
Андрей Дувакин 2025-06-07 14:48:45 +05:00
parent 01a27978e6
commit 67fa9db57a
8 changed files with 70 additions and 57 deletions

View File

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

View File

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

View File

@ -0,0 +1,8 @@
from pydantic import BaseModel
from app.domain.entities.patient import PatientEntity
class PaginatedPatientsResponseEntity(BaseModel):
patients: list[PatientEntity]
total_count: int

View File

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

View File

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

View File

@ -30,34 +30,31 @@ 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 = () => {
<Col xs={24} xl={12}>
<Button block onClick={() => patientsUI.handleEditPatient(record)}>Изменить</Button>
</Col>
<Col xs={24} xl={12}>
<Popconfirm
title="Вы уверены, что хотите удалить пациента?"
@ -113,6 +109,8 @@ const PatientsPage = () => {
/>
);
console.log(patientsData.isLoading)
return (
<div style={patientsUI.containerStyle}>
<Title level={1}><TeamOutlined/> Пациенты</Title>
@ -126,7 +124,6 @@ const PatientsPage = () => {
allowClear
/>
</Col>
{patientsUI.viewMode === "tile" && (
<Col xs={24} md={5} sm={6} xl={3} xxl={2}>
<Tooltip title={"Сортировка пациентов"}>
@ -141,7 +138,6 @@ const PatientsPage = () => {
</Tooltip>
</Col>
)}
<Col xs={24} md={patientsUI.viewMode === "tile" ? 5 : 10}
sm={patientsUI.viewMode === "tile" ? 8 : 14}
xl={patientsUI.viewMode === "tile" ? 3 : 5}

View File

@ -3,11 +3,19 @@ import {
useDeletePatientMutation,
useGetPatientsQuery,
} from "../../../Api/patientsApi.js";
import { useSelector } from "react-redux";
const usePatients = () => {
const {data: patients = [], isLoading, isError} = useGetPatientsQuery(undefined, {
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,

View File

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