From 342e021211cddf0e519e64260728fee4346ccd23 Mon Sep 17 00:00:00 2001 From: andrei Date: Sat, 7 Jun 2025 15:30:27 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D1=81=D0=BE=D1=80=D1=82=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B0=20=D0=BF=D0=B0=D1=86=D0=B8=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=BF=D0=BE=20=D1=84=D0=B0=D0=BC=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/application/patients_repository.py | 10 +++- api/app/controllers/patients_router.py | 5 +- api/app/infrastructure/patients_service.py | 6 +-- web-app/src/Api/patientsApi.js | 9 +++- .../Pages/PatientsPage/PatientsPage.jsx | 33 +++++-------- .../Pages/PatientsPage/usePatients.js | 10 +++- web-app/src/Redux/Slices/patientsSlice.js | 48 ++++++++----------- 7 files changed, 63 insertions(+), 58 deletions(-) diff --git a/api/app/application/patients_repository.py b/api/app/application/patients_repository.py index b9f5b52..17ba552 100644 --- a/api/app/application/patients_repository.py +++ b/api/app/application/patients_repository.py @@ -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.ext.asyncio import AsyncSession @@ -10,7 +10,8 @@ class PatientsRepository: def __init__(self, db: AsyncSession): 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) 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) result = await self.db.execute(stmt) patients = result.scalars().all() diff --git a/api/app/controllers/patients_router.py b/api/app/controllers/patients_router.py index ebf33e3..eb2dc3a 100644 --- a/api/app/controllers/patients_router.py +++ b/api/app/controllers/patients_router.py @@ -1,3 +1,5 @@ +from typing import Literal + from fastapi import APIRouter, Depends, Query 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_size: int = Query(10, ge=1, le=100, description="Number of patients per page"), 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), user=Depends(get_current_user), ): 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} diff --git a/api/app/infrastructure/patients_service.py b/api/app/infrastructure/patients_service.py index 6b2e033..dad217b 100644 --- a/api/app/infrastructure/patients_service.py +++ b/api/app/infrastructure/patients_service.py @@ -1,4 +1,4 @@ -from typing import Optional, Tuple +from typing import Optional, Tuple, Literal from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession @@ -13,10 +13,10 @@ class PatientsService: def __init__(self, db: AsyncSession): 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]: 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 ( [self.model_to_entity(patient) for patient in patients], total_count diff --git a/web-app/src/Api/patientsApi.js b/web-app/src/Api/patientsApi.js index 4a3b082..d12c781 100644 --- a/web-app/src/Api/patientsApi.js +++ b/web-app/src/Api/patientsApi.js @@ -7,9 +7,14 @@ export const patientsApi = createApi({ tagTypes: ['Patient'], endpoints: (builder) => ({ getPatients: builder.query({ - query: ({ page, pageSize, search }) => ({ + query: ({ page, pageSize, search, sortOrder }) => ({ url: '/patients/', - params: { page, page_size: pageSize, search: search || undefined }, + params: { + page, + page_size: pageSize, + search: search || undefined, + sort_order: sortOrder || undefined, + }, }), providesTags: ['Patients'], }), diff --git a/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx b/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx index 02c659c..b40b41f 100644 --- a/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx +++ b/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx @@ -18,7 +18,7 @@ import { SortAscendingOutlined, SortDescendingOutlined, TableOutlined, - TeamOutlined, + TeamOutlined } from "@ant-design/icons"; import PatientListCard from "../../Dummies/PatientListCard.jsx"; import PatientFormModal from "../../Dummies/PatientFormModal/PatientFormModal.jsx"; @@ -37,24 +37,20 @@ const PatientsPage = () => { title: "Фамилия", dataIndex: "last_name", key: "last_name", - sorter: true, }, { title: "Имя", dataIndex: "first_name", key: "first_name", - sorter: true, }, { title: "Отчество", dataIndex: "patronymic", key: "patronymic", - sorter: true, }, { title: "Дата рождения", dataIndex: "birthday", - sorter: true, render: patientsData.formatDate, }, { @@ -118,26 +114,23 @@ const PatientsPage = () => { placeholder="Поиск пациента" value={patientsData.searchText} onChange={(e) => patientsData.handleSetSearchText(e.target.value)} - onPressEnter={patientsData.handleSearch} style={patientsData.formItemStyle} allowClear onClear={patientsData.handleClearSearch} /> - {patientsData.viewMode === "tile" && ( - - - - - - )} + + + + + { const [tempSearchText, setTempSearchText] = useState(""); 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 секунд 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 handleSetPageSize = (size) => dispatch(setPageSize(size)); const handleSetViewMode = (mode) => dispatch(setViewMode(mode)); @@ -131,6 +135,8 @@ const usePatients = () => { handleClearSearch, handleDeletePatient, handleSetSortOrder, + handleSetCurrentPage, + handleSetPageSize, handleSetViewMode, handleCloseModal, handleAddPatient, diff --git a/web-app/src/Redux/Slices/patientsSlice.js b/web-app/src/Redux/Slices/patientsSlice.js index 7981aa2..afd855e 100644 --- a/web-app/src/Redux/Slices/patientsSlice.js +++ b/web-app/src/Redux/Slices/patientsSlice.js @@ -1,58 +1,50 @@ -import {createSlice} from '@reduxjs/toolkit' -import {cacheInfo} from "../../Utils/cachedInfoUtils.js"; +import { createSlice } from '@reduxjs/toolkit'; const initialState = { - searchText: '', sortOrder: 'asc', viewMode: 'tile', + isModalVisible: false, + selectedPatient: null, currentPage: 1, pageSize: 10, - selectedPatient: null, - isModalVisible: false }; const patientsSlice = createSlice({ name: 'patientsUI', initialState, reducers: { - setSearchText: (state, action) => { - state.searchText = action.payload; - }, - setSortOrder: (state, action) => { + setSortOrder(state, action) { state.sortOrder = action.payload; }, - setViewMode: (state, action) => { + setViewMode(state, action) { state.viewMode = action.payload; - cacheInfo("viewModePatients", action.payload); }, - setCurrentPage: (state, action) => { - state.currentPage = action.payload; - }, - setPageSize: (state, action) => { - state.pageSize = action.payload; - }, - openModal: (state) => { + openModal(state) { state.isModalVisible = true; }, - closeModal: (state) => { + closeModal(state) { state.isModalVisible = false; - state.selectedPatient = null; }, - selectPatient: (state, action) => { + selectPatient(state, action) { state.selectedPatient = action.payload; - } - } -}) + }, + setCurrentPage(state, action) { + state.currentPage = action.payload; + }, + setPageSize(state, action) { + state.pageSize = action.payload; + }, + }, +}); export const { - setSearchText, setSortOrder, setViewMode, - setCurrentPage, - setPageSize, openModal, closeModal, - selectPatient + selectPatient, + setCurrentPage, + setPageSize, } = patientsSlice.actions; export default patientsSlice.reducer; \ No newline at end of file