Merge pull request '[AURORA-1061] - Подключить админку к новому бэку' (#30) from AURORA-1061 into test
Reviewed-on: #30
This commit is contained in:
commit
14d2271eb9
@ -1,23 +1,20 @@
|
||||
import api from './client';
|
||||
|
||||
export const SspApi = {
|
||||
get(sspId) {
|
||||
return api.get(`/ssp/${sspId}`).then((r) => r.data);
|
||||
getSsp(sspId, params = {}) {
|
||||
return api.get(`/org-unit/${sspId}`, { params }).then((r) => r.data);
|
||||
},
|
||||
getAll(params = {}) {
|
||||
return api.get(`/ssp/`, { params }).then((r) => r.data);
|
||||
return api.get(`/org-unit/`, { params }).then((r) => r.data);
|
||||
},
|
||||
getCountUsers() {
|
||||
return api.get(`/ssp/count-users/`).then((r) => r.data);
|
||||
updateSsp(sspId, ssp) {
|
||||
return api.patch(`/org-unit/${sspId}`, ssp).then((r) => r.data);
|
||||
},
|
||||
getUsersById(sspId) {
|
||||
return api.get(`/ssp/get-users/${sspId}`).then((r) => r.data);
|
||||
createSsp(ssp) {
|
||||
return api.post(`/org-unit/`, ssp).then((r) => r.data);
|
||||
},
|
||||
update(sspId, ssp) {
|
||||
return api.patch(`/ssp/${sspId}`, ssp).then((r) => r.data);
|
||||
},
|
||||
create(ssp) {
|
||||
return api.put(`/ssp/`, ssp).then((r) => r.data);
|
||||
deleteSsp(sspId) {
|
||||
return api.delete(`/org-unit/${sspId}`).then((r) => r.data);
|
||||
},
|
||||
getManySsp(userId) {
|
||||
return api
|
||||
|
||||
@ -9,7 +9,7 @@ export const UsersApi = {
|
||||
},
|
||||
getAll() {
|
||||
return api
|
||||
.get("/users/", { params: { limit: 1000, skip: 0 } })
|
||||
.get("/users/", { params: { limit: 1000, skip: 0, load_orgs: true } })
|
||||
.then((r) => r.data);
|
||||
},
|
||||
update(idUser, data) {
|
||||
@ -18,8 +18,8 @@ export const UsersApi = {
|
||||
getAllRoles() {
|
||||
return api.get(`/users/roles/`).then((r) => r.data);
|
||||
},
|
||||
add(payload) {
|
||||
return api.put(`/users/`, payload).then((r) => r.data);
|
||||
createUser(payload) {
|
||||
return api.post(`/users/`, payload).then((r) => r.data);
|
||||
},
|
||||
delete(userId) {
|
||||
return api.delete(`/users/${userId}`).then((r) => r.data);
|
||||
|
||||
@ -1,28 +1,58 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { TextField, InputAdornment } from '@mui/material';
|
||||
import { Search } from './icons/icons';
|
||||
|
||||
function SearchComponent({
|
||||
placeholder = 'Поиск...',
|
||||
value,
|
||||
value = '',
|
||||
onChange,
|
||||
debounceMs = 0,
|
||||
height = '2rem',
|
||||
sx: sxProp,
|
||||
...rest
|
||||
}) {
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
const onChangeRef = useRef(onChange);
|
||||
|
||||
useEffect(() => {
|
||||
onChangeRef.current = onChange;
|
||||
}, [onChange]);
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(value);
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!debounceMs) return;
|
||||
const timer = setTimeout(() => {
|
||||
if (inputValue !== value) {
|
||||
onChangeRef.current(inputValue);
|
||||
}
|
||||
}, debounceMs);
|
||||
return () => clearTimeout(timer);
|
||||
}, [inputValue, debounceMs, value]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
onChange(e.target.value);
|
||||
const next = e.target.value;
|
||||
setInputValue(next);
|
||||
if (!debounceMs) {
|
||||
onChange(next);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TextField
|
||||
{...rest}
|
||||
size="small"
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
value={inputValue}
|
||||
onChange={handleChange}
|
||||
sx={{
|
||||
minWidth: '20rem',
|
||||
maxWidth: '100%',
|
||||
width: '100%',
|
||||
flex: 1,
|
||||
...sxProp,
|
||||
'& .MuiOutlinedInput-root': {
|
||||
height: height,
|
||||
maxHeight: '5rem',
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
||||
import { ruRU } from '@mui/x-date-pickers/locales';
|
||||
import ruLocale from 'date-fns/locale/ru';
|
||||
import { AuthProvider } from './app/context/AuthProvider';
|
||||
import theme from './app/theme/theme';
|
||||
import './index.css';
|
||||
@ -12,6 +15,13 @@ const baseName = (process.env.REACT_APP_ROOT_PATH || '/').replace(/\/$/, '');
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<ThemeProvider theme={theme}>
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFns}
|
||||
adapterLocale={ruLocale}
|
||||
localeText={
|
||||
ruRU.components.MuiLocalizationProvider.defaultProps.localeText
|
||||
}
|
||||
>
|
||||
<AuthProvider>
|
||||
<BrowserRouter
|
||||
basename={baseName}
|
||||
@ -24,5 +34,6 @@ createRoot(document.getElementById('root')).render(
|
||||
<ToastContainer autoClose={2000} />
|
||||
</BrowserRouter>
|
||||
</AuthProvider>
|
||||
</LocalizationProvider>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { PageContainer } from '../../StyledForPage';
|
||||
import { HeaderSwitch } from '../../../components/HeaderMenu/HeaderSwitch.jsx';
|
||||
import { SwitchUserSsp } from '../../../components/AdminPanel/SwitchUserSsp';
|
||||
import { ROLES_NAME_ID } from '../../../constants';
|
||||
import { useAuth } from '../../../app/context/AuthProvider';
|
||||
import { Box } from '@mui/material';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
||||
import { toast } from 'react-toastify';
|
||||
import { AuditLogsApi } from '../../../api/audit-logs';
|
||||
import { SspApi } from '../../../api/ssp';
|
||||
@ -71,7 +69,7 @@ const AuditLogsPage = () => {
|
||||
page: page + 1,
|
||||
limit: rowsPerPage,
|
||||
user_id: selectedUser?.id,
|
||||
ssp_id: selectedSsp?.id,
|
||||
org_unit_id: selectedSsp?.id,
|
||||
event_type: selectedEventType?.id,
|
||||
date_from: dateFrom ? dateFrom.toISOString() : undefined,
|
||||
date_to: dateTo ? dateTo.toISOString() : undefined,
|
||||
@ -80,13 +78,8 @@ const AuditLogsPage = () => {
|
||||
};
|
||||
|
||||
const data = await AuditLogsApi.getAll(params);
|
||||
setLogs(data.result);
|
||||
setTotalCount((prev) => {
|
||||
if (data.result.length == rowsPerPage) {
|
||||
return data.result.length + rowsPerPage * (page + 1);
|
||||
}
|
||||
return prev - rowsPerPage + data.result.length;
|
||||
});
|
||||
setLogs(data.result ?? []);
|
||||
setTotalCount(data.count ?? 0);
|
||||
} catch (error) {
|
||||
toast.error('Ошибка получения журнала аудита');
|
||||
console.error(error);
|
||||
@ -118,6 +111,16 @@ const AuditLogsPage = () => {
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
const handleTaskIdChange = useCallback((value) => {
|
||||
setTaskId(value);
|
||||
setPage(0);
|
||||
}, []);
|
||||
|
||||
const handleFormIdChange = useCallback((value) => {
|
||||
setFormId(value);
|
||||
setPage(0);
|
||||
}, []);
|
||||
|
||||
const handleResetFilters = () => {
|
||||
setSelectedUser(null);
|
||||
setSelectedSsp(null);
|
||||
@ -132,7 +135,6 @@ const AuditLogsPage = () => {
|
||||
if (!user || user.role_id !== ROLES_NAME_ID.admin) return null;
|
||||
|
||||
return (
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||
<PageContainer>
|
||||
<HeaderSwitch />
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
@ -156,8 +158,8 @@ const AuditLogsPage = () => {
|
||||
onDateFromChange={setDateFrom}
|
||||
onDateToChange={setDateTo}
|
||||
onResetFilters={handleResetFilters}
|
||||
onTaskIdChange={setTaskId}
|
||||
onFormIdChange={setFormId}
|
||||
onTaskIdChange={handleTaskIdChange}
|
||||
onFormIdChange={handleFormIdChange}
|
||||
/>
|
||||
|
||||
<AuditLogsTable
|
||||
@ -170,7 +172,6 @@ const AuditLogsPage = () => {
|
||||
onRowsPerPageChange={handleRowsPerPageChange}
|
||||
/>
|
||||
</PageContainer>
|
||||
</LocalizationProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -148,10 +148,11 @@ const AuditLogsFilters = ({
|
||||
<FilterFieldWrapper sx={{ flex: 1 }}>
|
||||
<FormLabelStyled>Дата начала</FormLabelStyled>
|
||||
<DatePickerStyled
|
||||
format="dd/MM/yyyy"
|
||||
format="dd.MM.yyyy"
|
||||
value={dateFrom}
|
||||
onChange={(newValue) => onDateFromChange(newValue)}
|
||||
slotProps={{
|
||||
field: { clearable: true },
|
||||
textField: { size: 'small', sx: { width: '100%' } },
|
||||
}}
|
||||
/>
|
||||
@ -160,10 +161,11 @@ const AuditLogsFilters = ({
|
||||
<FilterFieldWrapper sx={{ flex: 1 }}>
|
||||
<FormLabelStyled>Дата окончания</FormLabelStyled>
|
||||
<DatePickerStyled
|
||||
format="dd/MM/yyyy"
|
||||
format="dd.MM.yyyy"
|
||||
value={dateTo}
|
||||
onChange={(newValue) => onDateToChange(newValue)}
|
||||
slotProps={{
|
||||
field: { clearable: true },
|
||||
textField: { size: 'small', sx: { width: '100%' } },
|
||||
}}
|
||||
/>
|
||||
@ -183,6 +185,7 @@ const AuditLogsFilters = ({
|
||||
}}
|
||||
height={'2.56rem'}
|
||||
placeholder="Например: #1234"
|
||||
debounceMs={400}
|
||||
onChange={onTaskIdChange}
|
||||
value={taskId}
|
||||
/>
|
||||
@ -195,6 +198,7 @@ const AuditLogsFilters = ({
|
||||
}}
|
||||
height={'2.56rem'}
|
||||
placeholder="Например: #1234"
|
||||
debounceMs={400}
|
||||
onChange={onFormIdChange}
|
||||
value={formId}
|
||||
/>
|
||||
|
||||
@ -184,8 +184,7 @@ const AuditLogsTable = ({
|
||||
onPageChange={onPageChange}
|
||||
onRowsPerPageChange={onRowsPerPageChange}
|
||||
// Опции высоты
|
||||
containerHeight="calc(100vh - 450px)"
|
||||
minHeight="400px"
|
||||
minHeight='calc(100vh - 29rem)'
|
||||
// Состояние загрузки
|
||||
loading={loading}
|
||||
// Отключаем ненужные функции
|
||||
|
||||
@ -1,164 +0,0 @@
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
Button,
|
||||
Stack,
|
||||
Paper,
|
||||
} from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { DefaultOutlinedButton } from '../../../../components/common/Buttons/Buttons';
|
||||
|
||||
// Стилизованные кнопки для страниц
|
||||
const StyledPageButton = styled(Button)(({ active }) => ({
|
||||
minWidth: '2.5rem',
|
||||
height: '2.56rem',
|
||||
padding: '0',
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 500,
|
||||
transition: 'all 0.2s ease',
|
||||
...(active === 'true'
|
||||
? {
|
||||
background: 'linear-gradient(172deg, #7abc51 0%, #46881c 100%)',
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-1px)',
|
||||
},
|
||||
}
|
||||
: {
|
||||
border: '1px solid #d1d5dc',
|
||||
color: '#374151',
|
||||
'&:hover': {
|
||||
borderColor: '#5FAC43',
|
||||
backgroundColor: 'rgba(95, 172, 67, 0.04)',
|
||||
transform: 'translateY(-1px)',
|
||||
},
|
||||
}),
|
||||
'&:active': {
|
||||
transform: 'translateY(0)',
|
||||
},
|
||||
}));
|
||||
|
||||
const TableFooter = ({
|
||||
totalCount,
|
||||
page,
|
||||
rowsPerPage,
|
||||
onPageChange,
|
||||
onRowsPerPageChange,
|
||||
rowsPerPageOptions = [10, 20, 50, 100],
|
||||
}) => {
|
||||
const totalPages = Math.ceil(totalCount / rowsPerPage);
|
||||
|
||||
const handlePreviousPage = () => {
|
||||
if (page > 0) {
|
||||
onPageChange(page - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
if (page < totalPages - 1) {
|
||||
onPageChange(page + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRowsPerPageChange = (event) => {
|
||||
onRowsPerPageChange(parseInt(event.target.value, 10));
|
||||
};
|
||||
|
||||
const from = page * rowsPerPage + 1;
|
||||
const to = Math.min((page + 1) * rowsPerPage, totalCount);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
sx={{
|
||||
borderTop: '0.53px solid #e5e7eb',
|
||||
backgroundColor: '#F9FAFB',
|
||||
position: 'sticky',
|
||||
bottom: 0,
|
||||
borderRadius: '0 0 0.63rem 0.63rem',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '0.75rem 1rem',
|
||||
flexWrap: 'wrap',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{/* Левая часть - информация о строках */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: '#6B7280',
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
>
|
||||
{`Показано ${from}-${to}`}
|
||||
</Typography>
|
||||
|
||||
<FormControl size="small" sx={{ minWidth: 120 }}>
|
||||
<Select
|
||||
labelId="rows-per-page-label"
|
||||
value={rowsPerPage}
|
||||
onChange={handleRowsPerPageChange}
|
||||
sx={{
|
||||
backgroundColor: 'white',
|
||||
'& .MuiSelect-select': {
|
||||
padding: '0.375rem 2rem 0.375rem 0.75rem',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{rowsPerPageOptions.map((option) => (
|
||||
<MenuItem key={option} value={option}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
||||
{/* Правая часть - навигация */}
|
||||
<Stack direction="row" spacing={2} alignItems="center">
|
||||
<DefaultOutlinedButton
|
||||
onClick={handlePreviousPage}
|
||||
disabled={page === 0}
|
||||
height={'2.56rem'}
|
||||
>
|
||||
Предыдущая
|
||||
</DefaultOutlinedButton>
|
||||
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
{/* Текущая страница */}
|
||||
<StyledPageButton active="true">{page + 1}</StyledPageButton>
|
||||
|
||||
{/* Следующая страница (если есть) */}
|
||||
{page + 1 < totalPages && (
|
||||
<StyledPageButton
|
||||
active="false"
|
||||
onClick={() => onPageChange(page + 1)}
|
||||
>
|
||||
{page + 2}
|
||||
</StyledPageButton>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
<DefaultOutlinedButton
|
||||
onClick={handleNextPage}
|
||||
disabled={page === totalPages - 1 || totalPages === 0}
|
||||
height={'2.56rem'}
|
||||
>
|
||||
Следующая
|
||||
</DefaultOutlinedButton>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableFooter;
|
||||
@ -32,7 +32,7 @@ export async function transformUser(userId) {
|
||||
|
||||
export async function transformSsp(sspId) {
|
||||
try {
|
||||
const ssp = await SspApi.get(sspId);
|
||||
const ssp = await SspApi.getSsp(sspId);
|
||||
if (ssp?.result?.title) {
|
||||
return ssp.result.title;
|
||||
}
|
||||
@ -50,7 +50,7 @@ export async function transformSsps(sspIds) {
|
||||
const res = [];
|
||||
for (const sspId of sspIds) {
|
||||
try {
|
||||
const ssp = await SspApi.get(sspId);
|
||||
const ssp = await SspApi.getSsp(sspId);
|
||||
res.push(ssp?.result?.title || 'РФ/ССП не найден');
|
||||
} catch (error) {
|
||||
res.push('РФ/ССП не найден');
|
||||
|
||||
@ -20,6 +20,14 @@ export const ModalAddSsp = ({ open, onClose, onSave }) => {
|
||||
const [usersData, setUsersData] = useState({ update: [], delete: [] });
|
||||
const [allUsers, setAllUsers] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setTitle('');
|
||||
setType('ssp');
|
||||
setUsersData({ update: [], delete: [] });
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
UsersApi.getAll()
|
||||
.then((data) => {
|
||||
@ -37,7 +45,7 @@ export const ModalAddSsp = ({ open, onClose, onSave }) => {
|
||||
const handleClickSave = () => {
|
||||
const saveSsp = {
|
||||
title: title,
|
||||
is_ssp: type === 'ssp' ? 1 : 0,
|
||||
is_ssp: type === 'ssp',
|
||||
};
|
||||
|
||||
onSave?.(saveSsp, usersData);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Modal from '../../../components/common/Modal/Modal';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import { Box, Button, CircularProgress } from '@mui/material';
|
||||
import { SspApi } from '../../../api/ssp';
|
||||
import { UsersApi } from '../../../api/users';
|
||||
import { toast } from 'react-toastify';
|
||||
@ -14,6 +14,7 @@ export const ModalChangeUsersInSsp = ({ open, onClose, ssp, onEdit }) => {
|
||||
});
|
||||
|
||||
const [allUsers, setAllUsers] = useState([]);
|
||||
const [loadingUsers, setLoadingUsers] = useState(false);
|
||||
useEffect(() => {
|
||||
UsersApi.getAll()
|
||||
.then((data) => {
|
||||
@ -29,15 +30,33 @@ export const ModalChangeUsersInSsp = ({ open, onClose, ssp, onEdit }) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setUsers([]);
|
||||
setUsersDataForUpdate({ update: [], delete: [] });
|
||||
setLoadingUsers(false);
|
||||
return;
|
||||
}
|
||||
if (!ssp) return;
|
||||
SspApi.getUsersById(ssp.id).then((data) => {
|
||||
|
||||
setLoadingUsers(true);
|
||||
setUsers([]);
|
||||
setUsersDataForUpdate({ update: [], delete: [] });
|
||||
|
||||
SspApi.getSsp(ssp.id, { load_users: true })
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
setUsers(data.result);
|
||||
setUsers(data.result?.users ?? []);
|
||||
return;
|
||||
}
|
||||
toast.error('Ошибка получания пользователей ' + data.message);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Ошибка получания пользователей');
|
||||
})
|
||||
.finally(() => {
|
||||
setLoadingUsers(false);
|
||||
});
|
||||
}, [ssp]);
|
||||
}, [open, ssp]);
|
||||
|
||||
const handleClickSave = () => {
|
||||
onEdit?.(ssp, usersDataForUpdate);
|
||||
@ -62,6 +81,7 @@ export const ModalChangeUsersInSsp = ({ open, onClose, ssp, onEdit }) => {
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleClickSave}
|
||||
disabled={loadingUsers}
|
||||
style={{ height: '2.5rem' }}
|
||||
>
|
||||
Сохранить
|
||||
@ -69,13 +89,26 @@ export const ModalChangeUsersInSsp = ({ open, onClose, ssp, onEdit }) => {
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Box maxHeight={'max-content'}>
|
||||
<Box maxHeight={'max-content'} minHeight="8rem">
|
||||
{loadingUsers ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '8rem',
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={32} />
|
||||
</Box>
|
||||
) : (
|
||||
<AutocompleteForChangeOptions
|
||||
options={allUsers}
|
||||
values={users}
|
||||
onChange={handleChangeUsersList}
|
||||
label="Пользователи"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@ -11,8 +11,8 @@ export const ModalConfirmArchiveSsp = ({ open, onClose, ssp, onArchive }) => {
|
||||
|
||||
const handleConfirmArchive = async () => {
|
||||
if (!ssp) return;
|
||||
onArchive?.(ssp);
|
||||
setLoading(true);
|
||||
await onArchive?.(ssp);
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
|
||||
@ -16,11 +16,16 @@ export const ModalEditSsp = ({ open, onClose, ssp, onEdit }) => {
|
||||
const [type, setType] = useState('ssp');
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setTitle('');
|
||||
setType('ssp');
|
||||
return;
|
||||
}
|
||||
if (ssp) {
|
||||
setTitle(ssp.title || '');
|
||||
setType(!ssp.is_ssp ? 'regional' : 'ssp');
|
||||
}
|
||||
}, [ssp]);
|
||||
}, [open, ssp]);
|
||||
|
||||
const handleClickSave = () => {
|
||||
if (!ssp) return;
|
||||
|
||||
@ -27,6 +27,7 @@ import { WhitePlus } from '../../../components/common/icons/icons';
|
||||
const SspsPage = () => {
|
||||
const { user } = useAuth();
|
||||
const [ssps, setSsps] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [sspForChangeUsers, setSspForChangeUsers] = useState(null);
|
||||
const [needUpdateCountUsers, setNeedUpdateCountUsers] = useState(false);
|
||||
const [filterString, setFilterString] = useState('');
|
||||
@ -40,46 +41,27 @@ const SspsPage = () => {
|
||||
const [openModalAdd, setOpenModalAdd] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
SspApi.getAll()
|
||||
.then((data) => {
|
||||
const loadSsps = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await SspApi.getAll({ load_users_count: true });
|
||||
if (!data.success) {
|
||||
toast.error(
|
||||
'Ошибка получения ССП/Региональные филиалы ' + data.message,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const newSsps = data.result.sort((a, b) => a.id - b.id);
|
||||
setSsps(newSsps);
|
||||
setNeedUpdateCountUsers(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
setSsps(data.result.sort((a, b) => a.id - b.id));
|
||||
} catch {
|
||||
toast.error('Ошибка получения ССП/Региональные филиалы ');
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadSsps();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!needUpdateCountUsers) return;
|
||||
if (ssps.length === 0) return;
|
||||
|
||||
SspApi.getCountUsers().then((data) => {
|
||||
if (!data.success) {
|
||||
toast.error(
|
||||
'Ошибка получения ССП/Региональные филиалы ' + data.message,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const counts = data.result;
|
||||
const sspsWithCount = [];
|
||||
for (const ssp of ssps) {
|
||||
const sspWithCount = { ...ssp };
|
||||
sspWithCount['countUser'] = counts[ssp.id];
|
||||
sspsWithCount.push(sspWithCount);
|
||||
}
|
||||
setSsps(sspsWithCount);
|
||||
});
|
||||
setNeedUpdateCountUsers(false);
|
||||
}, [needUpdateCountUsers, ssps.length]);
|
||||
|
||||
useEffect(() => {
|
||||
if (openModalUsersinSspChange && openModalAdd) return;
|
||||
setTimeout(() => {
|
||||
@ -97,26 +79,35 @@ const SspsPage = () => {
|
||||
|
||||
const handleCloseModalUsersInSspChange = () => {
|
||||
setOpenModalUsersinSspChange(false);
|
||||
setSspForChangeUsers(null);
|
||||
};
|
||||
|
||||
const handleChangeStatus = (ssp) => {
|
||||
SspApi.update(ssp.id, ssp)
|
||||
const handleChangeStatus = (ssp) =>
|
||||
SspApi.deleteSsp(ssp.id)
|
||||
.then((data) => {
|
||||
if (!data.success) {
|
||||
toast.error('Не удалось поменять статус ' + data.message);
|
||||
return;
|
||||
}
|
||||
setSsps((prev) => {
|
||||
const newSsps = [...prev.filter((s) => s.id !== ssp.id), ssp].sort(
|
||||
(a, b) => a.id - b.id,
|
||||
toast.error(
|
||||
'Не удалось отправить в архив ' + (data.message || ''),
|
||||
);
|
||||
throw new Error(data.message || 'archive_failed');
|
||||
}
|
||||
toast.success('ССП/РФ перенесены в архив');
|
||||
setSsps((prev) =>
|
||||
prev
|
||||
.map((s) =>
|
||||
s.id === ssp.id ? { ...s, is_active: false } : s,
|
||||
)
|
||||
.sort((a, b) => a.id - b.id),
|
||||
);
|
||||
return newSsps;
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Не удалось поменять статус ');
|
||||
.catch((error) => {
|
||||
if (error?.response) {
|
||||
const msg =
|
||||
error.response.data?.message || error.response.data?.detail;
|
||||
toast.error(msg || 'Не удалось отправить в архив');
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
const updateUser = (user) => {
|
||||
UsersApi.update(user.id, user)
|
||||
@ -130,36 +121,64 @@ const SspsPage = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const updateUserManySsp = (user, sspIds) => {
|
||||
const adjustSspUsersCount = (sspId, delta) => {
|
||||
if (!delta) return;
|
||||
setSsps((prev) =>
|
||||
prev.map((s) =>
|
||||
s.id === sspId
|
||||
? { ...s, users_count: (s.users_count ?? 0) + delta }
|
||||
: s,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const updateUserManySsp = (user, sspIds) =>
|
||||
SspApi.putManySsps(user.id, sspIds)
|
||||
.then((data) => {
|
||||
if (!data.success) {
|
||||
toast.error('Ошибка обновления пользователя ' + data.message);
|
||||
toast.error(data?.message || 'Ошибка добавления пользователя');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Ошибка обновления пользователя ');
|
||||
.catch((error) => {
|
||||
const msg =
|
||||
error?.response?.data?.message ||
|
||||
error?.response?.data?.detail;
|
||||
toast.error(msg || 'Ошибка добавления пользователя');
|
||||
return false;
|
||||
});
|
||||
};
|
||||
const deleteUserManySsp = (user, sspIds) => {
|
||||
|
||||
const deleteUserManySsp = (user, sspIds) =>
|
||||
SspApi.deleteManySsps(user.id, sspIds)
|
||||
.then((data) => {
|
||||
if (!data.success) {
|
||||
toast.error('Ошибка обновления пользователя ' + data.message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Ошибка обновления пользователя ');
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateUsers = async (ssp, usersDataForUpdate) => {
|
||||
let added = 0;
|
||||
let removed = 0;
|
||||
|
||||
for (const user of usersDataForUpdate.update) {
|
||||
await updateUserManySsp(user, { ssp_ids: [ssp.id] });
|
||||
if (await updateUserManySsp(user, { ssp_ids: [ssp.id] })) {
|
||||
added += 1;
|
||||
}
|
||||
}
|
||||
for (const user of usersDataForUpdate.delete) {
|
||||
await deleteUserManySsp(user, { ssp_ids: [ssp.id] });
|
||||
if (await deleteUserManySsp(user, { ssp_ids: [ssp.id] })) {
|
||||
removed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
adjustSspUsersCount(ssp.id, added - removed);
|
||||
};
|
||||
|
||||
const handleEditSsp = (ssp) => {
|
||||
@ -168,7 +187,7 @@ const SspsPage = () => {
|
||||
};
|
||||
|
||||
const handleUpdateSsp = (ssp) => {
|
||||
SspApi.update(ssp.id, ssp)
|
||||
SspApi.updateSsp(ssp.id, ssp)
|
||||
.then((data) => {
|
||||
if (!data.success) {
|
||||
toast.error('Не удалось обновить данные ' + data.message);
|
||||
@ -188,6 +207,7 @@ const SspsPage = () => {
|
||||
|
||||
const handleCloseModalEdit = () => {
|
||||
setOpenModalEdit(false);
|
||||
setSspForEdit(null);
|
||||
};
|
||||
|
||||
const handleAddSsp = () => {
|
||||
@ -199,14 +219,14 @@ const SspsPage = () => {
|
||||
};
|
||||
|
||||
const handleSaveNewSsp = (ssp, usersData) => {
|
||||
SspApi.create(ssp)
|
||||
SspApi.createSsp(ssp)
|
||||
.then((data) => {
|
||||
if (!data.success) {
|
||||
toast.error('Не удалось создать ССП/Рег. филиал ' + data.message);
|
||||
return;
|
||||
}
|
||||
const newSsp = data.result;
|
||||
setSsps((prev) => [...prev, newSsp]);
|
||||
const newSsp = { ...data.result, users_count: data.result.users_count ?? 0 };
|
||||
setSsps((prev) => [...prev, newSsp].sort((a, b) => a.id - b.id));
|
||||
handleUpdateUsers(newSsp, usersData);
|
||||
})
|
||||
.catch(() => {
|
||||
@ -219,7 +239,7 @@ const SspsPage = () => {
|
||||
<>
|
||||
<PageContainer>
|
||||
<HeaderSwitch></HeaderSwitch>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<SwitchUserSsp></SwitchUserSsp>
|
||||
<Box sx={{ display: 'flex', gap: '1rem', alignItems: 'end' }}>
|
||||
<PrimaryButton onClick={handleAddSsp} startIcon={<WhitePlus />}>
|
||||
@ -231,6 +251,7 @@ const SspsPage = () => {
|
||||
<SearchComponent
|
||||
placeholder="Поиск по ССП/Рег. филиалам..."
|
||||
onChange={setFilterString}
|
||||
sx={{ maxWidth: '300px' }}
|
||||
/>
|
||||
<Box
|
||||
sx={{ display: 'flex', flexDirection: 'column', gap: '0.125rem' }}
|
||||
@ -342,6 +363,7 @@ const SspsPage = () => {
|
||||
</Box>
|
||||
<SspTable
|
||||
data={ssps}
|
||||
loading={loading}
|
||||
onUsersInSspChange={handleOpenModalUsersInSspChange}
|
||||
onStatusChange={handleChangeStatus}
|
||||
onEdit={handleEditSsp}
|
||||
|
||||
@ -8,6 +8,7 @@ import StyledTable from '../components/StyledTable/StyledTable';
|
||||
|
||||
const SspDataTable = ({
|
||||
data,
|
||||
loading = false,
|
||||
onEdit,
|
||||
filterString = '',
|
||||
filterStatus = false,
|
||||
@ -25,8 +26,7 @@ const SspDataTable = ({
|
||||
};
|
||||
|
||||
const handleStatusChange = (ssp) => {
|
||||
const updatedSsp = { ...ssp, is_active: ssp.is_active === 1 };
|
||||
onStatusChange?.(updatedSsp);
|
||||
onStatusChange?.(ssp);
|
||||
};
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
@ -84,7 +84,7 @@ const SspDataTable = ({
|
||||
enableColumnActions: false,
|
||||
},
|
||||
{
|
||||
accessorKey: 'countUser',
|
||||
accessorKey: 'users_count',
|
||||
header: 'Количество пользователей',
|
||||
size: 227,
|
||||
maxSize: 454,
|
||||
@ -184,6 +184,7 @@ const SspDataTable = ({
|
||||
<StyledTable
|
||||
columns={columns}
|
||||
data={filteredData}
|
||||
loading={loading}
|
||||
enableRowActions={true}
|
||||
renderRowActions={renderRowActions}
|
||||
positionActionsColumn="last"
|
||||
@ -193,8 +194,8 @@ const SspDataTable = ({
|
||||
enableGlobalFilter={false}
|
||||
enableTopToolbar={false}
|
||||
enablePagination={false}
|
||||
containerHeight="55rem"
|
||||
minHeight="55rem"
|
||||
containerHeight='calc(100vh - 16rem)'
|
||||
minHeight='calc(100vh - 16rem)'
|
||||
/>
|
||||
<ModalConfirmArchiveSsp
|
||||
open={openConfirmModal}
|
||||
|
||||
@ -56,7 +56,7 @@ export const ModalAddUsers = ({ open, onClose, onAddUser }) => {
|
||||
const handleAddUsers = () => {
|
||||
for (const user of usersEmailToAdd) {
|
||||
user['password'] = 'admin123';
|
||||
UsersApi.add(user)
|
||||
UsersApi.createUser(user)
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
onAddUser(data.result);
|
||||
|
||||
@ -23,7 +23,7 @@ export const ModalEditUserSsp = ({
|
||||
setCurUser(user);
|
||||
try {
|
||||
if (user && user.role_id == ROLES_NAME_ID.executor_rf) {
|
||||
const isSsp = user.ssps_data[0].is_ssp;
|
||||
const isSsp = user.org_units?.[0]?.is_ssp;
|
||||
setSelectedType(isSsp ? 'ssp' : 'rf');
|
||||
}
|
||||
} catch {
|
||||
@ -52,12 +52,12 @@ export const ModalEditUserSsp = ({
|
||||
|
||||
setDepsData({
|
||||
update: [selectedDep],
|
||||
delete: [...user.ssps_data],
|
||||
delete: [...(user.org_units ?? [])],
|
||||
});
|
||||
|
||||
setCurUser((prev) => ({
|
||||
...prev,
|
||||
ssp: [depId],
|
||||
org_units: [selectedDep],
|
||||
}));
|
||||
};
|
||||
|
||||
@ -81,9 +81,7 @@ export const ModalEditUserSsp = ({
|
||||
(sId) => deps.filter((dep) => dep.id == sId)[0],
|
||||
);
|
||||
}
|
||||
return (
|
||||
curUser.ssp?.map((sId) => deps.filter((dep) => dep.id == sId)[0]) || []
|
||||
);
|
||||
return curUser.org_units ?? [];
|
||||
}, [curUser, deps, newDepsData]);
|
||||
|
||||
const handleTypeChange = (value) => {
|
||||
@ -175,7 +173,7 @@ export const ModalEditUserSsp = ({
|
||||
{selectedType === 'rf' ? 'РФ' : 'ССП'}
|
||||
</Typography>
|
||||
<SelectWithOptions
|
||||
value={curUser.ssp[0]}
|
||||
value={curUser.org_units?.[0]?.id}
|
||||
options={selectedType === 'rf' ? rfs : ssps}
|
||||
placeholder="-"
|
||||
onChange={handleChangeDepForSingle}
|
||||
@ -198,7 +196,7 @@ export const ModalEditUserSsp = ({
|
||||
ССП/РФ
|
||||
</Typography>
|
||||
<SelectWithOptions
|
||||
value={curUser.ssp[0]}
|
||||
value={curUser.org_units?.[0]?.id}
|
||||
placeholder="-"
|
||||
options={deps}
|
||||
onChange={handleChangeDepForSingle}
|
||||
|
||||
@ -5,7 +5,7 @@ import GroupDeps from './GroupDeps.jsx';
|
||||
|
||||
const DepartmentCell = ({ values, user, onEdit }) => {
|
||||
const items = useMemo(
|
||||
() => (Array.isArray(values) ? values : [values]),
|
||||
() => (Array.isArray(values) ? values : []),
|
||||
[values],
|
||||
);
|
||||
const itemsPerRow = 5;
|
||||
|
||||
@ -274,7 +274,7 @@ export const EditUserModal = ({
|
||||
onEditSsp?.(editUser);
|
||||
});
|
||||
}
|
||||
const selectedSsps = editUser.ssps_data.map((s) => s.title);
|
||||
const selectedSsps = (editUser.org_units ?? []).map((s) => s.title);
|
||||
return (
|
||||
<Box>
|
||||
<FieldLabel>Подразделение</FieldLabel>
|
||||
|
||||
@ -36,9 +36,7 @@ const UserTable = ({
|
||||
onSelectedUsers,
|
||||
selectedUsersId,
|
||||
// Пропсы высоты
|
||||
containerHeight = 'calc(100vh - 450px)',
|
||||
minHeight = '55rem',
|
||||
maxHeight,
|
||||
maxHeight = 'calc(100vh - 17rem)',
|
||||
loading = false,
|
||||
}) => {
|
||||
const [selectedUsers, setSelectedUsers] = useState(new Set());
|
||||
@ -249,7 +247,7 @@ const UserTable = ({
|
||||
enableColumnActions: false,
|
||||
},
|
||||
{
|
||||
accessorKey: 'ssps_data',
|
||||
accessorKey: 'org_units',
|
||||
header: 'Подразделения',
|
||||
muiTableHeadCellProps: {
|
||||
sx: {
|
||||
@ -291,9 +289,9 @@ const UserTable = ({
|
||||
<StyledTable
|
||||
columns={columns}
|
||||
data={filteredData}
|
||||
containerHeight={containerHeight}
|
||||
minHeight={minHeight}
|
||||
maxHeight={maxHeight}
|
||||
containerHeight={maxHeight}
|
||||
minHeight={maxHeight}
|
||||
loading={loading}
|
||||
enableRowActions={true}
|
||||
renderRowActions={({ row }) => (
|
||||
|
||||
@ -24,37 +24,32 @@ const UsersPage = () => {
|
||||
const [dep, setDep] = useState(null);
|
||||
const [role, setRole] = useState(null);
|
||||
const [users, setUsers] = useState([]);
|
||||
const [formattedUsers, setFormattedUsers] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [filterString, setFilterString] = useState('');
|
||||
const [editableUser, setEditableUser] = useState(null);
|
||||
const [selectedUsersId, setSelectedUsersId] = useState(new Set());
|
||||
const [openEditUsers, setOpenEditUserSsp] = useState(false);
|
||||
const [openModalAddUsers, setOpenModalAddUser] = useState(false);
|
||||
|
||||
const formatUsers = async (users) => {
|
||||
const userPromises = users.map(async (user) => {
|
||||
const loadUsers = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await SspApi.getManySsp(user.id);
|
||||
const data = await UsersApi.getAll();
|
||||
if (!data.success) {
|
||||
toast.error('Ошибка получения пользователей ' + data.message);
|
||||
} else {
|
||||
user.ssp = [...data.result.ssp.map((s) => s.id)];
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
setUsers(data.result.sort((a, b) => a.id - b.id));
|
||||
} catch {
|
||||
toast.error('Ошибка получения пользователей');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
user.ssps_data = user.ssp
|
||||
.map((sspId) => deps.find((d) => d.id === sspId) || null)
|
||||
.filter(Boolean);
|
||||
return user;
|
||||
});
|
||||
|
||||
const formattedUsers = await Promise.all(userPromises);
|
||||
return formattedUsers;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setRoles(ROLES_ID_RUSSIAN_NAME_ARRAY);
|
||||
|
||||
SspApi.getAll().then((data) => {
|
||||
if (!data.success) {
|
||||
toast.error(
|
||||
@ -65,38 +60,10 @@ const UsersPage = () => {
|
||||
const activeDeps = data.result.filter((v) => v.is_active);
|
||||
setDeps(activeDeps);
|
||||
});
|
||||
setRoles(ROLES_ID_RUSSIAN_NAME_ARRAY);
|
||||
|
||||
|
||||
loadUsers();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!deps.length) return;
|
||||
|
||||
UsersApi.getAll().then(async (data) => {
|
||||
if (!data.success) {
|
||||
toast.error('Ошибка получения пользователей ' + data.message);
|
||||
return;
|
||||
}
|
||||
setUsers(data.result.sort((a, b) => a.id - b.id));
|
||||
});
|
||||
}, [deps]);
|
||||
|
||||
useEffect(() => {
|
||||
if (deps.length === 0) return;
|
||||
|
||||
const fetchFormattedUsers = async () => {
|
||||
try {
|
||||
const formatted = await formatUsers(users);
|
||||
setFormattedUsers(formatted);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при форматировании пользователей:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchFormattedUsers();
|
||||
}, [users, deps]);
|
||||
|
||||
const handleEditUserSsp = (user) => {
|
||||
setEditableUser(user);
|
||||
setOpenEditUserSsp(true);
|
||||
@ -140,7 +107,7 @@ const UsersPage = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
const data = await UsersApi.update(user.id, user);
|
||||
const data = await UsersApi.update(user.id, { ...user, load_orgs: true });
|
||||
|
||||
if (!data.success) {
|
||||
toast.error('Не удалось обновить данные', data.message);
|
||||
@ -148,19 +115,24 @@ const UsersPage = () => {
|
||||
}
|
||||
|
||||
const updatedUser = data.result;
|
||||
setUsers((prev) => {
|
||||
const users = prev.filter((u) => u.id != updatedUser.id);
|
||||
const newUsers = [...users, updatedUser].sort((a, b) => a.id - b.id);
|
||||
return newUsers;
|
||||
});
|
||||
if (updatedUser?.id) {
|
||||
setUsers((prev) =>
|
||||
prev
|
||||
.map((u) => (u.id === updatedUser.id ? updatedUser : u))
|
||||
.sort((a, b) => a.id - b.id),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(error?.detail || 'Не удалось обновить данные');
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddUserToList = (user) => {
|
||||
const newUsers = [...users, user];
|
||||
setUsers(newUsers);
|
||||
const handleUserAdded = (newUser) => {
|
||||
if (!newUser?.id) return;
|
||||
setUsers((prev) => {
|
||||
if (prev.some((u) => u.id === newUser.id)) return prev;
|
||||
return [...prev, newUser].sort((a, b) => a.id - b.id);
|
||||
});
|
||||
};
|
||||
|
||||
const handleOpenModalAddUsers = () => {
|
||||
@ -208,8 +180,8 @@ const UsersPage = () => {
|
||||
update: [sspId],
|
||||
delete:
|
||||
user.role_id === ROLES_NAME_ID.executor_rf &&
|
||||
user.ssp[0] !== undefined
|
||||
? [user.ssp[0]]
|
||||
user.org_units?.[0]?.id !== undefined
|
||||
? [user.org_units[0].id]
|
||||
: [],
|
||||
};
|
||||
user.ssp_id = sspId;
|
||||
@ -360,7 +332,8 @@ const UsersPage = () => {
|
||||
</Box>
|
||||
|
||||
<UserTable
|
||||
data={formattedUsers}
|
||||
data={users}
|
||||
loading={loading}
|
||||
filterString={filterString}
|
||||
ssp={dep !== null ? dep : null}
|
||||
roleId={role !== null ? role.id : ''}
|
||||
@ -385,7 +358,7 @@ const UsersPage = () => {
|
||||
open={openModalAddUsers}
|
||||
onClose={handleCloseModalAddUsers}
|
||||
sspOptions={deps}
|
||||
onAddUser={handleAddUserToList}
|
||||
onAddUser={handleUserAdded}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -191,6 +191,10 @@ const StyledTable = ({
|
||||
state: {
|
||||
isLoading: loading,
|
||||
},
|
||||
localization: {
|
||||
noRecordsToDisplay: 'Нет данных для отображения',
|
||||
...tableOptions.localization,
|
||||
},
|
||||
muiTablePaperProps: {
|
||||
sx: {
|
||||
...tableStyles.paper,
|
||||
|
||||
@ -4,41 +4,21 @@ import {
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
Button,
|
||||
Stack,
|
||||
Paper,
|
||||
Pagination,
|
||||
PaginationItem,
|
||||
} from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { DefaultOutlinedButton } from '../../../../components/common/Buttons/Buttons';
|
||||
|
||||
const StyledPageButton = styled(Button)(({ active }) => ({
|
||||
minWidth: '2.5rem',
|
||||
height: '2.56rem',
|
||||
padding: '0',
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 500,
|
||||
transition: 'all 0.2s ease',
|
||||
...(active === 'true'
|
||||
? {
|
||||
const paginationItemSx = {
|
||||
'&.Mui-selected': {
|
||||
background: 'linear-gradient(172deg, #7abc51 0%, #46881c 100%)',
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-1px)',
|
||||
background: 'linear-gradient(172deg, #6aad48 0%, #3d7a19 100%)',
|
||||
},
|
||||
}
|
||||
: {
|
||||
border: '1px solid #d1d5dc',
|
||||
color: '#374151',
|
||||
'&:hover': {
|
||||
borderColor: '#5FAC43',
|
||||
backgroundColor: 'rgba(95, 172, 67, 0.04)',
|
||||
transform: 'translateY(-1px)',
|
||||
},
|
||||
}),
|
||||
'&:active': {
|
||||
transform: 'translateY(0)',
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const TableFooter = ({
|
||||
totalCount,
|
||||
@ -50,23 +30,15 @@ const TableFooter = ({
|
||||
}) => {
|
||||
const totalPages = Math.ceil(totalCount / rowsPerPage);
|
||||
|
||||
const handlePreviousPage = () => {
|
||||
if (page > 0) {
|
||||
onPageChange(page - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
if (page < totalPages - 1) {
|
||||
onPageChange(page + 1);
|
||||
}
|
||||
const handlePaginationChange = (_event, newPage) => {
|
||||
onPageChange(newPage - 1);
|
||||
};
|
||||
|
||||
const handleRowsPerPageChange = (event) => {
|
||||
onRowsPerPageChange(parseInt(event.target.value, 10));
|
||||
};
|
||||
|
||||
const from = page * rowsPerPage + 1;
|
||||
const from = totalCount > 0 ? page * rowsPerPage + 1 : 0;
|
||||
const to = Math.min((page + 1) * rowsPerPage, totalCount);
|
||||
|
||||
return (
|
||||
@ -89,7 +61,6 @@ const TableFooter = ({
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{/* Левая часть - информация о строках */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
@ -98,7 +69,9 @@ const TableFooter = ({
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
>
|
||||
{`Показано ${from}-${to}`}
|
||||
{totalCount > 0
|
||||
? `Показано ${from}-${to} из ${totalCount.toLocaleString('ru-RU')}`
|
||||
: `Показано ${from}-${to}`}
|
||||
</Typography>
|
||||
|
||||
<FormControl size="small" sx={{ minWidth: 120 }}>
|
||||
@ -122,38 +95,22 @@ const TableFooter = ({
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
||||
{/* Правая часть - навигация */}
|
||||
<Stack direction="row" spacing={2} alignItems="center">
|
||||
<DefaultOutlinedButton
|
||||
onClick={handlePreviousPage}
|
||||
disabled={page === 0}
|
||||
height={'2.56rem'}
|
||||
>
|
||||
Предыдущая
|
||||
</DefaultOutlinedButton>
|
||||
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
{/* Текущая страница */}
|
||||
<StyledPageButton active="true">{page + 1}</StyledPageButton>
|
||||
|
||||
{/* Следующая страница (если есть) */}
|
||||
{page + 1 < totalPages && (
|
||||
<StyledPageButton
|
||||
active="false"
|
||||
onClick={() => onPageChange(page + 1)}
|
||||
>
|
||||
{page + 2}
|
||||
</StyledPageButton>
|
||||
<Pagination
|
||||
count={Math.max(1, totalPages)}
|
||||
page={page + 1}
|
||||
onChange={handlePaginationChange}
|
||||
color="primary"
|
||||
shape="rounded"
|
||||
showFirstButton
|
||||
showLastButton
|
||||
siblingCount={1}
|
||||
boundaryCount={1}
|
||||
disabled={totalCount === 0}
|
||||
renderItem={(item) => (
|
||||
<PaginationItem {...item} sx={paginationItemSx} />
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
<DefaultOutlinedButton
|
||||
onClick={handleNextPage}
|
||||
disabled={page === totalPages - 1 || totalPages === 0}
|
||||
height={'2.56rem'}
|
||||
>
|
||||
Следующая
|
||||
</DefaultOutlinedButton>
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
@ -16,11 +16,13 @@ export const createFilterData = (filters) => {
|
||||
case 'is_active':
|
||||
return !value || item[key] === value;
|
||||
|
||||
case 'ssp_id':
|
||||
const ssp_ids = item.ssps_data.map((s) => s.id);
|
||||
case 'ssp_id': {
|
||||
const orgUnits = item.org_units ?? [];
|
||||
const ssp_ids = orgUnits.map((s) => s.id);
|
||||
return ssp_ids.length > 0
|
||||
? item.ssps_data.map((s) => s.id).includes(value)
|
||||
? ssp_ids.includes(value)
|
||||
: value === null;
|
||||
}
|
||||
|
||||
default:
|
||||
if (value === null) return true;
|
||||
|
||||
@ -48,7 +48,7 @@ function DictVspPage() {
|
||||
? SspApi.getAll()
|
||||
: user.role_id === ROLES_NAME_ID.executor_dfip
|
||||
? SspApi.getManySsp(user.id)
|
||||
: SspApi.get(user.ssp[0].id),
|
||||
: SspApi.getSsp(user.ssp[0].id),
|
||||
]);
|
||||
|
||||
if (!vspResponse.success) {
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import { Box, TextField, Typography } from '@mui/material';
|
||||
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
||||
import ruLocale from 'date-fns/locale/ru';
|
||||
|
||||
// Стилевые компоненты
|
||||
export const ModalContainer = ({ children, maxHeight = '60vh' }) => (
|
||||
@ -107,7 +104,6 @@ export const StyledDatePicker = ({
|
||||
error,
|
||||
helperText,
|
||||
}) => (
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={ruLocale}>
|
||||
<DatePicker
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
@ -134,5 +130,4 @@ export const StyledDatePicker = ({
|
||||
}}
|
||||
format="dd.MM.yyyy"
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user