Compare commits
No commits in common. "9b526901129f3afc82c88a50741587627d8aa8c8" and "658bfe65152014233d6ff7c85f48ef1b3b1b37bf" have entirely different histories.
9b52690112
...
658bfe6515
@ -1,8 +0,0 @@
|
|||||||
import api from './client';
|
|
||||||
|
|
||||||
export const SummaryApi = {
|
|
||||||
get(sheet, year, orgIds = []) {
|
|
||||||
const params = orgIds.length > 0 ? { org_ids: orgIds.join(',') } : {};
|
|
||||||
return api.get(`/svod/1/${sheet}/${year}`, { params }).then((response) => response.data);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -35,9 +35,9 @@ export const redColumn = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const blueColumn = {
|
export const blueColumn = {
|
||||||
ROOT: "#96c2f9",
|
ROOT: "#538DD4",
|
||||||
GROUP: "#bad5f5",
|
GROUP: "#8DB3E2",
|
||||||
ITEM: "#ddecfe",
|
ITEM: "#C5D8F0",
|
||||||
SUB_ITEM: "#C5D8F0",
|
SUB_ITEM: "#C5D8F0",
|
||||||
INPUT: "#FFFFFF",
|
INPUT: "#FFFFFF",
|
||||||
COLOR: "#000000",
|
COLOR: "#000000",
|
||||||
|
|||||||
@ -285,7 +285,7 @@ export const ProjectCardComponent = ({
|
|||||||
}}>
|
}}>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<IconWithContent icon={<DateIcon />}>{years.join(', ') || 'Год не указан'}</IconWithContent>
|
<IconWithContent icon={<DateIcon />}>{years.join(', ') || 'Год не указан'}</IconWithContent>
|
||||||
<IconWithContent icon={<UserIcon />}>{org_unit_name || 'ССП/РФ не указана'}</IconWithContent>
|
<IconWithContent icon={<UserIcon />}>{org_unit_name || 'Организация не указана'}</IconWithContent>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|
||||||
{exportMode && <div>{isChecked ? <CheckBoxCheckSvg size='1.625rem' /> : <CheckBoxUncheckSvg size='1.625rem' />}</div>}
|
{exportMode && <div>{isChecked ? <CheckBoxCheckSvg size='1.625rem' /> : <CheckBoxUncheckSvg size='1.625rem' />}</div>}
|
||||||
|
|||||||
@ -1,359 +1,35 @@
|
|||||||
import RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';
|
import ConstructionOutlinedIcon from '@mui/icons-material/ConstructionOutlined';
|
||||||
import SearchRoundedIcon from '@mui/icons-material/SearchRounded';
|
import { Box, Paper, Typography } from '@mui/material';
|
||||||
import {
|
|
||||||
Alert, Autocomplete, Box, Button, Chip, CircularProgress, InputAdornment,
|
|
||||||
MenuItem, Paper, Tab, Tabs, TextField, Typography,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { MaterialReactTable, useMaterialReactTable } from 'material-react-table';
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { SspApi } from '../../api/ssp';
|
|
||||||
import { SummaryApi } from '../../api/summary';
|
|
||||||
import { useAuth } from '../../app/context/AuthProvider';
|
|
||||||
import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch';
|
import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch';
|
||||||
import { blueColumn } from '../../components/RealtimeTable/constants/columnColors';
|
|
||||||
import { sectionCodeColor } from '../../components/RealtimeTable/constants/columnConfig';
|
|
||||||
import { ROLES_NAME_ID } from '../../constants/constants';
|
|
||||||
|
|
||||||
const SHEETS = [
|
const SvodPage = () => (
|
||||||
{ value: 'MAIN', label: 'Общий свод'},
|
<>
|
||||||
{ value: 'FORM_1', label: 'Смета ГО' },
|
|
||||||
{ value: 'FORM_2', label: 'Смета РФ' },
|
|
||||||
{ value: 'FORM_3', label: 'Проекты РФ Развитие' },
|
|
||||||
{ value: 'FORM_4', label: 'Проектная деятельность' },
|
|
||||||
];
|
|
||||||
const METRICS = [
|
|
||||||
{ key: 'plan', label: 'План' },
|
|
||||||
{ key: 'approved', label: 'Утверждено' },
|
|
||||||
{ key: 'fact', label: 'Факт' },
|
|
||||||
{ key: 'corrected', label: 'Скорректированный план' },
|
|
||||||
];
|
|
||||||
const DIRECTIONS = [
|
|
||||||
{ key: 'support', label: 'Поддержка' },
|
|
||||||
{ key: 'development', label: 'Развитие' },
|
|
||||||
];
|
|
||||||
const QUARTERS = [
|
|
||||||
{ key: 'q1', label: 'I кв.' }, { key: 'q2', label: 'II кв.' },
|
|
||||||
{ key: 'q3', label: 'III кв.' }, { key: 'q4', label: 'IV кв.' },
|
|
||||||
{ key: 'year', label: 'Год' },
|
|
||||||
];
|
|
||||||
const numberFormatter = new Intl.NumberFormat('ru-RU', { maximumFractionDigits: 2 });
|
|
||||||
const valueAt = (row, path) => path.reduce((value, key) => value?.[key], row.data);
|
|
||||||
const tableSize = (rem) => rem * 16;
|
|
||||||
|
|
||||||
const formatValue = (value) => {
|
|
||||||
if (value === null || value === undefined || value === '') return '—';
|
|
||||||
const numericValue = typeof value === 'number' ? value : Number(value);
|
|
||||||
return Number.isNaN(numericValue) ? value : numberFormatter.format(numericValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getShapeForSheet = (sheet) => {
|
|
||||||
const directionShape = { q1: 0, q2: 0, q3: 0, q4: 0, year: 0 };
|
|
||||||
const correctedShape = sheet === 'FORM_4' ? directionShape : { q2: 0, q3: 0, q4: 0 };
|
|
||||||
return {
|
|
||||||
plan: { support: directionShape, development: directionShape, ...(['FORM_1', 'MAIN'].includes(sheet) ? { total_year: 0 } : {}) },
|
|
||||||
approved: { support: directionShape, development: directionShape, ...(['FORM_1', 'MAIN'].includes(sheet) ? { total_year: 0 } : {}) },
|
|
||||||
fact: { support: directionShape, development: directionShape, ...(['FORM_1', 'MAIN'].includes(sheet) ? { total_year: 0 } : {}) },
|
|
||||||
corrected: { support: correctedShape, development: correctedShape },
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const valueColumn = (metric, direction, period) => ({
|
|
||||||
id: `${metric.key}.${direction.key}.${period.key}`,
|
|
||||||
header: period.label,
|
|
||||||
accessorFn: (row) => valueAt(row, [metric.key, direction.key, period.key]),
|
|
||||||
Cell: ({ cell }) => formatValue(cell.getValue()),
|
|
||||||
size: tableSize(period.key === 'year' ? 8 : 6.75),
|
|
||||||
muiTableBodyCellProps: { align: 'right' },
|
|
||||||
muiTableHeadCellProps: { align: 'right' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const createColumns = (sheet) => {
|
|
||||||
const visibleDirections = sheet === 'FORM_2'
|
|
||||||
? DIRECTIONS.filter(({ key }) => key === 'support')
|
|
||||||
: ['FORM_3', 'FORM_4'].includes(sheet)
|
|
||||||
? DIRECTIONS.filter(({ key }) => key === 'development')
|
|
||||||
: DIRECTIONS;
|
|
||||||
const shape = getShapeForSheet(sheet);
|
|
||||||
const metricColumns = METRICS.map((metric) => ({
|
|
||||||
id: metric.key,
|
|
||||||
header: metric.label,
|
|
||||||
columns: visibleDirections.map((direction) => ({
|
|
||||||
id: `${metric.key}.${direction.key}`,
|
|
||||||
header: direction.label,
|
|
||||||
columns: QUARTERS
|
|
||||||
.filter(({ key }) => valueAt({ data: shape }, [metric.key, direction.key, key]) !== undefined)
|
|
||||||
.map((period) => valueColumn(metric, direction, period)),
|
|
||||||
})).concat(shape[metric.key].total_year === undefined ? [] : [{
|
|
||||||
id: `${metric.key}.total`,
|
|
||||||
header: 'Итого',
|
|
||||||
columns: [{
|
|
||||||
id: `${metric.key}.total_year`,
|
|
||||||
header: 'Год',
|
|
||||||
accessorFn: (row) => valueAt(row, [metric.key, 'total_year']),
|
|
||||||
Cell: ({ cell }) => formatValue(cell.getValue()),
|
|
||||||
size: tableSize(8.25),
|
|
||||||
muiTableBodyCellProps: { align: 'right' },
|
|
||||||
muiTableHeadCellProps: { align: 'right' },
|
|
||||||
}],
|
|
||||||
}]),
|
|
||||||
}));
|
|
||||||
return [
|
|
||||||
{ id: 'section_code', header: 'Код', accessorFn: (row) => row.data?.section_code, size: tableSize(5.75) },
|
|
||||||
{
|
|
||||||
id: 'name', header: 'Статья расходов', accessorFn: (row) => row.data?.name, size: tableSize(20.625),
|
|
||||||
Cell: ({ cell, row }) => (
|
|
||||||
<Box sx={{ pl: `${row.original.depth * 1.125}rem`, fontWeight: row.original.depth < 2 ? 600 : 400 }}>
|
|
||||||
{cell.getValue() || 'Без названия'}
|
|
||||||
</Box>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
...metricColumns,
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSummaryRowColor = (row) => {
|
|
||||||
const rowType = row.original?.row_type || row.row_type;
|
|
||||||
const sectionCodeValue = row.original?.data?.section_code ?? row.original?.data?.header?.section_code;
|
|
||||||
if (sectionCodeValue === null || sectionCodeValue === undefined || sectionCodeValue === '') return undefined;
|
|
||||||
const sectionCode = String(sectionCodeValue)[0];
|
|
||||||
return (sectionCodeColor[sectionCode] || blueColumn)[rowType];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTextColor = (backgroundColor) => {
|
|
||||||
if (!backgroundColor?.startsWith('#') || backgroundColor.length !== 7) return '#000';
|
|
||||||
const red = Number.parseInt(backgroundColor.slice(1, 3), 16);
|
|
||||||
const green = Number.parseInt(backgroundColor.slice(3, 5), 16);
|
|
||||||
const blue = Number.parseInt(backgroundColor.slice(5, 7), 16);
|
|
||||||
return 0.299 * red + 0.587 * green + 0.114 * blue < 128 ? '#fff' : '#000';
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildSummaryTree = (sourceRows) => {
|
|
||||||
const roots = [];
|
|
||||||
const parents = [];
|
|
||||||
|
|
||||||
for (const [index, sourceRow] of sourceRows.entries()) {
|
|
||||||
const row = {
|
|
||||||
...sourceRow,
|
|
||||||
_rowId: `${sourceRow.sort_order ?? index}-${sourceRow.data?.section_code ?? index}`,
|
|
||||||
subRows: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
while (parents.length > row.depth) parents.pop();
|
|
||||||
if (row.depth > 0 && parents[row.depth - 1]) parents[row.depth - 1].subRows.push(row);
|
|
||||||
else roots.push(row);
|
|
||||||
|
|
||||||
parents[row.depth] = row;
|
|
||||||
parents.length = row.depth + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return roots;
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterSummaryTree = (tree, query) => tree.flatMap((row) => {
|
|
||||||
const matches = [row.data?.section_code, row.data?.name]
|
|
||||||
.some((value) => String(value || '').toLowerCase().includes(query));
|
|
||||||
|
|
||||||
if (matches) return [row];
|
|
||||||
|
|
||||||
const subRows = filterSummaryTree(row.subRows, query);
|
|
||||||
return subRows.length ? [{ ...row, subRows }] : [];
|
|
||||||
});
|
|
||||||
|
|
||||||
const countSummaryTree = (tree) => tree.reduce((count, row) => count + 1 + countSummaryTree(row.subRows), 0);
|
|
||||||
|
|
||||||
const getAvailableYears = () => {
|
|
||||||
const currentYear = new Date().getFullYear();
|
|
||||||
return Array.from({ length: 7 }, (_, index) => currentYear + 1 - index);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function SvodPage() {
|
|
||||||
const { user } = useAuth();
|
|
||||||
const [sheet, setSheet] = useState('MAIN');
|
|
||||||
const [year, setYear] = useState(new Date().getFullYear());
|
|
||||||
const [organizations, setOrganizations] = useState([]);
|
|
||||||
const [selectedOrganizations, setSelectedOrganizations] = useState([]);
|
|
||||||
const [rows, setRows] = useState([]);
|
|
||||||
const [search, setSearch] = useState('');
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [organizationsLoading, setOrganizationsLoading] = useState(false);
|
|
||||||
const [error, setError] = useState('');
|
|
||||||
const [requestVersion, setRequestVersion] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!user) return;
|
|
||||||
const loadOrganizations = async () => {
|
|
||||||
setOrganizationsLoading(true);
|
|
||||||
try {
|
|
||||||
if (user.role_id === ROLES_NAME_ID.admin) {
|
|
||||||
const response = await SspApi.getAll({ is_active: true, limit: 100 });
|
|
||||||
setOrganizations(response.result || []);
|
|
||||||
} else {
|
|
||||||
setOrganizations(user.org_units || []);
|
|
||||||
}
|
|
||||||
} catch (_error) {
|
|
||||||
setOrganizations(user.org_units || []);
|
|
||||||
toast.error('Не удалось загрузить список ССП/РФ');
|
|
||||||
} finally {
|
|
||||||
setOrganizationsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
loadOrganizations();
|
|
||||||
}, [user]);
|
|
||||||
|
|
||||||
const loadSummary = useCallback(async () => {
|
|
||||||
setLoading(true);
|
|
||||||
setError('');
|
|
||||||
try {
|
|
||||||
const response = await SummaryApi.get(sheet, year, selectedOrganizations.map(({ id }) => id));
|
|
||||||
setRows([...(response.result || [])].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0)));
|
|
||||||
} catch (loadError) {
|
|
||||||
setRows([]);
|
|
||||||
setError(loadError.response?.data?.detail || 'Не удалось загрузить свод. Попробуйте ещё раз.');
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}, [selectedOrganizations, sheet, year]);
|
|
||||||
|
|
||||||
useEffect(() => { loadSummary(); }, [loadSummary, requestVersion]);
|
|
||||||
|
|
||||||
const filteredRows = useMemo(() => {
|
|
||||||
const normalizedSearch = search.trim().toLowerCase();
|
|
||||||
const tree = buildSummaryTree(rows);
|
|
||||||
return normalizedSearch ? filterSummaryTree(tree, normalizedSearch) : tree;
|
|
||||||
}, [rows, search]);
|
|
||||||
const columns = useMemo(() => createColumns(sheet), [sheet]);
|
|
||||||
const filteredRowsCount = useMemo(() => countSummaryTree(filteredRows), [filteredRows]);
|
|
||||||
const table = useMaterialReactTable({
|
|
||||||
columns,
|
|
||||||
data: filteredRows,
|
|
||||||
enableExpanding: true,
|
|
||||||
enableExpandAll: true,
|
|
||||||
getSubRows: (row) => row.subRows,
|
|
||||||
getRowId: (row) => row._rowId,
|
|
||||||
enableColumnActions: false,
|
|
||||||
enableColumnFilters: false,
|
|
||||||
enableDensityToggle: false,
|
|
||||||
enableFullScreenToggle: false,
|
|
||||||
enableHiding: false,
|
|
||||||
enablePagination: false,
|
|
||||||
enableTopToolbar: false,
|
|
||||||
enableBottomToolbar: false,
|
|
||||||
enableSorting: false,
|
|
||||||
displayColumnDefOptions: {
|
|
||||||
'mrt-row-expand': {
|
|
||||||
size: tableSize(3.75),
|
|
||||||
minSize: tableSize(3.75),
|
|
||||||
maxSize: tableSize(3.75),
|
|
||||||
grow: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
state: { isLoading: loading },
|
|
||||||
initialState: { columnPinning: { left: ['mrt-row-expand', 'section_code', 'name'] }, expanded: true },
|
|
||||||
muiTablePaperProps: {
|
|
||||||
elevation: 0,
|
|
||||||
sx: {
|
|
||||||
border: '0.0625rem solid #e5e7eb',
|
|
||||||
borderRadius: '0.75rem',
|
|
||||||
overflow: 'hidden',
|
|
||||||
flex: 1,
|
|
||||||
minHeight: 0,
|
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
muiTableContainerProps: {
|
|
||||||
sx: {
|
|
||||||
flex: 1,
|
|
||||||
overflow: 'auto',
|
|
||||||
minHeight: 0,
|
|
||||||
height: '100%',
|
|
||||||
'& thead': {
|
|
||||||
position: 'sticky',
|
|
||||||
top: 0,
|
|
||||||
zIndex: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
muiTableHeadCellProps: {
|
|
||||||
sx: {
|
|
||||||
position: 'sticky',
|
|
||||||
top: 0,
|
|
||||||
boxShadow: 'none',
|
|
||||||
backgroundColor: '#f8f9fa', borderRight: '0.0625rem solid #e5e7eb', borderBottom: '0.0625rem solid #dfe3e7',
|
|
||||||
color: '#4a5565', fontSize: '0.75rem', fontWeight: 700, lineHeight: 1.2, px: '0.75rem', py: '0.5rem', whiteSpace: 'nowrap',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
muiTableBodyCellProps: ({ row, column }) => {
|
|
||||||
const backgroundColor = getSummaryRowColor(row);
|
|
||||||
return {
|
|
||||||
sx: {
|
|
||||||
backgroundColor, borderRight: '0.0625rem solid #edf0f2', color: getTextColor(backgroundColor),
|
|
||||||
fontSize: '0.8125rem', px: column.id === 'mrt-row-expand' ? '0.25rem' : '0.75rem',
|
|
||||||
py: column.id === 'mrt-row-expand' ? 0 : '0.5rem', whiteSpace: 'nowrap',
|
|
||||||
textAlign: ['section_code', 'name'].includes(column.id) ? 'left' : 'right',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
muiExpandButtonProps: { sx: { width: '1.25rem', height: '1.25rem', p: 0, '& .MuiSvgIcon-root': { fontSize: '1.125rem' } } },
|
|
||||||
localization: { noRecordsToDisplay: 'Нет данных для выбранных параметров' },
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ px: '2rem', pt: '1rem', pb: '1.5rem', height: '100%', minHeight: 0, overflow: 'hidden', display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
|
||||||
<HeaderSwitch />
|
<HeaderSwitch />
|
||||||
|
<Box
|
||||||
<Paper elevation={0} sx={{ border: '0.0625rem solid #e5e7eb', borderRadius: '0.75rem', overflow: 'hidden', flexShrink: 0 }}>
|
sx={{
|
||||||
<Tabs value={sheet} onChange={(_event, value) => setSheet(value)} variant='scrollable' scrollButtons='auto'
|
minHeight: 'calc(100vh - 4rem)',
|
||||||
sx={{ px: '0.5rem', minHeight: '3rem', borderBottom: '0.0625rem solid #edf0f2' }}>
|
display: 'flex',
|
||||||
{SHEETS.map((item) => <Tab key={item.value} value={item.value} label={item.label}
|
alignItems: 'center',
|
||||||
sx={{ textTransform: 'none', minHeight: '3rem' }} />)}
|
justifyContent: 'center',
|
||||||
</Tabs>
|
p: 3,
|
||||||
<Box sx={{ p: '1rem', display: 'flex', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap' }}>
|
}}>
|
||||||
<TextField select size='small' label='Год' value={year}
|
<Paper
|
||||||
onChange={(event) => setYear(Number(event.target.value))} sx={{ width: '7.5rem' }}>
|
elevation={0}
|
||||||
{getAvailableYears().map((availableYear) => <MenuItem key={availableYear} value={availableYear}>{availableYear}</MenuItem>)}
|
sx={{
|
||||||
</TextField>
|
width: '100%',
|
||||||
<Autocomplete
|
maxWidth: 520,
|
||||||
multiple disableCloseOnSelect size='small' options={organizations} loading={organizationsLoading}
|
p: 5,
|
||||||
value={selectedOrganizations} isOptionEqualToValue={(option, value) => option.id === value.id}
|
textAlign: 'center',
|
||||||
getOptionLabel={(option) => option.title || ''} onChange={(_event, value) => setSelectedOrganizations(value)}
|
border: '1px solid',
|
||||||
sx={{ minWidth: '18.75rem', flex: '1 1 22.5rem', maxWidth: '32.5rem' }}
|
borderColor: 'divider',
|
||||||
renderTags={(value, getTagProps) => value.slice(0, 2).map((option, index) => {
|
borderRadius: 4,
|
||||||
const tagProps = getTagProps({ index });
|
}}>
|
||||||
return <Chip {...tagProps} key={option.id} label={option.title} size='small' />;
|
<ConstructionOutlinedIcon sx={{ fontSize: 56, color: 'text.secondary', mb: 2 }} />
|
||||||
}).concat(value.length > 2 ? [<Chip key='more' label={`+${value.length - 2}`} size='small' />] : [])}
|
<Typography color='text.secondary'>Страница находится в разработке</Typography>
|
||||||
renderInput={(params) => <TextField {...params} label='ССП/РФ'
|
|
||||||
placeholder={selectedOrganizations.length ? '' : 'Все доступные'}
|
|
||||||
InputProps={{
|
|
||||||
...(params.InputProps || {}),
|
|
||||||
endAdornment: <>{organizationsLoading && <CircularProgress size='1.125rem' />}{params.InputProps?.endAdornment}</>,
|
|
||||||
}} />}
|
|
||||||
/>
|
|
||||||
<TextField size='small' value={search} onChange={(event) => setSearch(event.target.value)}
|
|
||||||
placeholder='Код или статья расходов' sx={{ minWidth: '16.25rem', flex: '1 1 17.5rem', maxWidth: '23.75rem' }}
|
|
||||||
InputProps={{ startAdornment: <InputAdornment position='start'><SearchRoundedIcon sx={{ color: '#99a1af' }} /></InputAdornment> }} />
|
|
||||||
{!loading && filteredRowsCount !== rows.length && (
|
|
||||||
<Typography variant='caption' color='text.secondary' sx={{ flexShrink: 0, whiteSpace: 'nowrap' }}>
|
|
||||||
{`Найдено ${filteredRowsCount.toLocaleString('ru-RU')} из ${rows.length.toLocaleString('ru-RU')} строк`}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
variant='outlined'
|
|
||||||
color='default'
|
|
||||||
startIcon={<RefreshRoundedIcon />}
|
|
||||||
disabled={loading}
|
|
||||||
onClick={() => setRequestVersion((version) => version + 1)}
|
|
||||||
sx={{ ml: 'auto', height: '2.5rem', flexShrink: 0 }}>
|
|
||||||
Обновить
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
{error ? (
|
|
||||||
<Alert severity='error' action={<Button color='inherit' size='small'
|
|
||||||
onClick={() => setRequestVersion((version) => version + 1)}>Повторить</Button>}>{error}</Alert>
|
|
||||||
) : <MaterialReactTable table={table} />}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
</>
|
||||||
}
|
|
||||||
|
);
|
||||||
|
|
||||||
|
export default SvodPage;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user