AURORA-1840: состояние модалок листа вынесено из RealtimeTable #218

Open
aep wants to merge 2 commits from feat/213-aurora-1840-sheet-modals into test
4 changed files with 110 additions and 35 deletions

View File

@ -0,0 +1,42 @@
import { forwardRef, memo, useCallback, useImperativeHandle, useReducer } from 'react';
import { SHEET_MODALS_INITIAL_STATE, getCreateModalConfig, sheetModalsReducer } from '../utils/sheetModalsState';
import { CreateProgramModal } from './CreateProgramProjectModal';
import { SelectExpenseItemModal } from './SelectExpenseItemModal';
import { SelectVspModal } from './SelectVspModal';
const SheetModalsComponent = forwardRef(function SheetModals(
{ formId, sheetName, onAddVsp, onAddExpenseItem, onAddProgram, onAddProject },
ref,
) {
const [state, dispatch] = useReducer(sheetModalsReducer, SHEET_MODALS_INITIAL_STATE);
useImperativeHandle(ref, () => ({ open: (kind) => dispatch({ type: 'open', kind }) }), []);
const close = useCallback(() => dispatch({ type: 'close' }), []);
const createConfig = getCreateModalConfig(state.kind);
const createHandlers = { onAddProgram, onAddProject };
return (
<>
<SelectVspModal isOpen={state.kind === 'vsp'} onClose={close} formId={formId} onSelect={onAddVsp} title='Выбор ВСП' />
<SelectExpenseItemModal
isOpen={state.kind === 'expense'}
onClose={close}
formId={formId}
onSelect={onAddExpenseItem}
title='Добавление строки'
sheet={sheetName}
/>
<CreateProgramModal
isOpen={createConfig !== null}
onClose={close}
title={createConfig?.title}
onCreate={createConfig ? createHandlers[createConfig.handlerKey] : undefined}
/>
</>
);
});
export const SheetModals = memo(SheetModalsComponent);

View File

@ -12,9 +12,7 @@ import { getTableColumns } from './tableColumns';
import { CircularProgress } from '@mui/material';
import { debounce } from '@mui/material';
import { toast } from 'react-toastify';
import { CreateProgramModal } from './Modals/CreateProgramProjectModal';
import { SelectExpenseItemModal } from './Modals/SelectExpenseItemModal';
import { SelectVspModal } from './Modals/SelectVspModal';
import { SheetModals } from './Modals/SheetModals';
import { additionExpenseRowTable, additionVspRowTable } from './constants/addingRowConfig';
import { BASE_TABLE_CONFIG, TABLE_ROW_HEIGHT, getTableBodyCellProps, getTablePaperStyles } from './constants/tableConfig';
import { useRealtimeActions, useVspOptions } from './contexts/RealtimeContext';
@ -103,9 +101,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
const [expanded, setExpanded] = useState(true);
const [expenseItemsVisibilityAction, setExpenseItemsVisibilityAction] = useState('hide');
const [isOpenModalSelectVsp, setIsOpenModalSelectVsp] = useState(false);
const [isOpenModalSelectExpenseItem, setIsOpenModalSelectExpenseItem] = useState(false);
const [createModalType, setCreateModalType] = useState(null);
const modalsRef = useRef(null);
const isFormNoCanAddRow = useMemo(() => FormsNoCanAddRow[formType]?.includes(sheetName) ?? false, [formType, sheetName]);
const canModifyRows = userRoleId === ROLES_NAME_ID.admin || columnsCurStage.length > 0;
@ -554,6 +550,9 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
[handleNavigateToColumn, searchMatches],
);
const handleSearchNext = useCallback(() => navigateToSearchMatch(1), [navigateToSearchMatch]);
const handleSearchPrevious = useCallback(() => navigateToSearchMatch(-1), [navigateToSearchMatch]);
const handleSearchKeyDown = useCallback(
(event) => {
if (event.key !== 'Enter') return;
@ -650,7 +649,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
);
const handleOpenModalSelectVsp = useCallback(() => {
setIsOpenModalSelectVsp(true);
modalsRef.current?.open('vsp');
}, []);
const handleOpenModalSelectExpenseItem = useCallback(() => {
@ -663,7 +662,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
toast.error('Выделите строку проекта');
return;
}
setIsOpenModalSelectExpenseItem(true);
modalsRef.current?.open('expense');
}, [getSelectedRow]);
const handleDeleteRow = useCallback(() => {
@ -699,7 +698,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
toast.error('Добавление строк недоступно: нет доступных для редактирования столбцов');
return;
}
setCreateModalType('program');
modalsRef.current?.open('program');
}, [canModifyRows]);
const addProject = useCallback(() => {
@ -716,13 +715,9 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
toast.error('Выделите строку программы');
return;
}
setCreateModalType('project');
modalsRef.current?.open('project');
}, [canModifyRows, getSelectedRow]);
const closeSelectVspModal = useCallback(() => setIsOpenModalSelectVsp(false), []);
const closeSelectExpenseItemModal = useCallback(() => setIsOpenModalSelectExpenseItem(false), []);
const closeCreateModal = useCallback(() => setCreateModalType(null), []);
useEffect(() => {
return () => {
rowVirtualizerRef.current = null;
@ -783,8 +778,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
onGlobalFilterChange={handleGlobalFilterChange}
globalFilter={globalFilter}
onSearchKeyDown={handleSearchKeyDown}
onSearchNext={() => navigateToSearchMatch(1)}
onSearchPrevious={() => navigateToSearchMatch(-1)}
onSearchNext={handleSearchNext}
onSearchPrevious={handleSearchPrevious}
searchMatchCount={searchMatches.length}
searchMatchIndex={searchMatchIndex}
sizeMult={sizeMult}
@ -848,26 +843,14 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
</div>
</div>
</div>
<SelectVspModal
isOpen={isOpenModalSelectVsp}
onClose={closeSelectVspModal}
<SheetModals
ref={modalsRef}
formId={formId}
onSelect={handleAddVspRow}
title='Выбор ВСП'
/>
<SelectExpenseItemModal
isOpen={isOpenModalSelectExpenseItem}
onClose={closeSelectExpenseItemModal}
formId={formId}
onSelect={handleAddExpenseItemRow}
title='Добавление строки'
sheet={sheetName}
/>
<CreateProgramModal
isOpen={createModalType !== null}
onClose={closeCreateModal}
title={createModalType === 'program' ? 'Создание программы' : 'Создание проекта'}
onCreate={createModalType === 'program' ? handleAddProgramRow : handleAddProjectRow}
sheetName={sheetName}
onAddVsp={handleAddVspRow}
onAddExpenseItem={handleAddExpenseItemRow}
onAddProgram={handleAddProgramRow}
onAddProject={handleAddProjectRow}
/>
</>
);

View File

@ -0,0 +1,21 @@
export const SHEET_MODAL_KINDS = ['vsp', 'expense', 'program', 'project'];
export const SHEET_MODALS_INITIAL_STATE = { kind: null };
export const sheetModalsReducer = (state, action) => {
switch (action?.type) {
case 'open':
return SHEET_MODAL_KINDS.includes(action.kind) ? { kind: action.kind } : state;
case 'close':
return state.kind === null ? state : SHEET_MODALS_INITIAL_STATE;
default:
return state;
}
};
const CREATE_MODAL_CONFIG = {
program: { title: 'Создание программы', handlerKey: 'onAddProgram' },
project: { title: 'Создание проекта', handlerKey: 'onAddProject' },
};
export const getCreateModalConfig = (kind) => CREATE_MODAL_CONFIG[kind] ?? null;

View File

@ -0,0 +1,29 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { SHEET_MODALS_INITIAL_STATE, getCreateModalConfig, sheetModalsReducer } from './sheetModalsState.js';
test('open sets the kind and close resets it', () => {
for (const kind of ['vsp', 'expense', 'program', 'project']) {
const opened = sheetModalsReducer(SHEET_MODALS_INITIAL_STATE, { type: 'open', kind });
assert.deepEqual(opened, { kind });
assert.deepEqual(sheetModalsReducer(opened, { type: 'close' }), SHEET_MODALS_INITIAL_STATE);
}
});
test('opening another kind replaces the current one', () => {
const state = sheetModalsReducer({ kind: 'vsp' }, { type: 'open', kind: 'expense' });
assert.deepEqual(state, { kind: 'expense' });
});
test('unknown kind or action keeps the same state object; closing a closed state too', () => {
assert.equal(sheetModalsReducer(SHEET_MODALS_INITIAL_STATE, { type: 'open', kind: 'nope' }), SHEET_MODALS_INITIAL_STATE);
assert.equal(sheetModalsReducer(SHEET_MODALS_INITIAL_STATE, { type: 'close' }), SHEET_MODALS_INITIAL_STATE);
assert.equal(sheetModalsReducer(SHEET_MODALS_INITIAL_STATE, { type: 'x' }), SHEET_MODALS_INITIAL_STATE);
});
test('create modal config differs for program and project, null for others', () => {
assert.deepEqual(getCreateModalConfig('program'), { title: 'Создание программы', handlerKey: 'onAddProgram' });
assert.deepEqual(getCreateModalConfig('project'), { title: 'Создание проекта', handlerKey: 'onAddProject' });
assert.equal(getCreateModalConfig('vsp'), null);
assert.equal(getCreateModalConfig(null), null);
});