AURORA-1840: состояние модалок листа вынесено из RealtimeTable #218
42
web/src/components/RealtimeTable/Modals/SheetModals.jsx
Normal file
42
web/src/components/RealtimeTable/Modals/SheetModals.jsx
Normal 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);
|
||||
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
21
web/src/components/RealtimeTable/utils/sheetModalsState.js
Normal file
21
web/src/components/RealtimeTable/utils/sheetModalsState.js
Normal 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;
|
||||
@ -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);
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user