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/components/RealtimeTable/RealtimeTable.jsx b/web/src/components/RealtimeTable/RealtimeTable.jsx
index 39e6f0b..654b88a 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();
@@ -65,7 +66,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 +126,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,
@@ -141,20 +150,15 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
columnVisibility,
globalFilter,
showColumnFilters,
- rowSelection
+ rowSelection,
},
initialState: {
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: {
@@ -231,10 +235,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
setRowSelection({});
}, [rowSelection, table])
- if (!data) {
- return
Загрузка...
;
- }
-
return (
<>
{
>
+ {isTableLoading && (
+
+
+
+ )}
+
{headerPortalRef.current &&
createPortal(
{
+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) => (
-
+