From 43703866aa623fbb6f42653ca801f0440721d014 Mon Sep 17 00:00:00 2001 From: Veronika Tsygankova Date: Wed, 3 Jun 2026 11:49:06 +0300 Subject: [PATCH 1/4] =?UTF-8?q?[AURORA-1060]=20-=20=D0=92=D0=B8=D1=80?= =?UTF-8?q?=D1=83=D1=82=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=B4=D0=B5=D1=80=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RealtimeTable/RealtimeTable.jsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/web/src/components/RealtimeTable/RealtimeTable.jsx b/web/src/components/RealtimeTable/RealtimeTable.jsx index 39e6f0b..b67bcc2 100644 --- a/web/src/components/RealtimeTable/RealtimeTable.jsx +++ b/web/src/components/RealtimeTable/RealtimeTable.jsx @@ -65,7 +65,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => { const { headerPortalRef, containerRef } = useHeaderPortal(); const { sizeMult, setSizeMult, tableScaleStyle, tableWrapperStyle } = - useTableScale(containerRef); + useTableScale(); const [columnsConfig, setColumnsConfig] = useState(null); @@ -125,11 +125,19 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => { columns, data: data, enableRowVirtualization: true, + enableColumnVirtualization: true, rowVirtualizerOptions: { - overscan: 50, + overscan: 30, scrollPaddingStart: 0, scrollPaddingEnd: 0, }, + columnVirtualizerOptions: ({ table }) => ({ + overscan: 5, + measureElement: (el) => { + const index = Number(el?.getAttribute?.('data-index')); + return table.getVisibleLeafColumns()[index]?.getSize() ?? 0; + }, + }), enableRowSelection: true, onRowSelectionChange: setRowSelection, onColumnSizingChange: setColumnSizing, @@ -147,14 +155,9 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => { expanded: true }, meta: tableMeta, - muiTableHeadCellProps: ({ column }) => ({ - sx: { - width: column.getSize(), - minWidth: column.getSize(), - maxWidth: column.getSize(), - boxSizing: 'border-box', - }, - }), + muiTableHeadCellProps: { + sx: { boxSizing: 'border-box' }, + }, muiTableBodyCellProps: getTableHeadCellStyles, muiTableHeadProps: { sx: { From 6419b5ad1d204f108486f46cae9be6c0832eb42a Mon Sep 17 00:00:00 2001 From: Veronika Tsygankova Date: Thu, 4 Jun 2026 15:11:15 +0300 Subject: [PATCH 2/4] =?UTF-8?q?[AURORA-1070]=20-=20=D0=9E=D1=88=D0=B8?= =?UTF-8?q?=D0=B1=D0=BA=D0=B0=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BB?= =?UTF-8?q?=D0=B8=D1=87=D0=B5=D1=81=D1=82=D0=B2=D0=B0=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D0=B5=D0=B9=20?= =?UTF-8?q?=D0=B2=20=D0=9F=D0=BE=D0=B4=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=A1=D0=9F=D0=9F/=D0=A0=D0=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/api/users.js | 4 +- .../SppsPage/ModalChangeUsersInSsp.jsx | 69 +++++++++++++++++-- .../pages/AdminPanel/SppsPage/SppsPage.jsx | 2 +- .../pages/AdminPanel/UsersPage/UsersPage.jsx | 2 +- .../AutocompleteForChangeOptions.jsx | 21 ++++-- 5 files changed, 81 insertions(+), 17 deletions(-) diff --git a/web/src/api/users.js b/web/src/api/users.js index 9ee1126..22b7406 100644 --- a/web/src/api/users.js +++ b/web/src/api/users.js @@ -7,9 +7,9 @@ export const UsersApi = { getById(userId) { return api.get(`/users/${userId}`).then((r) => r.data); }, - getAll() { + getAll(params = {}) { return api - .get("/users/", { params: { limit: 1000, skip: 0, load_orgs: true } }) + .get("/users/", { params: { limit: 1000, skip: 0, ...params } }) .then((r) => r.data); }, update(idUser, data) { diff --git a/web/src/pages/AdminPanel/SppsPage/ModalChangeUsersInSsp.jsx b/web/src/pages/AdminPanel/SppsPage/ModalChangeUsersInSsp.jsx index 8845a9e..df7e9fc 100644 --- a/web/src/pages/AdminPanel/SppsPage/ModalChangeUsersInSsp.jsx +++ b/web/src/pages/AdminPanel/SppsPage/ModalChangeUsersInSsp.jsx @@ -6,15 +6,20 @@ import { UsersApi } from '../../../api/users'; import { toast } from 'react-toastify'; import { AutocompleteForChangeOptions } from '../components/AutocompleteForChangeOptions'; -export const ModalChangeUsersInSsp = ({ open, onClose, ssp, onEdit }) => { +export const ModalChangeUsersInSsp = ({ + open, + onClose, + ssp, + onUsersCountChange, +}) => { const [users, setUsers] = useState([]); const [usersDataForUpdate, setUsersDataForUpdate] = useState({ update: [], delete: [], }); - const [allUsers, setAllUsers] = useState([]); const [loadingUsers, setLoadingUsers] = useState(false); + useEffect(() => { UsersApi.getAll() .then((data) => { @@ -58,14 +63,66 @@ export const ModalChangeUsersInSsp = ({ open, onClose, ssp, onEdit }) => { }); }, [open, ssp]); - const handleClickSave = () => { - onEdit?.(ssp, usersDataForUpdate); + const deleteUserFromSsp = async (user) => { + try { + const data = await SspApi.deleteManySsps(user.id, { ssp_ids: [ssp.id] }); + if (!data.success) { + toast.error(data?.message || 'Ошибка удаления пользователя из ССП'); + return false; + } + return true; + } catch (error) { + const msg = + error?.response?.data?.message || error?.response?.data?.detail; + toast.error(msg || 'Ошибка удаления пользователя из ССП'); + return false; + } + }; + + const addUserToSsp = async (user) => { + try { + const data = await SspApi.putManySsps(user.id, { ssp_ids: [ssp.id] }); + if (!data.success) { + toast.error(data?.message || 'Ошибка добавления пользователя'); + return false; + } + return true; + } catch (error) { + const msg = + error?.response?.data?.message || error?.response?.data?.detail; + toast.error(msg || 'Ошибка добавления пользователя'); + return false; + } + }; + + const handleClickSave = async () => { + let added = 0; + let removed = 0; + + for (const user of usersDataForUpdate.update) { + if (await addUserToSsp(user)) { + added += 1; + } + } + + for (const user of usersDataForUpdate.delete) { + if (await deleteUserFromSsp(user)) { + removed += 1; + } + } + + const delta = added - removed; + if (delta !== 0) { + onUsersCountChange?.(ssp.id, delta); + } + onClose?.(); }; - const handleChangeUsersList = (dataForUpdate) => { - setUsersDataForUpdate(dataForUpdate); + const handleChangeUsersList = (updater) => { + setUsersDataForUpdate(updater); }; + if (!ssp) return; return ( { open={openModalUsersinSspChange} onClose={handleCloseModalUsersInSspChange} ssp={sspForChangeUsers} - onEdit={handleUpdateUsers} + onUsersCountChange={adjustSspUsersCount} /> { const loadUsers = async () => { setLoading(true); try { - const data = await UsersApi.getAll(); + const data = await UsersApi.getAll({ load_orgs: true }); if (!data.success) { toast.error('Ошибка получения пользователей ' + data.message); return; diff --git a/web/src/pages/AdminPanel/components/AutocompleteForChangeOptions.jsx b/web/src/pages/AdminPanel/components/AutocompleteForChangeOptions.jsx index 9cdd13b..217eacc 100644 --- a/web/src/pages/AdminPanel/components/AutocompleteForChangeOptions.jsx +++ b/web/src/pages/AdminPanel/components/AutocompleteForChangeOptions.jsx @@ -37,7 +37,7 @@ export const AutocompleteForChangeOptions = ({ if (value) { const newSelected = [...selected, value]; setSelected(newSelected); - onChange?.((prev) => ({ ...prev, update: newSelected })); + onChange?.((prev) => ({ ...prev, update: [...prev.update, value] })); const newAvailable = available.filter((item) => item.id !== value.id); setAvailable(newAvailable); @@ -49,16 +49,23 @@ export const AutocompleteForChangeOptions = ({ const handleRemove = (id) => { const itemToRemove = selected.find((s) => s.id === id); - if (itemToRemove) { - const newSelected = selected.filter((s) => s.id !== id); - setSelected(newSelected); + if (!itemToRemove) return; - setAvailable([...available, itemToRemove]); + const newSelected = selected.filter((s) => s.id !== id); + setSelected(newSelected); + setAvailable([...available, itemToRemove]); + const wasInitial = values.some((v) => v.id === id); + + if (wasInitial) { onChange?.((prev) => ({ ...prev, delete: [...prev.delete, itemToRemove], - update: [...newSelected], + })); + } else { + onChange?.((prev) => ({ + ...prev, + update: prev.update.filter((u) => u.id !== id), })); } }; @@ -130,7 +137,7 @@ export const AutocompleteForChangeOptions = ({ /> )} renderOption={(props, option) => ( -
  • +
  • Date: Fri, 5 Jun 2026 10:34:34 +0300 Subject: [PATCH 3/4] =?UTF-8?q?[AURORA-1060]=20-=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=BB=D0=BE=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=20=D0=B4=D0=BB=D1=8F=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8?= =?UTF-8?q?=D1=86=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RealtimeTable/RealtimeTable.jsx | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/web/src/components/RealtimeTable/RealtimeTable.jsx b/web/src/components/RealtimeTable/RealtimeTable.jsx index b67bcc2..884aa77 100644 --- a/web/src/components/RealtimeTable/RealtimeTable.jsx +++ b/web/src/components/RealtimeTable/RealtimeTable.jsx @@ -22,9 +22,10 @@ import { } from './constants/tableConfig'; import { useRealtime } from './contexts/RealtimeContext'; import { toast } from 'react-toastify'; +import { CircularProgress } from '@mui/material'; const RealtimeTable = ({ formType, formId, sheetName, direction }) => { - const { data, setData } = useRealtimeData(formId, sheetName, direction); + const { data, setData, isLoading: isTableLoading } = useRealtimeData(formId, sheetName, direction); const [globalFilter, setGlobalFilter] = useState(''); const [showColumnFilters, setShowColumnFilters] = useState(false); const [selectedColumn, setSelectedColumn] = useState(); @@ -149,7 +150,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => { columnVisibility, globalFilter, showColumnFilters, - rowSelection + rowSelection, + isLoading: isTableLoading, }, initialState: { expanded: true @@ -196,6 +198,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => { containerRef, tableMeta, rowSelection, + isTableLoading, ], ); @@ -234,10 +237,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => { setRowSelection({}); }, [rowSelection, table]) - if (!data) { - return
    Загрузка...
    ; - } - return ( <> { > + {isTableLoading && ( +
    + +
    + )} + {headerPortalRef.current && createPortal( Date: Fri, 5 Jun 2026 11:17:10 +0300 Subject: [PATCH 4/4] =?UTF-8?q?[AURORA-1060]=20-=20=D0=BF=D0=BE=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B8=D0=B2=D0=BB=D0=B0=20=D0=BE=D1=82=D0=BE=D0=B1?= =?UTF-8?q?=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BB=D0=BE=D0=B0?= =?UTF-8?q?=D0=B4=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/RealtimeTable/RealtimeTable.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/src/components/RealtimeTable/RealtimeTable.jsx b/web/src/components/RealtimeTable/RealtimeTable.jsx index 884aa77..654b88a 100644 --- a/web/src/components/RealtimeTable/RealtimeTable.jsx +++ b/web/src/components/RealtimeTable/RealtimeTable.jsx @@ -151,7 +151,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => { globalFilter, showColumnFilters, rowSelection, - isLoading: isTableLoading, }, initialState: { expanded: true @@ -198,7 +197,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => { containerRef, tableMeta, rowSelection, - isTableLoading, ], );