611 lines
21 KiB
JavaScript
611 lines
21 KiB
JavaScript
import RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';
|
||
import { Alert, Box, Button, Paper, Tab, Tabs, Typography } from '@mui/material';
|
||
import { MaterialReactTable, useMaterialReactTable } from 'material-react-table';
|
||
import { MRT_Localization_RU } from 'material-react-table/locales/ru';
|
||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||
|
||
import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch';
|
||
import { PrimaryButton } from '../../components/common/Buttons/Buttons';
|
||
import { useSvod } from '../SvodPage/SvodContext';
|
||
import { SvodFilters } from '../SvodPage/SvodFilters';
|
||
|
||
const SHEETS = [
|
||
{ value: 'GO', label: 'ССП' },
|
||
{ value: 'RF', label: 'РФ' },
|
||
];
|
||
const createInitialTableState = () =>
|
||
Object.fromEntries(SHEETS.map(({ value }) => [value, { rows: [], loading: false, error: '', loadedQueryKey: '' }]));
|
||
const QUARTERS = [1, 2, 3, 4];
|
||
const tableSize = (rem) => rem * 16;
|
||
const numberFormatter = new Intl.NumberFormat('ru-RU', { maximumFractionDigits: 2 });
|
||
const ROW_HEIGHT = 37;
|
||
const ROW_VIRTUALIZER_OPTIONS = {
|
||
overscan: 8,
|
||
estimateSize: () => ROW_HEIGHT,
|
||
measureElement: (element) => element?.offsetHeight || ROW_HEIGHT,
|
||
};
|
||
const COLUMN_BAND_COLORS = {
|
||
head: ['#f8f9fa', '#f1f7f3'],
|
||
body: ['#ffffff', '#f8fbf9'],
|
||
};
|
||
|
||
const valueAt = (row, path) => path.reduce((value, key) => value?.[key], row.data);
|
||
|
||
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 textColumn = (id, header, path, size = 10, options = {}) => ({
|
||
id,
|
||
header,
|
||
accessorFn: (row) => valueAt(row, path),
|
||
Cell: ({ cell }) => cell.getValue() || '—',
|
||
size: tableSize(size),
|
||
enableColumnFilter: false,
|
||
...options,
|
||
});
|
||
|
||
const numericColumn = (id, header, path, size = 7.5) => ({
|
||
id,
|
||
header,
|
||
accessorFn: (row) => valueAt(row, path),
|
||
Cell: ({ cell }) => formatValue(cell.getValue()),
|
||
size: tableSize(size),
|
||
enableColumnFilter: false,
|
||
muiTableBodyCellProps: { align: 'right' },
|
||
muiTableHeadCellProps: { align: 'right' },
|
||
});
|
||
|
||
const MONTHS_BY_QUARTER = {
|
||
1: ['Январь', 'Февраль', 'Март'],
|
||
2: ['Апрель', 'Май', 'Июнь'],
|
||
3: ['Июль', 'Август', 'Сентябрь'],
|
||
4: ['Октябрь', 'Ноябрь', 'Декабрь'],
|
||
};
|
||
const CARRYOVER_HEADERS = {
|
||
1: 'Корректировка из 1 квартала во 2–4 кварталы',
|
||
2: 'Корректировка из 2 квартала в 3–4 кварталы',
|
||
3: 'Корректировка из 3 квартала в 4 квартал',
|
||
};
|
||
const ESTIMATE_TYPE_STYLES = {
|
||
АХР: { backgroundColor: '#ecf9f0', borderColor: '#a7d9b8', color: '#08783e' },
|
||
Операц: { backgroundColor: '#eff6ff', borderColor: '#93c5fd', color: '#1d4ed8' },
|
||
КВП: { backgroundColor: '#fff7ed', borderColor: '#fdba74', color: '#c2410c' },
|
||
};
|
||
|
||
const createCorrectionColumns = (quarter) => ({
|
||
id: `q${quarter}.correction`,
|
||
header: 'Корректировка внутри квартала',
|
||
columns: [
|
||
numericColumn(`q${quarter}.adj_current`, 'Текущие корректировки (= 0)', [`q${quarter}`, 'adj_current'], 12),
|
||
numericColumn(`q${quarter}.adj_ssp`, 'Корректировки с ССП / сметой развития', [`q${quarter}`, 'adj_ssp'], 15),
|
||
numericColumn(`q${quarter}.adj_rf`, 'Корректировки с РФ', [`q${quarter}`, 'adj_rf'], 11),
|
||
numericColumn(`q${quarter}.adj_reserve`, 'Корректировки из резерва', [`q${quarter}`, 'adj_reserve'], 12),
|
||
],
|
||
});
|
||
|
||
const createPaymentColumns = (quarter) => ({
|
||
id: `q${quarter}.payment`,
|
||
header: 'Платеж и предоставление акта',
|
||
columns: [
|
||
textColumn(`q${quarter}.payment_date`, 'Дата платежа', [`q${quarter}`, 'payment_date'], 9),
|
||
numericColumn(`q${quarter}.payment_amount`, 'Сумма, тыс. руб. (без НДС)', [`q${quarter}`, 'payment_amount'], 12),
|
||
numericColumn(`q${quarter}.payment_amount_ho`, 'в т.ч. сумма (без НДС) ГО', [`q${quarter}`, 'payment_amount_ho'], 12),
|
||
numericColumn(`q${quarter}.payment_amount_rf`, 'в т.ч. сумма (без НДС) РФ', [`q${quarter}`, 'payment_amount_rf'], 12),
|
||
textColumn(`q${quarter}.payment_act`, 'Предоставление акта (др. подтверждения)', [`q${quarter}`, 'payment_act'], 16),
|
||
],
|
||
});
|
||
|
||
const createActualColumns = (quarter) => ({
|
||
id: `q${quarter}.actual`,
|
||
header: 'Фактические расходы за квартал',
|
||
columns: [
|
||
numericColumn(`q${quarter}.booking`, `Бронь ${quarter} кв.`, [`q${quarter}`, 'booking_amount'], 9),
|
||
...MONTHS_BY_QUARTER[quarter].map((month, index) =>
|
||
numericColumn(`q${quarter}.actual_m${index + 1}`, `Факт ${month}`, [`q${quarter}`, `actual_m${index + 1}`], 9),
|
||
),
|
||
...(quarter === 4 ? [numericColumn('q4.actual_spod', 'Факт СПОД', ['q4', 'actual_spod'], 9)] : []),
|
||
],
|
||
});
|
||
|
||
const createCarryoverColumns = (quarter) => {
|
||
const nextQuarter = quarter + 1;
|
||
const transferColumns = QUARTERS.filter((targetQuarter) => targetQuarter > quarter).map((targetQuarter) =>
|
||
numericColumn(
|
||
`q${quarter}.transfer_to_q${targetQuarter}`,
|
||
`Перенос в ${targetQuarter} кв.`,
|
||
[`q${quarter}`, `transfer_to_q${targetQuarter}`],
|
||
9,
|
||
),
|
||
);
|
||
return {
|
||
id: `q${quarter}.carryover`,
|
||
header: CARRYOVER_HEADERS[quarter],
|
||
columns: [
|
||
...transferColumns,
|
||
numericColumn(`q${quarter}.transfer_to_economy`, 'Перенос в фонд экономии', [`q${quarter}`, 'transfer_to_economy'], 12),
|
||
numericColumn(
|
||
`q${nextQuarter}.plan_revision_eco_change`,
|
||
'Изменение целевого назначения перенесенной экономии (= 0)',
|
||
[`q${nextQuarter}`, 'plan_revision_eco_change'],
|
||
16,
|
||
),
|
||
numericColumn(
|
||
`q${nextQuarter}.plan_revision_item_adj`,
|
||
'Корректировка статей базового плана (= 0)',
|
||
[`q${nextQuarter}`, 'plan_revision_item_adj'],
|
||
15,
|
||
),
|
||
numericColumn(
|
||
`q${nextQuarter}.plan_revision_increase`,
|
||
'Увеличение базового плана (> 0)',
|
||
[`q${nextQuarter}`, 'plan_revision_increase'],
|
||
13,
|
||
),
|
||
numericColumn(
|
||
`q${nextQuarter}.plan_revision_sequester`,
|
||
'Секвестр базового плана (< 0)',
|
||
[`q${nextQuarter}`, 'plan_revision_sequester'],
|
||
13,
|
||
),
|
||
],
|
||
};
|
||
};
|
||
|
||
const createColumns = (sheet) => [
|
||
textColumn('smeta_type', 'Вид сметы', ['header', 'smeta_type'], 8, {
|
||
enableColumnFilter: true,
|
||
filterVariant: 'select',
|
||
Cell: ({ cell }) => {
|
||
const value = cell.getValue();
|
||
if (!value) return '—';
|
||
const colors = ESTIMATE_TYPE_STYLES[value] || {
|
||
backgroundColor: '#f3f4f6',
|
||
borderColor: '#d1d5db',
|
||
color: '#4b5563',
|
||
};
|
||
return (
|
||
<Box
|
||
component='span'
|
||
sx={{
|
||
display: 'inline-block',
|
||
px: '0.625rem',
|
||
py: '0.25rem',
|
||
border: '0.0625rem solid',
|
||
borderRadius: '0.5rem',
|
||
fontWeight: 600,
|
||
lineHeight: 1.35,
|
||
whiteSpace: 'nowrap',
|
||
...colors,
|
||
}}>
|
||
{value}
|
||
</Box>
|
||
);
|
||
},
|
||
}),
|
||
textColumn('smeta_direction', 'Направление', ['header', 'smeta_direction'], 10, { enableColumnFilter: true, filterVariant: 'select' }),
|
||
textColumn('org_name', 'ССП', ['header', 'org_name'], 14, {
|
||
enableColumnFilter: true,
|
||
filterVariant: 'select',
|
||
Cell: ({ cell }) => {
|
||
const value = cell.getValue();
|
||
if (!value) return '—';
|
||
return (
|
||
<Box
|
||
component='span'
|
||
sx={{
|
||
display: 'inline-block',
|
||
maxWidth: '100%',
|
||
px: '0.625rem',
|
||
py: '0.25rem',
|
||
border: '0.0625rem solid #a7d9b8',
|
||
borderRadius: '0.5rem',
|
||
backgroundColor: '#ecf9f0',
|
||
color: '#08783e',
|
||
fontWeight: 600,
|
||
lineHeight: 1.35,
|
||
textTransform: 'uppercase',
|
||
overflow: 'hidden',
|
||
textOverflow: 'ellipsis',
|
||
whiteSpace: 'nowrap',
|
||
}}>
|
||
{value}
|
||
</Box>
|
||
);
|
||
},
|
||
}),
|
||
textColumn('name', 'Наименование статьи', ['header', 'name'], 20),
|
||
|
||
{
|
||
id: 'estimate',
|
||
header: sheet === 'GO' ? 'Смета расходов ГО в разрезе договоров' : 'Смета расходов РФ в разрезе договоров',
|
||
columns: [
|
||
textColumn('item_id', 'ID статьи', ['header', 'item_id'], 7),
|
||
textColumn('num_group', 'ID группы номенклатуры', ['header', 'num_group'], 10),
|
||
textColumn('justification', 'Конкретный вид расхода', ['header', 'justification'], 18),
|
||
...QUARTERS.map((quarter) => numericColumn(`plan.q${quarter}`, `${quarter} кв.`, ['plan', `plan_q${quarter}`])),
|
||
],
|
||
},
|
||
{
|
||
id: 'allocation',
|
||
header: 'Аллокация расходов',
|
||
columns: [
|
||
textColumn('allocation.internal_order', 'Внутренний заказ', ['allocation', 'internal_order'], 12),
|
||
textColumn('allocation.property_object', 'Объект недвижимости', ['allocation', 'property_object'], 14),
|
||
],
|
||
},
|
||
{
|
||
id: 'sequestration',
|
||
header: 'Секвестирование ДФиП',
|
||
columns: QUARTERS.map((quarter) =>
|
||
numericColumn(`sequestration.q${quarter}`, `${quarter} кв.`, ['sequestration', 'DFIP', `adj_q${quarter}`]),
|
||
),
|
||
},
|
||
{
|
||
id: 'reserve',
|
||
header: 'Отнесение в резерв',
|
||
columns: QUARTERS.map((quarter) => numericColumn(`reserve.q${quarter}`, `${quarter} кв.`, ['reserve', `amount_q${quarter}`])),
|
||
},
|
||
{
|
||
id: 'approved',
|
||
header: 'Итоговая смета расходов',
|
||
columns: QUARTERS.map((quarter) => numericColumn(`approved.q${quarter}`, `${quarter} кв.`, ['approved', `approved_q${quarter}`])),
|
||
},
|
||
{
|
||
id: 'contract',
|
||
header: 'Договор',
|
||
columns: [
|
||
textColumn('contract.counterparty', 'Контрагент', ['contract_detail', 'counterparty'], 14),
|
||
textColumn('contract.reference', 'Договор (реквизиты)', ['contract_detail', 'reference'], 12),
|
||
textColumn('contract.subject', 'Предмет', ['contract_detail', 'subject'], 18),
|
||
textColumn('contract.currency', 'Валюта договора', ['contract_detail', 'currency'], 9),
|
||
numericColumn('contract.ceiling', 'Предельная ст-ть, тыс. руб. (без НДС)', ['contract_detail', 'ceiling_amount'], 14),
|
||
...QUARTERS.map((quarter) =>
|
||
numericColumn(
|
||
`contract.expenses_q${quarter}`,
|
||
`Расходы ${quarter} кв., тыс. руб. (без НДС)`,
|
||
['contract_detail', `expenses_q${quarter}`],
|
||
13,
|
||
),
|
||
),
|
||
textColumn('contract.vat_rate', 'Ставка НДС (%)', ['contract_detail', 'vat_rate'], 9),
|
||
textColumn('contract.deadline', 'Срок', ['contract_detail', 'deadline'], 9),
|
||
],
|
||
},
|
||
createCorrectionColumns(1),
|
||
createPaymentColumns(1),
|
||
createActualColumns(1),
|
||
createCarryoverColumns(1),
|
||
createCorrectionColumns(2),
|
||
createPaymentColumns(2),
|
||
createActualColumns(2),
|
||
createCarryoverColumns(2),
|
||
createCorrectionColumns(3),
|
||
createPaymentColumns(3),
|
||
createActualColumns(3),
|
||
createCarryoverColumns(3),
|
||
createCorrectionColumns(4),
|
||
createPaymentColumns(4),
|
||
createActualColumns(4),
|
||
];
|
||
|
||
const createColumnBandMap = (columns) => {
|
||
const bands = new Map();
|
||
const addColumn = (column, band) => {
|
||
bands.set(column.id, band);
|
||
column.columns?.forEach((childColumn) => addColumn(childColumn, band));
|
||
};
|
||
columns.forEach((column, index) => addColumn(column, index % 2));
|
||
return bands;
|
||
};
|
||
|
||
const createColumnGroupEndSet = (columns) => {
|
||
const groupEnds = new Set();
|
||
const getLastLeafId = (column) => {
|
||
if (!column.columns?.length) return column.id;
|
||
return getLastLeafId(column.columns.at(-1));
|
||
};
|
||
columns.forEach((column) => {
|
||
if (!column.columns?.length) return;
|
||
groupEnds.add(column.id);
|
||
groupEnds.add(getLastLeafId(column));
|
||
});
|
||
return groupEnds;
|
||
};
|
||
|
||
const disableGroupColumnFilters = (columns) =>
|
||
columns.map((column) => {
|
||
if (!column.columns?.length) return column;
|
||
return {
|
||
...column,
|
||
enableColumnFilter: false,
|
||
columns: disableGroupColumnFilters(column.columns),
|
||
};
|
||
});
|
||
|
||
const COLUMN_CONFIG_CACHE = new Map();
|
||
|
||
const getColumnConfig = (sheet) => {
|
||
if (!COLUMN_CONFIG_CACHE.has(sheet)) {
|
||
const columns = disableGroupColumnFilters(createColumns(sheet));
|
||
COLUMN_CONFIG_CACHE.set(sheet, {
|
||
columns,
|
||
columnBands: createColumnBandMap(columns),
|
||
columnGroupEnds: createColumnGroupEndSet(columns),
|
||
});
|
||
}
|
||
|
||
return COLUMN_CONFIG_CACHE.get(sheet);
|
||
};
|
||
|
||
const matchesSearch = (row, query) => {
|
||
const searchableData = {
|
||
...row.data?.header,
|
||
...row.data?.contract_summary,
|
||
...row.data?.contract_detail,
|
||
};
|
||
return Object.values(searchableData).some((value) =>
|
||
String(value ?? '')
|
||
.toLowerCase()
|
||
.includes(query),
|
||
);
|
||
};
|
||
|
||
export default function Svod2Page() {
|
||
const { getSheetFilters, updateSheetFilters, getSummary, invalidateSummary } = useSvod();
|
||
const [sheet, setSheet] = useState('GO');
|
||
const [tableStateBySheet, setTableStateBySheet] = useState(createInitialTableState);
|
||
const [columnFiltersBySheet, setColumnFiltersBySheet] = useState(() => Object.fromEntries(SHEETS.map(({ value }) => [value, []])));
|
||
const latestSummaryRequestRef = useRef(Object.fromEntries(SHEETS.map(({ value }) => [value, 0])));
|
||
const { year, selectedOrganizations, search } = getSheetFilters(sheet);
|
||
const { rows, loading, error, loadedQueryKey } = tableStateBySheet[sheet];
|
||
const columnFilters = columnFiltersBySheet[sheet];
|
||
const selectedOrganizationIds = useMemo(
|
||
() => (selectedOrganizations.some(({ id }) => id === '__all__') ? [] : selectedOrganizations.map(({ id }) => id)),
|
||
[selectedOrganizations],
|
||
);
|
||
const currentQueryKey = useMemo(
|
||
() => JSON.stringify([sheet, year, selectedOrganizations.map(({ id }) => String(id)).sort()]),
|
||
[sheet, year, selectedOrganizations],
|
||
);
|
||
const hasCurrentData = loadedQueryKey === currentQueryKey;
|
||
const updateCurrentSheetFilters = useCallback((changes) => updateSheetFilters(sheet, changes), [sheet, updateSheetFilters]);
|
||
const updateCurrentColumnFilters = useCallback(
|
||
(updater) => {
|
||
setColumnFiltersBySheet((current) => {
|
||
const currentFilters = current[sheet];
|
||
const nextFilters = typeof updater === 'function' ? updater(currentFilters) : updater;
|
||
return nextFilters === currentFilters ? current : { ...current, [sheet]: nextFilters };
|
||
});
|
||
},
|
||
[sheet],
|
||
);
|
||
|
||
const loadSummary = useCallback(async () => {
|
||
const requestId = latestSummaryRequestRef.current[sheet] + 1;
|
||
latestSummaryRequestRef.current[sheet] = requestId;
|
||
setTableStateBySheet((current) => ({
|
||
...current,
|
||
[sheet]: { ...current[sheet], loading: true, error: '' },
|
||
}));
|
||
try {
|
||
const response = await getSummary(sheet, year, selectedOrganizationIds);
|
||
if (latestSummaryRequestRef.current[sheet] !== requestId) return;
|
||
setTableStateBySheet((current) => ({
|
||
...current,
|
||
[sheet]: { ...current[sheet], rows: response.result || [], loadedQueryKey: currentQueryKey },
|
||
}));
|
||
} catch (loadError) {
|
||
if (latestSummaryRequestRef.current[sheet] !== requestId) return;
|
||
setTableStateBySheet((current) => ({
|
||
...current,
|
||
[sheet]: {
|
||
...current[sheet],
|
||
rows: [],
|
||
error: loadError.response?.data?.detail || 'Не удалось загрузить свод. Попробуйте ещё раз.',
|
||
},
|
||
}));
|
||
} finally {
|
||
if (latestSummaryRequestRef.current[sheet] === requestId) {
|
||
setTableStateBySheet((current) => ({
|
||
...current,
|
||
[sheet]: { ...current[sheet], loading: false },
|
||
}));
|
||
}
|
||
}
|
||
}, [currentQueryKey, getSummary, selectedOrganizationIds, sheet, year]);
|
||
|
||
useEffect(() => {
|
||
return () => {
|
||
SHEETS.forEach(({ value }) => {
|
||
latestSummaryRequestRef.current[value] += 1;
|
||
});
|
||
};
|
||
}, []);
|
||
|
||
const refreshSummary = useCallback(() => {
|
||
invalidateSummary(sheet, year, selectedOrganizationIds);
|
||
loadSummary();
|
||
}, [invalidateSummary, loadSummary, selectedOrganizationIds, sheet, year]);
|
||
|
||
const filteredRows = useMemo(() => {
|
||
if (!hasCurrentData) return [];
|
||
const normalizedSearch = search.trim().toLowerCase();
|
||
return normalizedSearch ? rows.filter((row) => matchesSearch(row, normalizedSearch)) : rows;
|
||
}, [hasCurrentData, rows, search]);
|
||
const { columns, columnBands, columnGroupEnds } = useMemo(() => getColumnConfig(sheet), [sheet]);
|
||
const table = useMaterialReactTable({
|
||
columns,
|
||
data: filteredRows,
|
||
getRowId: (row) => `${row.data?.header?.source_form}-${row.data?.header?.source_id}-${row.data?.line_id}`,
|
||
enableColumnActions: false,
|
||
enableColumnFilters: true,
|
||
enableFacetedValues: true,
|
||
onColumnFiltersChange: updateCurrentColumnFilters,
|
||
enableDensityToggle: false,
|
||
enableFullScreenToggle: false,
|
||
enableHiding: false,
|
||
enablePagination: false,
|
||
enableRowVirtualization: true,
|
||
enableTopToolbar: false,
|
||
enableBottomToolbar: false,
|
||
enableSorting: false,
|
||
rowVirtualizerOptions: ROW_VIRTUALIZER_OPTIONS,
|
||
state: { isLoading: loading, columnFilters },
|
||
initialState: {
|
||
columnPinning: { left: ['smeta_type', 'smeta_direction', 'org_name', 'name'] },
|
||
showColumnFilters: 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 },
|
||
},
|
||
},
|
||
muiTableHeadProps: {
|
||
sx: {
|
||
'& tr:not(:last-of-type) > th > .MuiCollapse-root': {
|
||
display: 'none',
|
||
},
|
||
},
|
||
},
|
||
muiTableHeadCellProps: ({ column }) => ({
|
||
sx: {
|
||
position: 'sticky',
|
||
top: 0,
|
||
boxShadow: 'none',
|
||
backgroundColor: COLUMN_BAND_COLORS.head[columnBands.get(column.id) ?? 0],
|
||
borderRight: columnGroupEnds.has(column.id) ? '0.1875rem solid #9eafa3' : '0.0625rem solid #e5e7eb',
|
||
color: '#4a5565',
|
||
fontSize: '0.75rem',
|
||
fontWeight: 700,
|
||
lineHeight: 1.2,
|
||
px: '0.75rem',
|
||
py: '0.5rem',
|
||
whiteSpace: 'nowrap',
|
||
opacity: 1,
|
||
zIndex: column.getIsPinned() ? 5 : undefined,
|
||
},
|
||
}),
|
||
muiTableHeadRowProps: { sx: { boxShadow: 'none' } },
|
||
muiTableBodyCellProps: ({ column }) => ({
|
||
sx: {
|
||
backgroundColor: COLUMN_BAND_COLORS.body[columnBands.get(column.id) ?? 0],
|
||
borderRight: columnGroupEnds.has(column.id) ? '0.1875rem solid #9eafa3' : '0.0625rem solid #edf0f2',
|
||
fontSize: '0.8125rem',
|
||
px: '0.75rem',
|
||
py: '0.5rem',
|
||
whiteSpace: 'nowrap',
|
||
opacity: 1,
|
||
zIndex: column.getIsPinned() ? 2 : undefined,
|
||
},
|
||
}),
|
||
localization: {
|
||
...MRT_Localization_RU,
|
||
noRecordsToDisplay: 'Нет данных для выбранных параметров',
|
||
},
|
||
});
|
||
let content = <MaterialReactTable table={table} />;
|
||
if (!hasCurrentData && !loading && !error) {
|
||
content = (
|
||
<Alert severity='info'>
|
||
{selectedOrganizations.length === 0
|
||
? 'Выберите одно или несколько ССП/РФ либо пункт «Все ССП/РФ», затем нажмите «Загрузить».'
|
||
: 'Нажмите «Загрузить», чтобы получить данные по выбранным параметрам.'}
|
||
</Alert>
|
||
);
|
||
} else if (error) {
|
||
content = (
|
||
<Alert
|
||
severity='error'
|
||
action={
|
||
<Button color='inherit' size='small' onClick={refreshSummary}>
|
||
Повторить
|
||
</Button>
|
||
}>
|
||
{error}
|
||
</Alert>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Box
|
||
sx={{
|
||
px: '2rem',
|
||
pt: '1rem',
|
||
pb: '1.5rem',
|
||
height: '100%',
|
||
minHeight: 0,
|
||
overflow: 'hidden',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
gap: '1rem',
|
||
}}>
|
||
<HeaderSwitch />
|
||
<Paper elevation={0} sx={{ border: '0.0625rem solid #e5e7eb', borderRadius: '0.75rem', overflow: 'hidden', flexShrink: 0 }}>
|
||
<Tabs
|
||
value={sheet}
|
||
onChange={(_event, value) => setSheet(value)}
|
||
sx={{ px: '0.5rem', minHeight: '3rem', borderBottom: '0.0625rem solid #edf0f2' }}>
|
||
{SHEETS.map((item) => (
|
||
<Tab key={item.value} value={item.value} label={item.label} sx={{ textTransform: 'none', minHeight: '3rem' }} />
|
||
))}
|
||
</Tabs>
|
||
<SvodFilters
|
||
year={year}
|
||
selectedOrganizations={selectedOrganizations}
|
||
search={search}
|
||
onChange={updateCurrentSheetFilters}
|
||
searchPlaceholder='ССП/РФ, код, статья или договор'
|
||
emptyOrganizationsPlaceholder='Выберите ССП/РФ'
|
||
allowSelectAll>
|
||
{hasCurrentData && !loading && filteredRows.length !== rows.length && (
|
||
<Typography variant='caption' color='text.secondary' sx={{ flexShrink: 0, whiteSpace: 'nowrap' }}>
|
||
{`Найдено ${filteredRows.length.toLocaleString('ru-RU')} из ${rows.length.toLocaleString('ru-RU')} строк`}
|
||
</Typography>
|
||
)}
|
||
{hasCurrentData ? (
|
||
<Button
|
||
variant='outlined'
|
||
color='default'
|
||
startIcon={<RefreshRoundedIcon />}
|
||
disabled={loading || selectedOrganizations.length === 0}
|
||
onClick={refreshSummary}
|
||
sx={{ ml: 'auto', height: '2.5rem', flexShrink: 0 }}>
|
||
Обновить
|
||
</Button>
|
||
) : (
|
||
<PrimaryButton
|
||
variant='outlined'
|
||
color='default'
|
||
disabled={loading || selectedOrganizations.length === 0}
|
||
onClick={refreshSummary}
|
||
sx={{ ml: 'auto', height: '2.5rem', flexShrink: 0 }}>
|
||
Загрузить
|
||
</PrimaryButton>
|
||
)}
|
||
</SvodFilters>
|
||
</Paper>
|
||
{content}
|
||
</Box>
|
||
);
|
||
}
|