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