327 lines
10 KiB
JavaScript
327 lines
10 KiB
JavaScript
import { Divider, IconButton, Stack, Tooltip } 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 {
|
|
AddLine,
|
|
AllStageColumns,
|
|
CurrentStageColumns,
|
|
HiddenExpenseItems,
|
|
Minus,
|
|
Pin,
|
|
Plus,
|
|
Program,
|
|
Project,
|
|
RemoveLine,
|
|
Search,
|
|
VisibleExpenseItems,
|
|
} from '../../common/icons/icons';
|
|
import { collectLeafColumns } from '../utils/columnUtils';
|
|
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 = ({
|
|
selectedColumnId,
|
|
columnPinning,
|
|
columns,
|
|
onPinColumn,
|
|
onUnpinColumn,
|
|
onToggleColumnVisibility,
|
|
onSetColumnsVisibility,
|
|
columnVisibility,
|
|
columnsCurStage,
|
|
onChangeSizeMult,
|
|
onGlobalFilterChange,
|
|
globalFilter,
|
|
onSearchKeyDown,
|
|
onSearchNext,
|
|
onSearchPrevious,
|
|
searchMatchCount,
|
|
searchMatchIndex,
|
|
sizeMult,
|
|
onChangeShowColumnFilters,
|
|
showColumnFilters,
|
|
onAddRow,
|
|
onAddProgram,
|
|
onAddProject,
|
|
onDeleteRow,
|
|
formId,
|
|
sheetName,
|
|
direction,
|
|
year,
|
|
isFormNoCanAddRow,
|
|
canModifyRows,
|
|
formType,
|
|
validationErrors = [],
|
|
onNavigateToColumn,
|
|
expenseItemsVisibilityAction,
|
|
onToggleDepthVisibility,
|
|
}) => {
|
|
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' }}>
|
|
<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>
|
|
</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}>
|
|
<Plus />
|
|
<Program />
|
|
</IconButton>
|
|
</Tooltip>
|
|
|
|
<Tooltip title='Добавить проект'>
|
|
<IconButton onClick={onAddProject} variant='outlined' sx={controlSx}>
|
|
<Plus />
|
|
<Project />
|
|
</IconButton>
|
|
</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
|
|
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>
|
|
<ValidationErrorsAlert errors={validationErrors} onNavigateToColumn={onNavigateToColumn} />
|
|
</Panel>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default memo(SettingPanel);
|