360 lines
11 KiB
JavaScript
360 lines
11 KiB
JavaScript
import { Divider, IconButton, Stack, Tooltip, Typography } from '@mui/material';
|
||
import { memo, useCallback, useMemo, useState } from 'react';
|
||
import { FORM_TYPE_OPTIONS } from '../../../constants/constants';
|
||
import { exportSheet, exportSheetProject } from '../../../utils/exportFile';
|
||
import { stageColumnsHiddenFromPicker } from '../../Stages/constants';
|
||
import { ExportDefaultButton } from '../../common/Buttons/ButtonsActions';
|
||
import CollapsibleTree from '../../common/CollapsibleTree';
|
||
import SearchComponent from '../../common/SearchComponent';
|
||
import DenominationSelect from '../../common/DenominationSelect';
|
||
import {
|
||
AddLine,
|
||
AllStageColumns,
|
||
CurrentStageColumns,
|
||
EditSvg,
|
||
HiddenExpenseItems,
|
||
Minus,
|
||
Pin,
|
||
Plus,
|
||
Program,
|
||
Project,
|
||
RemoveLine,
|
||
Search,
|
||
VisibleExpenseItems,
|
||
} from '../../common/icons/icons';
|
||
import { collectLeafColumns } from '../utils/columnUtils';
|
||
import CellLegend from './CellLegend';
|
||
import { AHR_LIMIT_SHEET } from '../utils/moneyUtils';
|
||
import { GroupByObject } from './GroupByObject/GroupByObject';
|
||
import { Panel } from './SettingPanel.style';
|
||
import ValidationErrorsAlert from './ValidationErrorsAlert';
|
||
import ZoomSlider from './ZoomSlider';
|
||
|
||
const controlSx = {
|
||
minHeight: '2.5rem',
|
||
flex: '0 0 2.5rem',
|
||
};
|
||
|
||
const searchSx = { height: '2.5rem' };
|
||
const searchNavigationSx = {
|
||
display: 'inline-flex',
|
||
alignItems: 'center',
|
||
height: '2rem',
|
||
border: '1px solid rgba(0, 0, 0, 0.12)',
|
||
borderRadius: '0.4rem',
|
||
overflow: 'hidden',
|
||
backgroundColor: '#fff',
|
||
};
|
||
const searchNavigationButtonSx = {
|
||
width: '1.65rem',
|
||
height: '2rem',
|
||
minWidth: '1.65rem',
|
||
padding: 0,
|
||
borderRadius: 0,
|
||
fontSize: '0.95rem',
|
||
};
|
||
const searchMatchCounterSx = {
|
||
minWidth: '2.7rem',
|
||
padding: '0 0.3rem',
|
||
fontSize: '0.7rem',
|
||
lineHeight: 1,
|
||
textAlign: 'center',
|
||
whiteSpace: 'nowrap',
|
||
color: 'rgba(0, 0, 0, 0.65)',
|
||
};
|
||
const exportButtonSx = { height: '2.5rem', minHeight: '2.5rem' };
|
||
|
||
const SettingPanel = ({
|
||
budgetYear,
|
||
selectedColumnId,
|
||
columnPinning,
|
||
columns,
|
||
onPinColumn,
|
||
onUnpinColumn,
|
||
onToggleColumnVisibility,
|
||
onSetColumnsVisibility,
|
||
columnVisibility,
|
||
columnsCurStage,
|
||
onChangeSizeMult,
|
||
onGlobalFilterChange,
|
||
globalFilter,
|
||
onSearchKeyDown,
|
||
onSearchNext,
|
||
onSearchPrevious,
|
||
searchMatchCount,
|
||
searchMatchIndex,
|
||
sizeMult,
|
||
onChangeShowColumnFilters,
|
||
showColumnFilters,
|
||
onAddRow,
|
||
onAddProgram,
|
||
onAddProject,
|
||
onRenameNode,
|
||
canRenameNode,
|
||
onDeleteRow,
|
||
formId,
|
||
sheetName,
|
||
direction,
|
||
year,
|
||
isFormNoCanAddRow,
|
||
canModifyRows,
|
||
formType,
|
||
validationErrors = [],
|
||
onNavigateToError,
|
||
headerPaths,
|
||
expenseItemsVisibilityAction,
|
||
onToggleDepthVisibility,
|
||
denomination,
|
||
onChangeDenomination,
|
||
}) => {
|
||
const [isExporting, setIsExporting] = useState(false);
|
||
const isPinned = useMemo(
|
||
() => Boolean(selectedColumnId && columnPinning?.left?.includes(selectedColumnId)),
|
||
[columnPinning, selectedColumnId],
|
||
);
|
||
const selectedColumnIds = useMemo(() => Object.keys(columnVisibility).filter((key) => !columnVisibility[key]), [columnVisibility]);
|
||
|
||
const handleChangeShowColumnFilters = useCallback(() => {
|
||
onChangeShowColumnFilters(!showColumnFilters);
|
||
}, [onChangeShowColumnFilters, showColumnFilters]);
|
||
|
||
const handleExport = useCallback(async () => {
|
||
if (isExporting) return;
|
||
if (!formId || !sheetName || (formType === 'PROJECT' && !year)) return;
|
||
|
||
setIsExporting(true);
|
||
try {
|
||
if (formType === 'PROJECT') {
|
||
await exportSheetProject(formId, sheetName, year);
|
||
} else {
|
||
await exportSheet(formId, sheetName, direction);
|
||
}
|
||
} finally {
|
||
setIsExporting(false);
|
||
}
|
||
}, [direction, formId, formType, isExporting, sheetName, year]);
|
||
|
||
const handleTogglePin = useCallback(() => {
|
||
if (!selectedColumnId) return;
|
||
isPinned ? onUnpinColumn?.(selectedColumnId) : onPinColumn?.(selectedColumnId);
|
||
}, [isPinned, onPinColumn, onUnpinColumn, selectedColumnId]);
|
||
const leafColumns = useMemo(() => collectLeafColumns({ columns }), [columns]);
|
||
const stageColumnIds = useMemo(() => new Set([...stageColumnsHiddenFromPicker, ...(columnsCurStage || [])]), [columnsCurStage]);
|
||
const showOnlyCurrentStageColumns = useMemo(() => {
|
||
if (!columnsCurStage?.length || !leafColumns.length) return false;
|
||
return leafColumns.every((column) =>
|
||
stageColumnIds.has(column.id) ? columnVisibility[column.id] !== false : columnVisibility[column.id] === false,
|
||
);
|
||
}, [columnVisibility, columnsCurStage, leafColumns, stageColumnIds]);
|
||
|
||
const handleToggleCurrentStageColumns = useCallback(() => {
|
||
if (!columnsCurStage?.length) {
|
||
console.warn('Нет столбцов для текущего этапа');
|
||
return;
|
||
}
|
||
const visibilityByColumnId = {};
|
||
for (const column of leafColumns) {
|
||
visibilityByColumnId[column.id] = showOnlyCurrentStageColumns || stageColumnIds.has(column.id);
|
||
}
|
||
onSetColumnsVisibility(visibilityByColumnId);
|
||
}, [columnsCurStage, leafColumns, onSetColumnsVisibility, showOnlyCurrentStageColumns, stageColumnIds]);
|
||
|
||
const handleSetAllColumnsVisibility = useCallback(
|
||
(columnIds, isVisible) => {
|
||
const visibilityByColumnId = Object.fromEntries(columnIds.map((columnId) => [columnId, isVisible]));
|
||
onSetColumnsVisibility(visibilityByColumnId);
|
||
},
|
||
[onSetColumnsVisibility],
|
||
);
|
||
|
||
return (
|
||
<>
|
||
<Panel data-pin-panel>
|
||
<Stack direction='row' sx={{ gap: '1.25rem', alignItems: 'center' }}>
|
||
{validationErrors.length > 0 && (
|
||
<>
|
||
<ValidationErrorsAlert errors={validationErrors} onNavigateToError={onNavigateToError} headerPaths={headerPaths} />
|
||
<Divider orientation='vertical' flexItem />
|
||
</>
|
||
)}
|
||
<GroupByObject title='Отображение'>
|
||
{/* <ColorPickerButton onColorApply={addColorForCells} /> */}
|
||
|
||
<Tooltip title='Закрепить область'>
|
||
<IconButton
|
||
variant='outlined'
|
||
sx={controlSx}
|
||
data-active={isPinned}
|
||
disabled={!selectedColumnId}
|
||
onMouseDown={(e) => e.stopPropagation()}
|
||
onClick={handleTogglePin}>
|
||
<Pin />
|
||
</IconButton>
|
||
</Tooltip>
|
||
{sheetName !== AHR_LIMIT_SHEET && (
|
||
<>
|
||
<DenominationSelect value={denomination} onChange={onChangeDenomination} minWidth={130} />
|
||
<Typography variant='caption' color='text.secondary' sx={{ whiteSpace: 'nowrap' }}>
|
||
Ввод сумм — в тыс. руб.
|
||
</Typography>
|
||
</>
|
||
)}
|
||
</GroupByObject>
|
||
|
||
<Divider orientation='vertical' flexItem />
|
||
<GroupByObject title='Строки'>
|
||
{!isFormNoCanAddRow && canModifyRows && (
|
||
<>
|
||
<Tooltip title='Добавить строку'>
|
||
<IconButton onClick={onAddRow} variant='outlined' sx={controlSx}>
|
||
<Plus />
|
||
<AddLine />
|
||
</IconButton>
|
||
</Tooltip>
|
||
|
||
<Tooltip title='Удалить строку'>
|
||
<IconButton onClick={onDeleteRow} variant='outlined' sx={controlSx}>
|
||
<Minus />
|
||
<RemoveLine />
|
||
</IconButton>
|
||
</Tooltip>
|
||
{/* проверяем на 4 форму */}
|
||
{formType === FORM_TYPE_OPTIONS[2] && (
|
||
<>
|
||
<Tooltip title='Добавить программу'>
|
||
<IconButton onClick={onAddProgram} variant='outlined' sx={controlSx}>
|
||
<Program />
|
||
</IconButton>
|
||
</Tooltip>
|
||
|
||
<Tooltip title='Добавить проект'>
|
||
<IconButton onClick={onAddProject} variant='outlined' sx={controlSx}>
|
||
<Project />
|
||
</IconButton>
|
||
</Tooltip>
|
||
|
||
<Tooltip title='Переименовать'>
|
||
<span>
|
||
<IconButton onClick={onRenameNode} disabled={!canRenameNode} variant='outlined' sx={controlSx}>
|
||
<EditSvg />
|
||
</IconButton>
|
||
</span>
|
||
</Tooltip>
|
||
</>
|
||
)}
|
||
</>
|
||
)}
|
||
<Tooltip
|
||
title={
|
||
expenseItemsVisibilityAction === 'show'
|
||
? 'Показать следующий уровень статей расходов'
|
||
: 'Скрыть нижний уровень статей расходов'
|
||
}>
|
||
<IconButton
|
||
onClick={onToggleDepthVisibility}
|
||
variant='outlined'
|
||
data-active={expenseItemsVisibilityAction === 'show'}
|
||
sx={controlSx}>
|
||
{expenseItemsVisibilityAction === 'show' ? <VisibleExpenseItems /> : <HiddenExpenseItems />}
|
||
</IconButton>
|
||
</Tooltip>
|
||
</GroupByObject>
|
||
|
||
<Divider orientation='vertical' flexItem />
|
||
<GroupByObject title='Столбцы'>
|
||
{columns ? (
|
||
<CollapsibleTree
|
||
budgetYear={budgetYear}
|
||
columnTree={columns}
|
||
onSelectNode={onToggleColumnVisibility}
|
||
onSelectAllNodes={handleSetAllColumnsVisibility}
|
||
selectedIds={selectedColumnIds}
|
||
/>
|
||
) : (
|
||
''
|
||
)}
|
||
<Tooltip title={showOnlyCurrentStageColumns ? 'Показать все столбцы' : 'Скрыть все столбцы кроме текущих'}>
|
||
<IconButton
|
||
variant='outlined'
|
||
sx={controlSx}
|
||
color='success'
|
||
data-active={showOnlyCurrentStageColumns}
|
||
onClick={handleToggleCurrentStageColumns}
|
||
disabled={!columnsCurStage || columnsCurStage.length === 0}>
|
||
{showOnlyCurrentStageColumns ? <AllStageColumns /> : <CurrentStageColumns />}
|
||
</IconButton>
|
||
</Tooltip>
|
||
</GroupByObject>
|
||
<Divider orientation='vertical' flexItem />
|
||
<GroupByObject title='Масштаб'>
|
||
<ZoomSlider onChange={onChangeSizeMult} curScale={sizeMult} />
|
||
</GroupByObject>
|
||
<Divider orientation='vertical' flexItem />
|
||
<GroupByObject title='Поиск'>
|
||
<SearchComponent
|
||
value={globalFilter}
|
||
onChange={onGlobalFilterChange}
|
||
onKeyDown={onSearchKeyDown}
|
||
height='2.5rem'
|
||
sx={searchSx}
|
||
/>
|
||
<span style={searchNavigationSx}>
|
||
<Tooltip title='Предыдущий результат (Shift+Enter)'>
|
||
<span>
|
||
<IconButton
|
||
onClick={onSearchPrevious}
|
||
disabled={!searchMatchCount}
|
||
sx={searchNavigationButtonSx}
|
||
aria-label='Предыдущий результат поиска'>
|
||
↑
|
||
</IconButton>
|
||
</span>
|
||
</Tooltip>
|
||
<span style={searchMatchCounterSx}>
|
||
{searchMatchCount ? `${Math.max(searchMatchIndex + 1, 1)}/${searchMatchCount}` : '0/0'}
|
||
</span>
|
||
<Tooltip title='Следующий результат (Enter)'>
|
||
<span>
|
||
<IconButton
|
||
onClick={onSearchNext}
|
||
disabled={!searchMatchCount}
|
||
sx={searchNavigationButtonSx}
|
||
aria-label='Следующий результат поиска'>
|
||
↓
|
||
</IconButton>
|
||
</span>
|
||
</Tooltip>
|
||
</span>
|
||
<Tooltip title='Поиск по колонкам'>
|
||
<IconButton
|
||
variant='outlined'
|
||
color='success'
|
||
data-active={showColumnFilters}
|
||
onClick={handleChangeShowColumnFilters}
|
||
sx={controlSx}>
|
||
<Search />
|
||
</IconButton>
|
||
</Tooltip>
|
||
</GroupByObject>
|
||
|
||
<Divider orientation='vertical' flexItem />
|
||
|
||
<GroupByObject title='Экспорт'>
|
||
<ExportDefaultButton
|
||
onClick={handleExport}
|
||
disabled={!formId || !sheetName || isExporting}
|
||
text={isExporting ? 'Экспорт...' : 'Экспорт'}
|
||
sx={exportButtonSx}
|
||
/>
|
||
</GroupByObject>
|
||
</Stack>
|
||
<Stack direction='row' sx={{ alignItems: 'center', gap: 0.5 }}>
|
||
<CellLegend />
|
||
</Stack>
|
||
</Panel>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default memo(SettingPanel);
|