feat: Пациенты: Добавлена пагинация на backend и frontend
This commit is contained in:
parent
01a27978e6
commit
67fa9db57a
@ -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)
|
||||
|
||||
@ -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(
|
||||
|
||||
8
api/app/domain/entities/responses/paginated_patient.py
Normal file
8
api/app/domain/entities/responses/paginated_patient.py
Normal 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
|
||||
@ -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)
|
||||
|
||||
@ -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) => ({
|
||||
|
||||
@ -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 = () => {
|
||||
<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}
|
||||
@ -158,7 +154,7 @@ const PatientsPage = () => {
|
||||
|
||||
{patientsData.isLoading ? <LoadingIndicator/> : patientsUI.viewMode === "tile" ? (
|
||||
<List
|
||||
grid={{gutter: 16, column: 3}}
|
||||
grid={{ gutter: 16, column: 3 }}
|
||||
dataSource={patientsUI.filteredPatients}
|
||||
renderItem={patient => (
|
||||
<List.Item>
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
Loading…
x
Reference in New Issue
Block a user