AURORA-1980: переименование программы и проекта
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9218d2f7f5
commit
78227b46c0
61
web/src/components/RealtimeTable/Modals/RenameNodeModal.jsx
Normal file
61
web/src/components/RealtimeTable/Modals/RenameNodeModal.jsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { Box, Divider, IconButton, Stack } from '@mui/material';
|
||||||
|
import { memo, useEffect, useState } from 'react';
|
||||||
|
import { DangerOutlinedButton, PrimaryButton } from '../../common/Buttons/Buttons';
|
||||||
|
import { HeaderContainer, ModalBackdrop, ModalContainer, ModalTitle } from '../../common/Modal/ModalStyled';
|
||||||
|
import { StyledTextField } from '../../common/StyledTextField';
|
||||||
|
import { Cancel } from '../../common/icons/icons';
|
||||||
|
|
||||||
|
const TITLES = { program: 'Переименование программы', project: 'Переименование проекта' };
|
||||||
|
|
||||||
|
// AURORA-1980: target — результат getRenameTarget; onSubmit получает обрезанное название.
|
||||||
|
// Окно само не закрывается после отправки: закрытие/остаток при ошибке решает родитель.
|
||||||
|
const RenameNodeModalComponent = ({ target, isSaving = false, onClose, onSubmit }) => {
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setName(target?.name ?? '');
|
||||||
|
}, [target]);
|
||||||
|
|
||||||
|
if (!target) return null;
|
||||||
|
|
||||||
|
const isFormValid = name.trim() !== '';
|
||||||
|
|
||||||
|
const handleBackdropClick = (e) => {
|
||||||
|
if (e.target === e.currentTarget) onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
if (!isFormValid || isSaving) return;
|
||||||
|
onSubmit?.(name.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalBackdrop isOpen onClick={handleBackdropClick}>
|
||||||
|
<ModalContainer height='max-content'>
|
||||||
|
<HeaderContainer>
|
||||||
|
<Stack direction='row' spacing={2} sx={{ alignItems: 'center' }}>
|
||||||
|
<ModalTitle>{TITLES[target.kind]}</ModalTitle>
|
||||||
|
</Stack>
|
||||||
|
<IconButton size='small' onClick={onClose}>
|
||||||
|
<Cancel />
|
||||||
|
</IconButton>
|
||||||
|
</HeaderContainer>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Divider sx={{ marginBottom: '0.75rem' }} />
|
||||||
|
<StyledTextField label='Название' value={name} onChange={(e) => setName(e.target.value)} />
|
||||||
|
<Stack direction='row' spacing={0.75} sx={{ justifyContent: 'flex-end', marginTop: '1.5rem', width: '100%' }}>
|
||||||
|
<DangerOutlinedButton onClick={onClose} disabled={isSaving}>
|
||||||
|
Отмена
|
||||||
|
</DangerOutlinedButton>
|
||||||
|
<PrimaryButton onClick={handleSave} disabled={!isFormValid || isSaving} loading={isSaving}>
|
||||||
|
Сохранить
|
||||||
|
</PrimaryButton>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</ModalContainer>
|
||||||
|
</ModalBackdrop>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RenameNodeModal = memo(RenameNodeModalComponent);
|
||||||
@ -13,6 +13,7 @@ import { CircularProgress } from '@mui/material';
|
|||||||
import { debounce } from '@mui/material';
|
import { debounce } from '@mui/material';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { ConfirmDeleteRowModal } from './Modals/ConfirmDeleteRowModal';
|
import { ConfirmDeleteRowModal } from './Modals/ConfirmDeleteRowModal';
|
||||||
|
import { RenameNodeModal } from './Modals/RenameNodeModal';
|
||||||
import { SheetModals } from './Modals/SheetModals';
|
import { SheetModals } from './Modals/SheetModals';
|
||||||
import { additionExpenseRowTable, additionVspRowTable } from './constants/addingRowConfig';
|
import { additionExpenseRowTable, additionVspRowTable } from './constants/addingRowConfig';
|
||||||
import { BASE_TABLE_CONFIG, TABLE_ROW_HEIGHT, TABLE_CONTAINER_SX, getTablePaperStyles, getTableRowHeightPx } from './constants/tableConfig';
|
import { BASE_TABLE_CONFIG, TABLE_ROW_HEIGHT, TABLE_CONTAINER_SX, getTablePaperStyles, getTableRowHeightPx } from './constants/tableConfig';
|
||||||
@ -28,7 +29,7 @@ import { useValidationRules } from './hooks/useValidationRules';
|
|||||||
import { getColumnVirtualizerOptions, getOrderedVisibleColumns } from './utils/columnLayoutUtils';
|
import { getColumnVirtualizerOptions, getOrderedVisibleColumns } from './utils/columnLayoutUtils';
|
||||||
import { isCellEditable } from './utils/cellEditability';
|
import { isCellEditable } from './utils/cellEditability';
|
||||||
import { findNextEditableColumnId, findNextEditableRowId } from './utils/cellNavigation';
|
import { findNextEditableColumnId, findNextEditableRowId } from './utils/cellNavigation';
|
||||||
import { canAddRowForHeader, getDeleteTarget, getProjectAddTarget } from './utils/rowUtils';
|
import { canAddRowForHeader, getDeleteTarget, getProjectAddTarget, getRenameTarget, isRenameResponse } from './utils/rowUtils';
|
||||||
import { clearLegacyFormulaKeys } from './utils/localStorageUtils';
|
import { clearLegacyFormulaKeys } from './utils/localStorageUtils';
|
||||||
import { formatHeaderPaths, getValidationRowKey } from './utils/validationUtils';
|
import { formatHeaderPaths, getValidationRowKey } from './utils/validationUtils';
|
||||||
|
|
||||||
@ -139,6 +140,9 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
|
|
||||||
// AURORA-1978: { kind: 'row' | 'program' | 'project', id, name } — см. getDeleteTarget.
|
// AURORA-1978: { kind: 'row' | 'program' | 'project', id, name } — см. getDeleteTarget.
|
||||||
const [pendingDelete, setPendingDelete] = useState(null);
|
const [pendingDelete, setPendingDelete] = useState(null);
|
||||||
|
// AURORA-1980: { kind: 'program' | 'project', id, name } — см. getRenameTarget.
|
||||||
|
const [pendingRename, setPendingRename] = useState(null);
|
||||||
|
const [isRenameSaving, setIsRenameSaving] = useState(false);
|
||||||
const modalsRef = useRef(null);
|
const modalsRef = useRef(null);
|
||||||
|
|
||||||
const isFormNoCanAddRow = useMemo(() => FormsNoCanAddRow[formType]?.includes(sheetName) ?? false, [formType, sheetName]);
|
const isFormNoCanAddRow = useMemo(() => FormsNoCanAddRow[formType]?.includes(sheetName) ?? false, [formType, sheetName]);
|
||||||
@ -174,6 +178,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
deleteRow: contextDeleteRow,
|
deleteRow: contextDeleteRow,
|
||||||
deleteProgram: contextDeleteProgram,
|
deleteProgram: contextDeleteProgram,
|
||||||
deleteProject: contextDeleteProject,
|
deleteProject: contextDeleteProject,
|
||||||
|
renameNode: contextRenameNode,
|
||||||
|
subscribeToNodeRenameResult,
|
||||||
startEditing: contextStartEditing,
|
startEditing: contextStartEditing,
|
||||||
addProgram: contextAddProgram,
|
addProgram: contextAddProgram,
|
||||||
addProject: contextAddProject,
|
addProject: contextAddProject,
|
||||||
@ -986,6 +992,37 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
setRowSelection({});
|
setRowSelection({});
|
||||||
}, [contextDeleteProgram, contextDeleteProject, contextDeleteRow, pendingDelete]);
|
}, [contextDeleteProgram, contextDeleteProject, contextDeleteRow, pendingDelete]);
|
||||||
|
|
||||||
|
// AURORA-1980: ответ на своё переименование — успех закрывает окно, ошибка
|
||||||
|
// оставляет его с введённым текстом (тост показывает общий обработчик).
|
||||||
|
useEffect(
|
||||||
|
() =>
|
||||||
|
subscribeToNodeRenameResult((msg) => {
|
||||||
|
if (!isRenameResponse(msg, pendingRename)) return;
|
||||||
|
setIsRenameSaving(false);
|
||||||
|
if (!msg.error) setPendingRename(null);
|
||||||
|
}),
|
||||||
|
[pendingRename, subscribeToNodeRenameResult],
|
||||||
|
);
|
||||||
|
|
||||||
|
const canRenameNode = Boolean(getRenameTarget(getSelectedRow()?.original));
|
||||||
|
|
||||||
|
const handleRenameNode = useCallback(() => {
|
||||||
|
setPendingRename(getRenameTarget(getSelectedRow()?.original));
|
||||||
|
}, [getSelectedRow]);
|
||||||
|
|
||||||
|
const closeRenameNode = useCallback(() => {
|
||||||
|
setPendingRename(null);
|
||||||
|
setIsRenameSaving(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const submitRenameNode = useCallback(
|
||||||
|
(name) => {
|
||||||
|
if (!pendingRename) return;
|
||||||
|
setIsRenameSaving(contextRenameNode(pendingRename.kind, pendingRename.id, name));
|
||||||
|
},
|
||||||
|
[contextRenameNode, pendingRename],
|
||||||
|
);
|
||||||
|
|
||||||
const addRow = useCallback(() => {
|
const addRow = useCallback(() => {
|
||||||
if (!canModifyRows) {
|
if (!canModifyRows) {
|
||||||
toast.error('Добавление строк недоступно: нет доступных для редактирования столбцов');
|
toast.error('Добавление строк недоступно: нет доступных для редактирования столбцов');
|
||||||
@ -1086,6 +1123,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
onAddRow={addRow}
|
onAddRow={addRow}
|
||||||
onAddProgram={addProgram}
|
onAddProgram={addProgram}
|
||||||
onAddProject={addProject}
|
onAddProject={addProject}
|
||||||
|
onRenameNode={handleRenameNode}
|
||||||
|
canRenameNode={canRenameNode}
|
||||||
onDeleteRow={handleDeleteRow}
|
onDeleteRow={handleDeleteRow}
|
||||||
formId={formId}
|
formId={formId}
|
||||||
sheetName={sheetName}
|
sheetName={sheetName}
|
||||||
@ -1146,6 +1185,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ConfirmDeleteRowModal target={pendingDelete} onClose={closeConfirmDeleteRow} onConfirm={confirmDeleteRow} />
|
<ConfirmDeleteRowModal target={pendingDelete} onClose={closeConfirmDeleteRow} onConfirm={confirmDeleteRow} />
|
||||||
|
<RenameNodeModal target={pendingRename} isSaving={isRenameSaving} onClose={closeRenameNode} onSubmit={submitRenameNode} />
|
||||||
<SheetModals
|
<SheetModals
|
||||||
ref={modalsRef}
|
ref={modalsRef}
|
||||||
formId={formId}
|
formId={formId}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
AddLine,
|
AddLine,
|
||||||
AllStageColumns,
|
AllStageColumns,
|
||||||
CurrentStageColumns,
|
CurrentStageColumns,
|
||||||
|
EditSvg,
|
||||||
HiddenExpenseItems,
|
HiddenExpenseItems,
|
||||||
Minus,
|
Minus,
|
||||||
Pin,
|
Pin,
|
||||||
@ -88,6 +89,8 @@ const SettingPanel = ({
|
|||||||
onAddRow,
|
onAddRow,
|
||||||
onAddProgram,
|
onAddProgram,
|
||||||
onAddProject,
|
onAddProject,
|
||||||
|
onRenameNode,
|
||||||
|
canRenameNode,
|
||||||
onDeleteRow,
|
onDeleteRow,
|
||||||
formId,
|
formId,
|
||||||
sheetName,
|
sheetName,
|
||||||
@ -229,6 +232,14 @@ const SettingPanel = ({
|
|||||||
<Project />
|
<Project />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip title='Переименовать'>
|
||||||
|
<span>
|
||||||
|
<IconButton onClick={onRenameNode} disabled={!canRenameNode} variant='outlined' sx={controlSx}>
|
||||||
|
<EditSvg />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -117,6 +117,11 @@ export const RealtimeProvider = ({ children, formId, sheetName, direction, year,
|
|||||||
console.log(data.error);
|
console.log(data.error);
|
||||||
toast.error(getWsErrorMessage(data.error));
|
toast.error(getWsErrorMessage(data.error));
|
||||||
onErrorRef.current?.(data.error);
|
onErrorRef.current?.(data.error);
|
||||||
|
// AURORA-1980: окну переименования нужно полное сообщение (event + data),
|
||||||
|
// а не только текст ошибки.
|
||||||
|
if (data.event === 'program_updated' || data.event === 'project_updated') {
|
||||||
|
onNodeRenameResultRef.current?.(data);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,6 +181,15 @@ export const RealtimeProvider = ({ children, formId, sheetName, direction, year,
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// AURORA-1980: переименование программы/проекта формы 4.
|
||||||
|
case 'program_updated':
|
||||||
|
case 'project_updated':
|
||||||
|
if (data.result) {
|
||||||
|
onNodeUpdateRef.current?.(data);
|
||||||
|
onNodeRenameResultRef.current?.(data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.log('Unknown event:', data.event);
|
console.log('Unknown event:', data.event);
|
||||||
}
|
}
|
||||||
@ -189,6 +203,8 @@ export const RealtimeProvider = ({ children, formId, sheetName, direction, year,
|
|||||||
const onProgramAddRef = useRef(null);
|
const onProgramAddRef = useRef(null);
|
||||||
const onProjectAddRef = useRef(null);
|
const onProjectAddRef = useRef(null);
|
||||||
const onNodeDeleteRef = useRef(null);
|
const onNodeDeleteRef = useRef(null);
|
||||||
|
const onNodeUpdateRef = useRef(null);
|
||||||
|
const onNodeRenameResultRef = useRef(null);
|
||||||
const onErrorRef = useRef(null);
|
const onErrorRef = useRef(null);
|
||||||
const onAuthSuccessRef = useRef(null);
|
const onAuthSuccessRef = useRef(null);
|
||||||
const onCellEditStartRef = useRef(null);
|
const onCellEditStartRef = useRef(null);
|
||||||
@ -450,6 +466,25 @@ export const RealtimeProvider = ({ children, formId, sheetName, direction, year,
|
|||||||
[addCommonField, isConnectedRef, sendMessage],
|
[addCommonField, isConnectedRef, sendMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// AURORA-1980: переименование программы/проекта формы 4.
|
||||||
|
const renameNode = useCallback(
|
||||||
|
(kind, id, name) => {
|
||||||
|
if (!isConnectedRef.current) {
|
||||||
|
onErrorRef.current?.('Нет подключения или авторизации');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = {
|
||||||
|
event: `${kind}_updated`,
|
||||||
|
data: { [`${kind}_id`]: id, name },
|
||||||
|
};
|
||||||
|
addCommonField(message);
|
||||||
|
|
||||||
|
return sendMessage(message);
|
||||||
|
},
|
||||||
|
[addCommonField, isConnectedRef, sendMessage],
|
||||||
|
);
|
||||||
|
|
||||||
const subscribeToCellUpdates = useCallback((callback) => {
|
const subscribeToCellUpdates = useCallback((callback) => {
|
||||||
onCellUpdateRef.current = callback;
|
onCellUpdateRef.current = callback;
|
||||||
return () => {
|
return () => {
|
||||||
@ -492,6 +527,20 @@ export const RealtimeProvider = ({ children, formId, sheetName, direction, year,
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const subscribeToNodeUpdates = useCallback((callback) => {
|
||||||
|
onNodeUpdateRef.current = callback;
|
||||||
|
return () => {
|
||||||
|
onNodeUpdateRef.current = null;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const subscribeToNodeRenameResult = useCallback((callback) => {
|
||||||
|
onNodeRenameResultRef.current = callback;
|
||||||
|
return () => {
|
||||||
|
onNodeRenameResultRef.current = null;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const subscribeToErrors = useCallback((callback) => {
|
const subscribeToErrors = useCallback((callback) => {
|
||||||
onErrorRef.current = callback;
|
onErrorRef.current = callback;
|
||||||
return () => {
|
return () => {
|
||||||
@ -538,11 +587,14 @@ export const RealtimeProvider = ({ children, formId, sheetName, direction, year,
|
|||||||
deleteRow,
|
deleteRow,
|
||||||
deleteProgram,
|
deleteProgram,
|
||||||
deleteProject,
|
deleteProject,
|
||||||
|
renameNode,
|
||||||
subscribeToRowAdds,
|
subscribeToRowAdds,
|
||||||
subscribeToRowDeletes,
|
subscribeToRowDeletes,
|
||||||
subscribeToProgramAdds,
|
subscribeToProgramAdds,
|
||||||
subscribeToProjectAdds,
|
subscribeToProjectAdds,
|
||||||
subscribeToNodeDeletes,
|
subscribeToNodeDeletes,
|
||||||
|
subscribeToNodeUpdates,
|
||||||
|
subscribeToNodeRenameResult,
|
||||||
subscribeToErrors,
|
subscribeToErrors,
|
||||||
subscribeToAuthSuccess,
|
subscribeToAuthSuccess,
|
||||||
subscribeToCellEditStart,
|
subscribeToCellEditStart,
|
||||||
@ -560,6 +612,7 @@ export const RealtimeProvider = ({ children, formId, sheetName, direction, year,
|
|||||||
deleteProject,
|
deleteProject,
|
||||||
deleteRow,
|
deleteRow,
|
||||||
endEditing,
|
endEditing,
|
||||||
|
renameNode,
|
||||||
lockedCellsStore,
|
lockedCellsStore,
|
||||||
retryLogin,
|
retryLogin,
|
||||||
startEditing,
|
startEditing,
|
||||||
@ -569,6 +622,8 @@ export const RealtimeProvider = ({ children, formId, sheetName, direction, year,
|
|||||||
subscribeToCellUpdates,
|
subscribeToCellUpdates,
|
||||||
subscribeToErrors,
|
subscribeToErrors,
|
||||||
subscribeToNodeDeletes,
|
subscribeToNodeDeletes,
|
||||||
|
subscribeToNodeRenameResult,
|
||||||
|
subscribeToNodeUpdates,
|
||||||
subscribeToProgramAdds,
|
subscribeToProgramAdds,
|
||||||
subscribeToProjectAdds,
|
subscribeToProjectAdds,
|
||||||
subscribeToRowAdds,
|
subscribeToRowAdds,
|
||||||
|
|||||||
@ -33,6 +33,7 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
|||||||
subscribeToProjectAdds,
|
subscribeToProjectAdds,
|
||||||
subscribeToRowDeletes,
|
subscribeToRowDeletes,
|
||||||
subscribeToNodeDeletes,
|
subscribeToNodeDeletes,
|
||||||
|
subscribeToNodeUpdates,
|
||||||
subscribeToErrors,
|
subscribeToErrors,
|
||||||
subscribeToCellEditStart,
|
subscribeToCellEditStart,
|
||||||
subscribeToCellEditEnd,
|
subscribeToCellEditEnd,
|
||||||
@ -213,6 +214,11 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// AURORA-1980: переименование — имя в строке и пересчёт приходят всем.
|
||||||
|
const unsubscribeNodeUpdates = subscribeToNodeUpdates((msg) => {
|
||||||
|
setData((prevData) => updateCells(prevData, alignNodeRows(prevData, msg.result[1] ?? [])));
|
||||||
|
});
|
||||||
|
|
||||||
// Обработчик начала редактирования ячейки
|
// Обработчик начала редактирования ячейки
|
||||||
const unsubscribeCellEditStart = subscribeToCellEditStart((data) => {
|
const unsubscribeCellEditStart = subscribeToCellEditStart((data) => {
|
||||||
const dataCell = data.data;
|
const dataCell = data.data;
|
||||||
@ -253,6 +259,7 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
|||||||
unsubscribeProjectAdds();
|
unsubscribeProjectAdds();
|
||||||
unsubscribeRowDeletes();
|
unsubscribeRowDeletes();
|
||||||
unsubscribeNodeDeletes();
|
unsubscribeNodeDeletes();
|
||||||
|
unsubscribeNodeUpdates();
|
||||||
unsubscribeErrors();
|
unsubscribeErrors();
|
||||||
unsubscribeCellEditStart();
|
unsubscribeCellEditStart();
|
||||||
unsubscribeCellEditEnd();
|
unsubscribeCellEditEnd();
|
||||||
@ -263,6 +270,7 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
|||||||
subscribeToRowAdds,
|
subscribeToRowAdds,
|
||||||
subscribeToRowDeletes,
|
subscribeToRowDeletes,
|
||||||
subscribeToNodeDeletes,
|
subscribeToNodeDeletes,
|
||||||
|
subscribeToNodeUpdates,
|
||||||
subscribeToErrors,
|
subscribeToErrors,
|
||||||
subscribeToCellEditStart,
|
subscribeToCellEditStart,
|
||||||
subscribeToCellEditEnd,
|
subscribeToCellEditEnd,
|
||||||
|
|||||||
@ -440,3 +440,10 @@ test('AURORA-1984: insertCell после addRootRow — первый расхо
|
|||||||
const result = insertCell(withRoot, ['INPUT', 3, 3, { line_id: 5, header: { project_id: 21 } }], 21);
|
const result = insertCell(withRoot, ['INPUT', 3, 3, { line_id: 5, header: { project_id: 21 } }], 21);
|
||||||
assert.equal(result[0].subRows[0].subRows[0].data.line_id, 5);
|
assert.equal(result[0].subRows[0].subRows[0].data.line_id, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('AURORA-1980: ответ переименования обновляет название в строке', () => {
|
||||||
|
const local = [node('ROOT', 1, { section_code: 'CAP' }, [node('ITEM', 2, { project_id: 21, name: 'Старое' })])];
|
||||||
|
const rows = [['ITEM', 2, 5, { header: { project_id: 21, name: 'Новое' } }]];
|
||||||
|
const result = updateCells(local, alignNodeRows(local, rows));
|
||||||
|
assert.equal(result[0].subRows[0].data.header.name, 'Новое');
|
||||||
|
});
|
||||||
|
|||||||
@ -103,3 +103,22 @@ export function getParentId(data) {
|
|||||||
export function canAddRowForHeader(header) {
|
export function canAddRowForHeader(header) {
|
||||||
return header?.can_add_row !== false;
|
return header?.can_add_row !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AURORA-1980: что переименовывает кнопка панели — программу (GROUP) или проект (ITEM) формы 4.
|
||||||
|
export function getRenameTarget(rowData) {
|
||||||
|
const header = rowData?.data?.header;
|
||||||
|
if (rowData?.row_type === 'GROUP' && header?.program_id != null) {
|
||||||
|
return { kind: 'program', id: header.program_id, name: header.name ?? '' };
|
||||||
|
}
|
||||||
|
if (rowData?.row_type === 'ITEM' && header?.project_id != null) {
|
||||||
|
return { kind: 'project', id: header.project_id, name: header.name ?? '' };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AURORA-1980: WS без идентификатора запроса — свой ответ на своё переименование
|
||||||
|
// узнаём по is_self, событию и id узла (чужое переименование окно не закрывает).
|
||||||
|
export function isRenameResponse(msg, target) {
|
||||||
|
if (!msg || !target || msg.is_self !== true) return false;
|
||||||
|
return msg.event === `${target.kind}_updated` && msg.data?.[`${target.kind}_id`] === target.id;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,14 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import { buildRowHierarchy, canAddRowForHeader, getDataRowId, getDeleteTarget, getProjectAddTarget } from './rowUtils.js';
|
import {
|
||||||
|
buildRowHierarchy,
|
||||||
|
canAddRowForHeader,
|
||||||
|
getDataRowId,
|
||||||
|
getDeleteTarget,
|
||||||
|
getProjectAddTarget,
|
||||||
|
getRenameTarget,
|
||||||
|
isRenameResponse,
|
||||||
|
} from './rowUtils.js';
|
||||||
|
|
||||||
test('row hierarchy uses row types when depth skips a level and keeps INPUT rows as siblings', () => {
|
test('row hierarchy uses row types when depth skips a level and keeps INPUT rows as siblings', () => {
|
||||||
const rows = [
|
const rows = [
|
||||||
@ -97,3 +105,22 @@ test('AURORA-1984: getProjectAddTarget — цель по выделенной с
|
|||||||
assert.deepEqual(getProjectAddTarget(form4Row('ITEM', { project_id: 21 })), {});
|
assert.deepEqual(getProjectAddTarget(form4Row('ITEM', { project_id: 21 })), {});
|
||||||
assert.deepEqual(getProjectAddTarget(form4Row('INPUT', { program_id: 11, project_id: 21 })), { program_id: 11 });
|
assert.deepEqual(getProjectAddTarget(form4Row('INPUT', { program_id: 11, project_id: 21 })), { program_id: 11 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('AURORA-1980: getRenameTarget — программа, проект, остальное', () => {
|
||||||
|
assert.deepEqual(getRenameTarget(form4Row('GROUP', { program_id: 11, name: 'А' })), { kind: 'program', id: 11, name: 'А' });
|
||||||
|
assert.deepEqual(getRenameTarget(form4Row('ITEM', { project_id: 21, name: 'Б' })), { kind: 'project', id: 21, name: 'Б' });
|
||||||
|
assert.equal(getRenameTarget(form4Row('ROOT', { section_code: 'CAP' })), null);
|
||||||
|
assert.equal(getRenameTarget(form4Row('INPUT', { project_id: 21 })), null);
|
||||||
|
assert.equal(getRenameTarget(undefined), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AURORA-1980: isRenameResponse — только свой ответ на свой узел', () => {
|
||||||
|
const target = { kind: 'project', id: 21, name: 'Б' };
|
||||||
|
const msg = { is_self: true, event: 'project_updated', data: { project_id: 21 } };
|
||||||
|
assert.equal(isRenameResponse(msg, target), true);
|
||||||
|
assert.equal(isRenameResponse({ ...msg, error: 'нет прав' }, target), true);
|
||||||
|
assert.equal(isRenameResponse({ ...msg, is_self: false }, target), false);
|
||||||
|
assert.equal(isRenameResponse({ ...msg, data: { project_id: 22 } }, target), false);
|
||||||
|
assert.equal(isRenameResponse({ ...msg, event: 'program_updated', data: { program_id: 21 } }, target), false);
|
||||||
|
assert.equal(isRenameResponse(msg, null), false);
|
||||||
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user