feat: Добавлена сортировка пациентов по фамилии

This commit is contained in:
Андрей Дувакин 2025-06-07 15:30:27 +05:00
parent 1d8888da87
commit 342e021211
7 changed files with 63 additions and 58 deletions

View File

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

View File

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

View File

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

View File

@ -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'],
}), }),

View File

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

View File

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

View File

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