Merge branch 'test' into AURORA-1057

This commit is contained in:
Veronika Tsygankova 2026-06-05 12:03:41 +03:00
commit b27b8f3143
6 changed files with 113 additions and 33 deletions

View File

@ -7,9 +7,9 @@ export const UsersApi = {
getById(userId) { getById(userId) {
return api.get(`/users/${userId}`).then((r) => r.data); return api.get(`/users/${userId}`).then((r) => r.data);
}, },
getAll() { getAll(params = {}) {
return api return api
.get("/users/", { params: { limit: 1000, skip: 0, load_orgs: true } }) .get("/users/", { params: { limit: 1000, skip: 0, ...params } })
.then((r) => r.data); .then((r) => r.data);
}, },
update(idUser, data) { update(idUser, data) {

View File

@ -22,9 +22,10 @@ import {
} from './constants/tableConfig'; } from './constants/tableConfig';
import { useRealtime } from './contexts/RealtimeContext'; import { useRealtime } from './contexts/RealtimeContext';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { CircularProgress } from '@mui/material';
const RealtimeTable = ({ formType, formId, sheetName, direction }) => { 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 [globalFilter, setGlobalFilter] = useState('');
const [showColumnFilters, setShowColumnFilters] = useState(false); const [showColumnFilters, setShowColumnFilters] = useState(false);
const [selectedColumn, setSelectedColumn] = useState(); const [selectedColumn, setSelectedColumn] = useState();
@ -65,7 +66,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
const { headerPortalRef, containerRef } = useHeaderPortal(); const { headerPortalRef, containerRef } = useHeaderPortal();
const { sizeMult, setSizeMult, tableScaleStyle, tableWrapperStyle } = const { sizeMult, setSizeMult, tableScaleStyle, tableWrapperStyle } =
useTableScale(containerRef); useTableScale();
const [columnsConfig, setColumnsConfig] = useState(null); const [columnsConfig, setColumnsConfig] = useState(null);
@ -125,11 +126,19 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
columns, columns,
data: data, data: data,
enableRowVirtualization: true, enableRowVirtualization: true,
enableColumnVirtualization: true,
rowVirtualizerOptions: { rowVirtualizerOptions: {
overscan: 50, overscan: 30,
scrollPaddingStart: 0, scrollPaddingStart: 0,
scrollPaddingEnd: 0, scrollPaddingEnd: 0,
}, },
columnVirtualizerOptions: ({ table }) => ({
overscan: 5,
measureElement: (el) => {
const index = Number(el?.getAttribute?.('data-index'));
return table.getVisibleLeafColumns()[index]?.getSize() ?? 0;
},
}),
enableRowSelection: true, enableRowSelection: true,
onRowSelectionChange: setRowSelection, onRowSelectionChange: setRowSelection,
onColumnSizingChange: setColumnSizing, onColumnSizingChange: setColumnSizing,
@ -141,20 +150,15 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
columnVisibility, columnVisibility,
globalFilter, globalFilter,
showColumnFilters, showColumnFilters,
rowSelection rowSelection,
}, },
initialState: { initialState: {
expanded: true expanded: true
}, },
meta: tableMeta, meta: tableMeta,
muiTableHeadCellProps: ({ column }) => ({ muiTableHeadCellProps: {
sx: { sx: { boxSizing: 'border-box' },
width: column.getSize(),
minWidth: column.getSize(),
maxWidth: column.getSize(),
boxSizing: 'border-box',
}, },
}),
muiTableBodyCellProps: getTableHeadCellStyles, muiTableBodyCellProps: getTableHeadCellStyles,
muiTableHeadProps: { muiTableHeadProps: {
sx: { sx: {
@ -231,10 +235,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
setRowSelection({}); setRowSelection({});
}, [rowSelection, table]) }, [rowSelection, table])
if (!data) {
return <div>Загрузка...</div>;
}
return ( return (
<> <>
<SettingsPanel <SettingsPanel
@ -266,6 +266,22 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
> >
<MaterialReactTable table={table} /> <MaterialReactTable table={table} />
{isTableLoading && (
<div
style={{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(255, 255, 255, 0.6)',
zIndex: 2,
}}
>
<CircularProgress />
</div>
)}
{headerPortalRef.current && {headerPortalRef.current &&
createPortal( createPortal(
<TableHead <TableHead

View File

@ -6,15 +6,20 @@ import { UsersApi } from '../../../api/users';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { AutocompleteForChangeOptions } from '../components/AutocompleteForChangeOptions'; import { AutocompleteForChangeOptions } from '../components/AutocompleteForChangeOptions';
export const ModalChangeUsersInSsp = ({ open, onClose, ssp, onEdit }) => { export const ModalChangeUsersInSsp = ({
open,
onClose,
ssp,
onUsersCountChange,
}) => {
const [users, setUsers] = useState([]); const [users, setUsers] = useState([]);
const [usersDataForUpdate, setUsersDataForUpdate] = useState({ const [usersDataForUpdate, setUsersDataForUpdate] = useState({
update: [], update: [],
delete: [], delete: [],
}); });
const [allUsers, setAllUsers] = useState([]); const [allUsers, setAllUsers] = useState([]);
const [loadingUsers, setLoadingUsers] = useState(false); const [loadingUsers, setLoadingUsers] = useState(false);
useEffect(() => { useEffect(() => {
UsersApi.getAll() UsersApi.getAll()
.then((data) => { .then((data) => {
@ -58,14 +63,66 @@ export const ModalChangeUsersInSsp = ({ open, onClose, ssp, onEdit }) => {
}); });
}, [open, ssp]); }, [open, ssp]);
const handleClickSave = () => { const deleteUserFromSsp = async (user) => {
onEdit?.(ssp, usersDataForUpdate); 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?.(); onClose?.();
}; };
const handleChangeUsersList = (dataForUpdate) => { const handleChangeUsersList = (updater) => {
setUsersDataForUpdate(dataForUpdate); setUsersDataForUpdate(updater);
}; };
if (!ssp) return; if (!ssp) return;
return ( return (
<Modal <Modal

View File

@ -376,7 +376,7 @@ const SspsPage = () => {
open={openModalUsersinSspChange} open={openModalUsersinSspChange}
onClose={handleCloseModalUsersInSspChange} onClose={handleCloseModalUsersInSspChange}
ssp={sspForChangeUsers} ssp={sspForChangeUsers}
onEdit={handleUpdateUsers} onUsersCountChange={adjustSspUsersCount}
/> />
<ModalEditSsp <ModalEditSsp
open={openModalEdit} open={openModalEdit}

View File

@ -34,7 +34,7 @@ const UsersPage = () => {
const loadUsers = async () => { const loadUsers = async () => {
setLoading(true); setLoading(true);
try { try {
const data = await UsersApi.getAll(); const data = await UsersApi.getAll({ load_orgs: true });
if (!data.success) { if (!data.success) {
toast.error('Ошибка получения пользователей ' + data.message); toast.error('Ошибка получения пользователей ' + data.message);
return; return;

View File

@ -37,7 +37,7 @@ export const AutocompleteForChangeOptions = ({
if (value) { if (value) {
const newSelected = [...selected, value]; const newSelected = [...selected, value];
setSelected(newSelected); setSelected(newSelected);
onChange?.((prev) => ({ ...prev, update: newSelected })); onChange?.((prev) => ({ ...prev, update: [...prev.update, value] }));
const newAvailable = available.filter((item) => item.id !== value.id); const newAvailable = available.filter((item) => item.id !== value.id);
setAvailable(newAvailable); setAvailable(newAvailable);
@ -49,16 +49,23 @@ export const AutocompleteForChangeOptions = ({
const handleRemove = (id) => { const handleRemove = (id) => {
const itemToRemove = selected.find((s) => s.id === id); const itemToRemove = selected.find((s) => s.id === id);
if (itemToRemove) { if (!itemToRemove) return;
const newSelected = selected.filter((s) => s.id !== id); const newSelected = selected.filter((s) => s.id !== id);
setSelected(newSelected); setSelected(newSelected);
setAvailable([...available, itemToRemove]); setAvailable([...available, itemToRemove]);
const wasInitial = values.some((v) => v.id === id);
if (wasInitial) {
onChange?.((prev) => ({ onChange?.((prev) => ({
...prev, ...prev,
delete: [...prev.delete, itemToRemove], 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) => ( renderOption={(props, option) => (
<li {...props} style={{ padding: '4px 12px', minHeight: '36px' }}> <li {...props} style={{ padding: '4px 12px', minHeight: '36px' }} key={option.id}>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',