diff --git a/api/.dockerignore b/api/.dockerignore
index 95d9b1f..329ed4e 100644
--- a/api/.dockerignore
+++ b/api/.dockerignore
@@ -1,3 +1,4 @@
.venv
k8s
-.idea
\ No newline at end of file
+.idea
+.env
\ No newline at end of file
diff --git a/api/app/application/lenses_repository.py b/api/app/application/lenses_repository.py
index d88c519..9061964 100644
--- a/api/app/application/lenses_repository.py
+++ b/api/app/application/lenses_repository.py
@@ -1,6 +1,6 @@
from typing import Sequence, Optional, Tuple, Literal
-from sqlalchemy import select, desc, or_, func
+from sqlalchemy import select, desc, or_, func, String
from sqlalchemy.ext.asyncio import AsyncSession
from app.domain.models import Lens
@@ -11,18 +11,18 @@ class LensesRepository:
self.db = db
async def get_all(
- self,
- skip: int = 0,
- limit: int = 10,
- search: str = None,
- sort_order: Literal["asc", "desc"] = "desc",
- tor: float = None,
- diameter: float = None,
- preset_refraction: float = None,
- periphery_toricity: float = None,
- side: str = "all",
- issued: bool = None,
- trial: float = None,
+ self,
+ skip: int = 0,
+ limit: int = 10,
+ search: str = None,
+ sort_order: Literal["asc", "desc"] = "desc",
+ tor: float = None,
+ diameter: float = None,
+ preset_refraction: float = None,
+ periphery_toricity: float = None,
+ side: str = "all",
+ issued: bool = None,
+ trial: float = None,
) -> Tuple[Sequence[Lens], int]:
stmt = select(Lens)
count_stmt = select(func.count()).select_from(Lens)
@@ -31,24 +31,26 @@ class LensesRepository:
search = f"%{search}%"
stmt = stmt.filter(
or_(
- Lens.tor.cast(str).ilike(search),
- Lens.diameter.cast(str).ilike(search),
- Lens.preset_refraction.cast(str).ilike(search),
- Lens.periphery_toricity.cast(str).ilike(search),
- Lens.side.ilike(search),
- Lens.fvc.cast(str).ilike(search),
- Lens.trial.cast(str).ilike(search),
+ Lens.tor.cast(String).ilike(search),
+ Lens.diameter.cast(String).ilike(search),
+ Lens.preset_refraction.cast(String).ilike(search),
+ Lens.periphery_toricity.cast(String).ilike(search),
+ Lens.side.cast(String).ilike(search),
+ Lens.fvc.cast(String).ilike(search),
+ Lens.trial.cast(String).ilike(search),
+ Lens.esa.cast(String).ilike(search),
)
)
count_stmt = count_stmt.filter(
or_(
- Lens.tor.cast(str).ilike(search),
- Lens.diameter.cast(str).ilike(search),
- Lens.preset_refraction.cast(str).ilike(search),
- Lens.periphery_toricity.cast(str).ilike(search),
- Lens.side.ilike(search),
- Lens.fvc.cast(str).ilike(search),
- Lens.trial.cast(str).ilike(search),
+ Lens.tor.cast(String).ilike(search),
+ Lens.diameter.cast(String).ilike(search),
+ Lens.preset_refraction.cast(String).ilike(search),
+ Lens.periphery_toricity.cast(String).ilike(search),
+ Lens.side.cast(String).ilike(search),
+ Lens.fvc.cast(String).ilike(search),
+ Lens.trial.cast(String).ilike(search),
+ Lens.esa.cast(String).ilike(search),
)
)
diff --git a/api/app/infrastructure/appointment_files_service.py b/api/app/infrastructure/appointment_files_service.py
index 0a3d75c..176a64f 100644
--- a/api/app/infrastructure/appointment_files_service.py
+++ b/api/app/infrastructure/appointment_files_service.py
@@ -3,7 +3,6 @@ import uuid
from typing import Optional
import aiofiles
-import magic
from fastapi import UploadFile, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from starlette.responses import FileResponse
diff --git a/api/k8s/helm/visus-api/templates/deployment.yaml b/api/k8s/helm/visus-api/templates/deployment.yaml
index d0741e2..8b31812 100644
--- a/api/k8s/helm/visus-api/templates/deployment.yaml
+++ b/api/k8s/helm/visus-api/templates/deployment.yaml
@@ -24,6 +24,16 @@ spec:
secretKeyRef:
name: visus-api-secret
key: SECRET_KEY
+ - name: DATABASE_URL
+ valueFrom:
+ secretKeyRef:
+ name: visus-api-secret
+ key: DATABASE_URL
+ - name: ALGORITHM
+ valueFrom:
+ secretKeyRef:
+ name: visus-api-secret
+ key: ALGORITHM
volumeMounts:
- name: uploads-volume
mountPath: {{ .Values.persistence.uploads.containerPath }}
diff --git a/web-app/.dockerignore b/web-app/.dockerignore
index 6c26e73..593eedb 100644
--- a/web-app/.dockerignore
+++ b/web-app/.dockerignore
@@ -3,4 +3,5 @@ npm-debug.log
build
.dockerignore
.git
-k8s
\ No newline at end of file
+k8s
+.env
\ No newline at end of file
diff --git a/web-app/k8s/helm/visus-web/templates/secrets-example.yaml b/web-app/k8s/helm/visus-web/templates/secrets-example.yaml
deleted file mode 100644
index bd58ee5..0000000
--- a/web-app/k8s/helm/visus-web/templates/secrets-example.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-apiVersion: v1
-kind: Secret
-metadata:
- name: visus-api-secret
-type: Opaque
-data:
- SECRET_KEY:
- DATABASE_URL:
diff --git a/web-app/src/Api/lensesApi.js b/web-app/src/Api/lensesApi.js
index 52e932d..1d6953c 100644
--- a/web-app/src/Api/lensesApi.js
+++ b/web-app/src/Api/lensesApi.js
@@ -8,9 +8,30 @@ export const lensesApi = createApi({
tagTypes: ['Lens'],
endpoints: (builder) => ({
getLenses: builder.query({
- query: () => '/lenses/',
- providesTags: ['Lens'],
- refetchOnMountOrArgChange: 5
+ query: ({page, pageSize, search, sortOrder, searchParams}) => ({
+ url: '/lenses/',
+ params: {
+ page,
+ page_size: pageSize,
+ search: search || undefined,
+ sort_order: sortOrder || undefined,
+ tor: searchParams.tor || undefined,
+ diameter: searchParams.diameter || undefined,
+ preset_refraction: searchParams.preset_refraction || undefined,
+ periphery_toricity: searchParams.periphery_toricity || undefined,
+ side: searchParams.side !== 'all' ? searchParams.side : undefined,
+ issued: searchParams.issued !== 'all' ? searchParams.issued : undefined,
+ trial: searchParams.trial || undefined,
+ },
+ }),
+ providesTags: ['Lenses'],
+ transformResponse: (response) => {
+ if (!response || !Array.isArray(response.lenses)) {
+ console.warn('Unexpected lenses API response:', response);
+ return {lenses: [], total_count: 0};
+ }
+ return response;
+ },
}),
getNotIssuedLenses: builder.query({
query: () => '/lenses/not_issued/',
diff --git a/web-app/src/Components/Pages/LensesSetsPage/Components/LensesTab/LensesTab.jsx b/web-app/src/Components/Pages/LensesSetsPage/Components/LensesTab/LensesTab.jsx
index cca4a81..8ff82c5 100644
--- a/web-app/src/Components/Pages/LensesSetsPage/Components/LensesTab/LensesTab.jsx
+++ b/web-app/src/Components/Pages/LensesSetsPage/Components/LensesTab/LensesTab.jsx
@@ -148,8 +148,8 @@ const LensesTab = () => {
lensesData.handleSetSearchText(e.target.value)}
+ value={lensesData.tempSearchText}
+ onChange={(e) => lensesData.handleSetTempSearchText(e.target.value)}
style={lensesData.formItemStyle}
allowClear
/>
diff --git a/web-app/src/Components/Pages/LensesSetsPage/Components/LensesTab/useLenses.js b/web-app/src/Components/Pages/LensesSetsPage/Components/LensesTab/useLenses.js
index 9bc8923..786da08 100644
--- a/web-app/src/Components/Pages/LensesSetsPage/Components/LensesTab/useLenses.js
+++ b/web-app/src/Components/Pages/LensesSetsPage/Components/LensesTab/useLenses.js
@@ -1,4 +1,4 @@
-import { useEffect, useMemo } from "react";
+import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { notification } from "antd";
import {
@@ -16,14 +16,14 @@ import {
setSearchParams,
setSearchText,
setShowAdvancedSearch,
- setViewMode
+ setViewMode,
+ setSortOrder
} from "../../../../../Redux/Slices/lensesSlice.js";
import { getCachedInfo } from "../../../../../Utils/cachedInfoUtils.js";
const useLenses = () => {
const dispatch = useDispatch();
const {
- searchText,
viewMode,
currentPage,
pageSize,
@@ -31,16 +31,36 @@ const useLenses = () => {
isModalVisible,
showAdvancedSearch,
searchParams,
+ sortOrder,
} = useSelector(state => state.lensesUI);
- const { data = { lenses: [], total_count: 0 }, isLoading, isError } = useGetLensesQuery(
- { page: currentPage, pageSize, search: searchText || undefined },
+ const [tempSearchText, setTempSearchText] = useState("");
+
+ const { data = { lenses: [], total_count: 0 }, isLoading, isError, error } = useGetLensesQuery(
+ {
+ page: currentPage,
+ pageSize,
+ search: tempSearchText || undefined,
+ sortOrder,
+ searchParams,
+ },
{
pollingInterval: 20000,
refetchOnMountOrArgChange: true,
}
);
+ useEffect(() => {
+ if (isError) {
+ console.error('useGetLensesQuery error:', error);
+ notification.error({
+ message: "Ошибка загрузки данных",
+ description: error?.data?.detail || "Не удалось загрузить линзы",
+ placement: "topRight",
+ });
+ }
+ }, [isError, error]);
+
const [addLens, { isLoading: isAdding }] = useAddLensMutation();
const [updateLens, { isLoading: isUpdating }] = useUpdateLensMutation();
const [deleteLens, { isLoading: isDeleting }] = useDeleteLensMutation();
@@ -58,9 +78,24 @@ const useLenses = () => {
const tableActionButtonsStyle = { display: "flex", gap: "8px" };
const advancedSearchCardStyle = { marginBottom: 20 };
- const handleSetSearchText = (value) => {
- dispatch(setSearchText(value));
- dispatch(setCurrentPage(1)); // Сбрасываем на первую страницу при изменении поиска
+ const handleSetTempSearchText = (value) => {
+ setTempSearchText(value);
+ };
+
+ const handleSearch = () => {
+ dispatch(setSearchText(tempSearchText));
+ dispatch(setCurrentPage(1));
+ };
+
+ const handleClearSearch = () => {
+ setTempSearchText('');
+ dispatch(setSearchText(''));
+ dispatch(setCurrentPage(1));
+ };
+
+ const handleSetSortOrder = (value) => {
+ dispatch(setSortOrder(value));
+ dispatch(setCurrentPage(1));
};
const handleCloseModal = () => dispatch(closeModal());
@@ -85,6 +120,7 @@ const useLenses = () => {
} else {
dispatch(setSearchParams({ ...searchParams, [param]: value }));
}
+ dispatch(setCurrentPage(1));
};
const toggleAdvancedSearch = () => {
@@ -140,34 +176,10 @@ const useLenses = () => {
}
};
- const filteredLenses = useMemo(() => {
- return data.lenses.filter((lens) => {
- const textMatch = Object.values(lens).some((value) =>
- value?.toString().toLowerCase().includes(searchText.toLowerCase())
- );
-
- const advancedMatch = Object.entries(searchParams).every(([key, value]) => {
- if (value === null || value === '') return true;
- if (key === 'side') {
- if (value === 'all') return true;
- return lens.side === value;
- }
- if (key === 'issued') {
- return lens.issued === value || value === "all";
- }
- return lens[key] === value;
- });
-
- return textMatch && advancedMatch && (searchParams.issued || lens.issued === false);
- }).sort((a, b) => {
- return b.id - a.id; // Сортировка по убыванию id
- });
- }, [data.lenses, searchText, searchParams]);
-
const pagination = {
current: currentPage,
pageSize: pageSize,
- total: data.total_count,
+ total: data.total_count || 0,
showSizeChanger: true,
pageSizeOptions: ["5", "10", "20", "50"],
onChange: (page, newPageSize) => {
@@ -176,12 +188,12 @@ const useLenses = () => {
};
return {
- lenses: data.lenses,
- filteredLenses: filteredLenses.map(lens => ({ ...lens, key: lens.id })),
+ lenses: data.lenses || [],
+ filteredLenses: (data.lenses || []).map(lens => ({ ...lens, key: lens.id })),
isLoading,
isError,
isProcessing: isAdding || isUpdating || isDeleting,
- searchText,
+ tempSearchText,
viewMode,
currentPage,
pageSize,
@@ -189,6 +201,7 @@ const useLenses = () => {
isModalVisible,
showAdvancedSearch,
searchParams,
+ sortOrder,
pagination,
containerStyle,
filterBarStyle,
@@ -196,7 +209,10 @@ const useLenses = () => {
viewModIconStyle,
tableActionButtonsStyle,
advancedSearchCardStyle,
- handleSetSearchText,
+ handleSetTempSearchText,
+ handleSearch,
+ handleClearSearch,
+ handleSetSortOrder,
handleAddLens,
handleEditLens,
handleCloseModal,
diff --git a/web-app/src/Redux/Slices/lensesSlice.js b/web-app/src/Redux/Slices/lensesSlice.js
index b4930a3..5060299 100644
--- a/web-app/src/Redux/Slices/lensesSlice.js
+++ b/web-app/src/Redux/Slices/lensesSlice.js
@@ -1,12 +1,11 @@
-import {createSlice} from '@reduxjs/toolkit'
-import {cacheInfo} from "../../Utils/cachedInfoUtils.js";
-
+import { createSlice } from '@reduxjs/toolkit';
const initialState = {
searchText: '',
viewMode: 'tile',
currentPage: 1,
- pageSize: 10,
+ pageSize: 5,
+ sortOrder: 'desc',
selectedLens: null,
isModalVisible: false,
showAdvancedSearch: false,
@@ -16,57 +15,59 @@ const initialState = {
preset_refraction: null,
periphery_toricity: null,
side: 'all',
- issued: false,
- trial: null
+ issued: 'all',
+ trial: null,
},
};
const lensesSlice = createSlice({
- name: 'lensesUI',
+ name: 'lenses',
initialState,
reducers: {
- setSearchText: (state, action) => {
+ setSearchText(state, action) {
state.searchText = action.payload;
},
- setCurrentPage: (state, action) => {
+ setSortOrder(state, action) {
+ state.sortOrder = action.payload;
+ },
+ setViewMode(state, action) {
+ state.viewMode = action.payload;
+ },
+ setCurrentPage(state, action) {
state.currentPage = action.payload;
},
- setPageSize: (state, action) => {
+ setPageSize(state, action) {
state.pageSize = action.payload;
},
- setViewMode: (state, action) => {
- state.viewMode = action.payload;
- cacheInfo("viewModeLenses", action.payload);
- },
- openModal: (state) => {
- state.isModalVisible = true;
- },
- closeModal: (state) => {
- state.isModalVisible = false;
- state.selectedLens = null;
- },
- selectLens: (state, action) => {
+ selectLens(state, action) {
state.selectedLens = action.payload;
},
- setSearchParams: (state, action) => {
- state.searchParams = action.payload;
+ openModal(state) {
+ state.isModalVisible = true;
},
- setShowAdvancedSearch: (state, action) => {
+ closeModal(state) {
+ state.isModalVisible = false;
+ },
+ setShowAdvancedSearch(state, action) {
state.showAdvancedSearch = action.payload;
},
- }
+ setSearchParams(state, action) {
+ state.searchParams = action.payload;
+ },
+ },
});
export const {
setSearchText,
+ setSortOrder,
+ setViewMode,
setCurrentPage,
setPageSize,
- setViewMode,
+ selectLens,
openModal,
closeModal,
- selectLens,
+ setShowAdvancedSearch,
setSearchParams,
- setShowAdvancedSearch
} = lensesSlice.actions;
export default lensesSlice.reducer;
\ No newline at end of file