feat: Добавлена сортировка пациентов по фамилии
This commit is contained in:
parent
1d8888da87
commit
342e021211
@ -1,4 +1,4 @@
|
|||||||
from typing import Sequence, Optional, Tuple
|
from typing import Sequence, Optional, Tuple, Literal
|
||||||
|
|
||||||
from sqlalchemy import select, func, or_
|
from sqlalchemy import select, func, or_
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@ -10,7 +10,8 @@ class PatientsRepository:
|
|||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
async def get_all(self, skip: int = 0, limit: int = 10, search: str = None) -> Tuple[Sequence[Patient], int]:
|
async def get_all(self, skip: int = 0, limit: int = 10, search: str = None,
|
||||||
|
sort_order: Literal["asc", "desc"] = "asc") -> Tuple[Sequence[Patient], int]:
|
||||||
stmt = select(Patient)
|
stmt = select(Patient)
|
||||||
|
|
||||||
if search:
|
if search:
|
||||||
@ -25,6 +26,11 @@ class PatientsRepository:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if sort_order == "desc":
|
||||||
|
stmt = stmt.order_by(Patient.last_name.desc())
|
||||||
|
else:
|
||||||
|
stmt = stmt.order_by(Patient.last_name.asc())
|
||||||
|
|
||||||
stmt = stmt.offset(skip).limit(limit)
|
stmt = stmt.offset(skip).limit(limit)
|
||||||
result = await self.db.execute(stmt)
|
result = await self.db.execute(stmt)
|
||||||
patients = result.scalars().all()
|
patients = result.scalars().all()
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Literal
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
@ -20,11 +22,12 @@ async def get_all_patients(
|
|||||||
page: int = Query(1, ge=1, description="Page number"),
|
page: int = Query(1, ge=1, description="Page number"),
|
||||||
page_size: int = Query(10, ge=1, le=100, description="Number of patients per page"),
|
page_size: int = Query(10, ge=1, le=100, description="Number of patients per page"),
|
||||||
search: str = Query(None, description="Search term for filtering patients"),
|
search: str = Query(None, description="Search term for filtering patients"),
|
||||||
|
sort_order: Literal["asc", "desc"] = Query("asc", description="Sort order by first_name (asc or desc)"),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
):
|
):
|
||||||
patients_service = PatientsService(db)
|
patients_service = PatientsService(db)
|
||||||
patients, total_count = await patients_service.get_all_patients(page, page_size, search)
|
patients, total_count = await patients_service.get_all_patients(page, page_size, search, sort_order)
|
||||||
return {"patients": patients, "total_count": total_count}
|
return {"patients": patients, "total_count": total_count}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple, Literal
|
||||||
|
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@ -13,10 +13,10 @@ class PatientsService:
|
|||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
self.patient_repository = PatientsRepository(db)
|
self.patient_repository = PatientsRepository(db)
|
||||||
|
|
||||||
async def get_all_patients(self, page: int = 1, page_size: int = 10, search: str = None) -> Tuple[
|
async def get_all_patients(self, page: int = 1, page_size: int = 10, search: str = None, sort_order: Literal["asc", "desc"] = "asc") -> Tuple[
|
||||||
list[PatientEntity], int]:
|
list[PatientEntity], int]:
|
||||||
skip = (page - 1) * page_size
|
skip = (page - 1) * page_size
|
||||||
patients, total_count = await self.patient_repository.get_all(skip=skip, limit=page_size, search=search)
|
patients, total_count = await self.patient_repository.get_all(skip=skip, limit=page_size, search=search, sort_order=sort_order)
|
||||||
return (
|
return (
|
||||||
[self.model_to_entity(patient) for patient in patients],
|
[self.model_to_entity(patient) for patient in patients],
|
||||||
total_count
|
total_count
|
||||||
|
|||||||
@ -7,9 +7,14 @@ export const patientsApi = createApi({
|
|||||||
tagTypes: ['Patient'],
|
tagTypes: ['Patient'],
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
getPatients: builder.query({
|
getPatients: builder.query({
|
||||||
query: ({ page, pageSize, search }) => ({
|
query: ({ page, pageSize, search, sortOrder }) => ({
|
||||||
url: '/patients/',
|
url: '/patients/',
|
||||||
params: { page, page_size: pageSize, search: search || undefined },
|
params: {
|
||||||
|
page,
|
||||||
|
page_size: pageSize,
|
||||||
|
search: search || undefined,
|
||||||
|
sort_order: sortOrder || undefined,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
providesTags: ['Patients'],
|
providesTags: ['Patients'],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import {
|
|||||||
SortAscendingOutlined,
|
SortAscendingOutlined,
|
||||||
SortDescendingOutlined,
|
SortDescendingOutlined,
|
||||||
TableOutlined,
|
TableOutlined,
|
||||||
TeamOutlined,
|
TeamOutlined
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import PatientListCard from "../../Dummies/PatientListCard.jsx";
|
import PatientListCard from "../../Dummies/PatientListCard.jsx";
|
||||||
import PatientFormModal from "../../Dummies/PatientFormModal/PatientFormModal.jsx";
|
import PatientFormModal from "../../Dummies/PatientFormModal/PatientFormModal.jsx";
|
||||||
@ -37,24 +37,20 @@ const PatientsPage = () => {
|
|||||||
title: "Фамилия",
|
title: "Фамилия",
|
||||||
dataIndex: "last_name",
|
dataIndex: "last_name",
|
||||||
key: "last_name",
|
key: "last_name",
|
||||||
sorter: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Имя",
|
title: "Имя",
|
||||||
dataIndex: "first_name",
|
dataIndex: "first_name",
|
||||||
key: "first_name",
|
key: "first_name",
|
||||||
sorter: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Отчество",
|
title: "Отчество",
|
||||||
dataIndex: "patronymic",
|
dataIndex: "patronymic",
|
||||||
key: "patronymic",
|
key: "patronymic",
|
||||||
sorter: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Дата рождения",
|
title: "Дата рождения",
|
||||||
dataIndex: "birthday",
|
dataIndex: "birthday",
|
||||||
sorter: true,
|
|
||||||
render: patientsData.formatDate,
|
render: patientsData.formatDate,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -118,26 +114,23 @@ const PatientsPage = () => {
|
|||||||
placeholder="Поиск пациента"
|
placeholder="Поиск пациента"
|
||||||
value={patientsData.searchText}
|
value={patientsData.searchText}
|
||||||
onChange={(e) => patientsData.handleSetSearchText(e.target.value)}
|
onChange={(e) => patientsData.handleSetSearchText(e.target.value)}
|
||||||
onPressEnter={patientsData.handleSearch}
|
|
||||||
style={patientsData.formItemStyle}
|
style={patientsData.formItemStyle}
|
||||||
allowClear
|
allowClear
|
||||||
onClear={patientsData.handleClearSearch}
|
onClear={patientsData.handleClearSearch}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
{patientsData.viewMode === "tile" && (
|
<Col xs={24} md={5} sm={6} xl={3} xxl={2}>
|
||||||
<Col xs={24} md={5} sm={6} xl={3} xxl={2}>
|
<Tooltip title={"Сортировка пациентов"}>
|
||||||
<Tooltip title={"Сортировка пациентов"}>
|
<Select
|
||||||
<Select
|
value={patientsData.sortOrder}
|
||||||
value={patientsData.sortOrder}
|
onChange={patientsData.handleSetSortOrder}
|
||||||
onChange={patientsData.handleSetSortOrder}
|
style={patientsData.formItemStyle}
|
||||||
style={patientsData.formItemStyle}
|
>
|
||||||
>
|
<Option value="asc"><SortAscendingOutlined/> А-Я</Option>
|
||||||
<Option value="asc"><SortAscendingOutlined/> А-Я</Option>
|
<Option value="desc"><SortDescendingOutlined/> Я-А</Option>
|
||||||
<Option value="desc"><SortDescendingOutlined/> Я-А</Option>
|
</Select>
|
||||||
</Select>
|
</Tooltip>
|
||||||
</Tooltip>
|
</Col>
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
<Col xs={24} md={patientsData.viewMode === "tile" ? 5 : 10}
|
<Col xs={24} md={patientsData.viewMode === "tile" ? 5 : 10}
|
||||||
sm={patientsData.viewMode === "tile" ? 8 : 14}
|
sm={patientsData.viewMode === "tile" ? 8 : 14}
|
||||||
xl={patientsData.viewMode === "tile" ? 3 : 5}
|
xl={patientsData.viewMode === "tile" ? 3 : 5}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ const usePatients = () => {
|
|||||||
const [tempSearchText, setTempSearchText] = useState("");
|
const [tempSearchText, setTempSearchText] = useState("");
|
||||||
|
|
||||||
const { data = { patients: [], total_count: 0 }, isLoading, isError } = useGetPatientsQuery(
|
const { data = { patients: [], total_count: 0 }, isLoading, isError } = useGetPatientsQuery(
|
||||||
{ page: currentPage, pageSize, search: tempSearchText || undefined },
|
{ page: currentPage, pageSize, search: tempSearchText || undefined, sortOrder },
|
||||||
{
|
{
|
||||||
pollingInterval: 20000, // Автообновление каждые 20 секунд
|
pollingInterval: 20000, // Автообновление каждые 20 секунд
|
||||||
refetchOnMountOrArgChange: true, // Обновление при изменении параметров
|
refetchOnMountOrArgChange: true, // Обновление при изменении параметров
|
||||||
@ -76,7 +76,11 @@ const usePatients = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetSortOrder = (value) => dispatch(setSortOrder(value));
|
const handleSetSortOrder = (value) => {
|
||||||
|
dispatch(setSortOrder(value));
|
||||||
|
dispatch(setCurrentPage(1)); // Сбрасываем на первую страницу при изменении сортировки
|
||||||
|
};
|
||||||
|
|
||||||
const handleSetCurrentPage = (page) => dispatch(setCurrentPage(page));
|
const handleSetCurrentPage = (page) => dispatch(setCurrentPage(page));
|
||||||
const handleSetPageSize = (size) => dispatch(setPageSize(size));
|
const handleSetPageSize = (size) => dispatch(setPageSize(size));
|
||||||
const handleSetViewMode = (mode) => dispatch(setViewMode(mode));
|
const handleSetViewMode = (mode) => dispatch(setViewMode(mode));
|
||||||
@ -131,6 +135,8 @@ const usePatients = () => {
|
|||||||
handleClearSearch,
|
handleClearSearch,
|
||||||
handleDeletePatient,
|
handleDeletePatient,
|
||||||
handleSetSortOrder,
|
handleSetSortOrder,
|
||||||
|
handleSetCurrentPage,
|
||||||
|
handleSetPageSize,
|
||||||
handleSetViewMode,
|
handleSetViewMode,
|
||||||
handleCloseModal,
|
handleCloseModal,
|
||||||
handleAddPatient,
|
handleAddPatient,
|
||||||
|
|||||||
@ -1,58 +1,50 @@
|
|||||||
import {createSlice} from '@reduxjs/toolkit'
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import {cacheInfo} from "../../Utils/cachedInfoUtils.js";
|
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
searchText: '',
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
viewMode: 'tile',
|
viewMode: 'tile',
|
||||||
|
isModalVisible: false,
|
||||||
|
selectedPatient: null,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
selectedPatient: null,
|
|
||||||
isModalVisible: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const patientsSlice = createSlice({
|
const patientsSlice = createSlice({
|
||||||
name: 'patientsUI',
|
name: 'patientsUI',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setSearchText: (state, action) => {
|
setSortOrder(state, action) {
|
||||||
state.searchText = action.payload;
|
|
||||||
},
|
|
||||||
setSortOrder: (state, action) => {
|
|
||||||
state.sortOrder = action.payload;
|
state.sortOrder = action.payload;
|
||||||
},
|
},
|
||||||
setViewMode: (state, action) => {
|
setViewMode(state, action) {
|
||||||
state.viewMode = action.payload;
|
state.viewMode = action.payload;
|
||||||
cacheInfo("viewModePatients", action.payload);
|
|
||||||
},
|
},
|
||||||
setCurrentPage: (state, action) => {
|
openModal(state) {
|
||||||
state.currentPage = action.payload;
|
|
||||||
},
|
|
||||||
setPageSize: (state, action) => {
|
|
||||||
state.pageSize = action.payload;
|
|
||||||
},
|
|
||||||
openModal: (state) => {
|
|
||||||
state.isModalVisible = true;
|
state.isModalVisible = true;
|
||||||
},
|
},
|
||||||
closeModal: (state) => {
|
closeModal(state) {
|
||||||
state.isModalVisible = false;
|
state.isModalVisible = false;
|
||||||
state.selectedPatient = null;
|
|
||||||
},
|
},
|
||||||
selectPatient: (state, action) => {
|
selectPatient(state, action) {
|
||||||
state.selectedPatient = action.payload;
|
state.selectedPatient = action.payload;
|
||||||
}
|
},
|
||||||
}
|
setCurrentPage(state, action) {
|
||||||
})
|
state.currentPage = action.payload;
|
||||||
|
},
|
||||||
|
setPageSize(state, action) {
|
||||||
|
state.pageSize = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
setSearchText,
|
|
||||||
setSortOrder,
|
setSortOrder,
|
||||||
setViewMode,
|
setViewMode,
|
||||||
setCurrentPage,
|
|
||||||
setPageSize,
|
|
||||||
openModal,
|
openModal,
|
||||||
closeModal,
|
closeModal,
|
||||||
selectPatient
|
selectPatient,
|
||||||
|
setCurrentPage,
|
||||||
|
setPageSize,
|
||||||
} = patientsSlice.actions;
|
} = patientsSlice.actions;
|
||||||
|
|
||||||
export default patientsSlice.reducer;
|
export default patientsSlice.reducer;
|
||||||
Loading…
x
Reference in New Issue
Block a user