From 761a435b989f7a4de46a47ddd5130abf8bbf4272 Mon Sep 17 00:00:00 2001 From: PotapovaA Date: Thu, 25 Jun 2026 18:20:18 +0300 Subject: [PATCH] AURORA-1134 --- web/src/api/stages.js | 7 +- web/src/components/Stages/AddStagesModal.jsx | 14 +-- .../components/common/TaskCardComponent.jsx | 4 +- web/src/constants.jsx | 2 +- .../AuditLogsPage/AuditLogsPage.jsx | 2 +- .../components/AuditLogsFilters.jsx | 26 ----- .../components/JsonViewModal.jsx | 39 +++++-- .../AuditLogsPage/constants/auditLogs.jsx | 15 +-- .../AuditLogsPage/constants/configEvent.jsx | 38 +++++-- .../AuditLogsPage/utils/transformers.jsx | 100 +++++++++++++++++- .../AdminPanel/UsersPage/ModalEditUserSsp.jsx | 15 ++- .../UsersPage/UserTable/EditUserModal.jsx | 4 +- .../AutocompleteForChangeOptions.jsx | 2 +- web/src/pages/TaskPage/TaskPage.jsx | 4 +- web/src/pages/TasksPage/TasksPage.jsx | 6 +- 15 files changed, 185 insertions(+), 93 deletions(-) diff --git a/web/src/api/stages.js b/web/src/api/stages.js index 8fb0ecd..d680d97 100644 --- a/web/src/api/stages.js +++ b/web/src/api/stages.js @@ -5,13 +5,10 @@ export const StagesApi = { return api.post(`/stages/form/${formId}`, payload).then((r) => r.data); }, updateStage(payload) { - const { budget_form_id, sheet, phase_code, opens_at, closes_at } = payload; + const { budget_form_id, sheet, phase_code } = payload; return api - .patch(`/stages/form/${budget_form_id}/${sheet}/${phase_code}`, { - opens_at, - closes_at, - }) + .patch(`/stages/form/${budget_form_id}/${sheet}/${phase_code}`, payload) .then((r) => r.data); }, deleteStage(formId, sheet, phaseCode) { diff --git a/web/src/components/Stages/AddStagesModal.jsx b/web/src/components/Stages/AddStagesModal.jsx index f4ac2ca..4b61bee 100644 --- a/web/src/components/Stages/AddStagesModal.jsx +++ b/web/src/components/Stages/AddStagesModal.jsx @@ -55,17 +55,9 @@ export const AddStagesModal = ({ try { const { config } = await import(`../RealtimeTable/constants/${formType}/${sheetObj.sheet}.js`); setColumnsConfig(config.config); - - // Если есть сохраненные выбранные колонки для этого листа - if (stage?.column_keys) { - setSelectedColumns(stage.column_keys); - } else { - setSelectedColumns([]); - } } catch (error) { console.error(`Failed to load config for ${formType}/${selectedSheet}:`, error); setColumnsConfig({ columns: [], colors: {} }); - setSelectedColumns([]); } }; @@ -79,10 +71,7 @@ export const AddStagesModal = ({ setStageName(stage.phase_code ?? ''); setSelectedSheet(stage.sheet ?? null); setSelectedRole(stage.role ?? null); - - if (stage.column_keys) { - setSelectedColumns(stage.column_keys.map(c => 'data.' + c)); - } + setSelectedColumns(stage.column_keys.map(c => 'data.' + c)); } else { setStartDate(null); setEndDate(null); @@ -201,6 +190,7 @@ export const AddStagesModal = ({ placeholder="Введите название этапа" fullWidth size="small" + disabled={stage !== null} /> diff --git a/web/src/components/common/TaskCardComponent.jsx b/web/src/components/common/TaskCardComponent.jsx index 473a34f..ad629ed 100644 --- a/web/src/components/common/TaskCardComponent.jsx +++ b/web/src/components/common/TaskCardComponent.jsx @@ -26,7 +26,7 @@ import { formatDate } from '../../utils/formatDate'; import { IconWithContent } from './IconWithContent'; import { Chip } from '../styles/StyledChip'; import { ExportButton } from '../common/Buttons/ButtonsActions'; -import { FROM_TYPE_TRANSLATE } from '../../constants'; +import { FORM_TYPE_TRANSLATE } from '../../constants'; const TaskCard = styled.div` width: 26.8rem; @@ -100,7 +100,7 @@ export const TaskCardComponent = ({ const [isEditModalOpen, setEditModalOpen] = useState(false); const [editedTitle, setEditedTitle] = useState(''); - const [currentTitle, setCurrentTitle] = useState(FROM_TYPE_TRANSLATE[form_type_code] ||''); + const [currentTitle, setCurrentTitle] = useState(FORM_TYPE_TRANSLATE[form_type_code] ||''); const [isChecked, setIsChecked] = useState(false); useEffect(() => { diff --git a/web/src/constants.jsx b/web/src/constants.jsx index c009ff3..65e8351 100644 --- a/web/src/constants.jsx +++ b/web/src/constants.jsx @@ -36,7 +36,7 @@ export const EDIT_ACCESS_RUSSIAN = { }; export const FORM_TYPE_OPTIONS = ['FORM_1', 'FORM_2', 'FORM_3', 'FORM_4']; -export const FROM_TYPE_TRANSLATE = { 'FORM_1': 'Смета ССП', 'FORM_2': 'Смета РФ', 'FORM_3': 'Программа развитие ПРРС', 'FORM_4': 'Проектная деятельность' }; +export const FORM_TYPE_TRANSLATE = { 'FORM_1': 'Смета ССП', 'FORM_2': 'Смета РФ', 'FORM_3': 'Программа развитие ПРРС', 'FORM_4': 'Проектная деятельность' }; export const DIRECTION_TRANSLATE = { 'Support': 'Поддержка', 'Development': 'Развитие' }; export const ORG_UNIT_TYPE_OPTIONS = [ diff --git a/web/src/pages/AdminPanel/AuditLogsPage/AuditLogsPage.jsx b/web/src/pages/AdminPanel/AuditLogsPage/AuditLogsPage.jsx index ffffcd6..9df17cb 100644 --- a/web/src/pages/AdminPanel/AuditLogsPage/AuditLogsPage.jsx +++ b/web/src/pages/AdminPanel/AuditLogsPage/AuditLogsPage.jsx @@ -70,7 +70,7 @@ const AuditLogsPage = () => { limit: rowsPerPage, user_id: selectedUser?.id, org_unit_id: selectedSsp?.id, - event_type: selectedEventType?.id, + event: selectedEventType?.id, date_from: dateFrom ? dateFrom.toISOString() : undefined, date_to: dateTo ? dateTo.toISOString() : undefined, task_id: taskId ? taskId : undefined, diff --git a/web/src/pages/AdminPanel/AuditLogsPage/components/AuditLogsFilters.jsx b/web/src/pages/AdminPanel/AuditLogsPage/components/AuditLogsFilters.jsx index 5fd8407..e48d33b 100644 --- a/web/src/pages/AdminPanel/AuditLogsPage/components/AuditLogsFilters.jsx +++ b/web/src/pages/AdminPanel/AuditLogsPage/components/AuditLogsFilters.jsx @@ -177,32 +177,6 @@ const AuditLogsFilters = ({ spacing={'1rem'} sx={{ width: '100%', alignItems: 'end' }} > - - Поиск по контексту (ID задачи) - - - - Поиск по контексту (ID формы) - - { @@ -36,6 +36,7 @@ const JsonViewModal = ({ open, onClose, jsonData, eventType }) => { async function extractInfo(data, type) { data = JSON.parse(data); const config = EVENT_CONFIGS[type] || {}; + const configForMultDependents = EVENT_CONFIGS_MULT_DEPENDENTS[type] || {}; const result = {}; // Функция для рекурсивного обхода всех узлов @@ -46,16 +47,33 @@ const JsonViewModal = ({ open, onClose, jsonData, eventType }) => { const currentPath = path ? `${path}.${key}` : key; if (config[currentPath] || config[key]) { - const configKey = config[currentPath] || config[key]; let value = obj[key]; + const configKey = config[currentPath] || config[key]; // Применяем преобразование, если есть функция для этого ключа if (VALUE_TRANSFORMERS[configKey] && value) { - const transformer = VALUE_TRANSFORMERS[configKey]; - if (transformer.constructor.name === 'AsyncFunction') { - value = await transformer(value); - } else { - value = transformer(value); + try { + // Собираем зависимости для трансформера + // По умолчанию передаём только текущее значение + const dependents = [value]; + + // Если для этого ключа есть дополнительные зависимости (другие поля) + if (configForMultDependents[configKey] || configForMultDependents[configKey]) { + configForMultDependents[configKey].forEach(d => { + // Добавляем значения из зависимых полей (в корне data) + dependents.push(data[d]); + }); + } + + const transformer = VALUE_TRANSFORMERS[configKey]; + if (transformer.constructor.name === 'AsyncFunction') { + value = await transformer(...dependents); + } else { + value = transformer(...dependents); + } + } catch (error) { + console.error(`Ошибка в трансформере для ключа "${configKey}":`, error); + value = '—'; } } @@ -69,8 +87,11 @@ const JsonViewModal = ({ open, onClose, jsonData, eventType }) => { } } - // Начинаем обход с основного объекта - await traverseAndExtract(data); + try { + await traverseAndExtract(data); + } catch (error) { + return JSON.stringify({ error: 'Ошибка при извлечении данных' }, null, 2); + } return JSON.stringify(result, null, 2); } diff --git a/web/src/pages/AdminPanel/AuditLogsPage/constants/auditLogs.jsx b/web/src/pages/AdminPanel/AuditLogsPage/constants/auditLogs.jsx index a00c098..344a520 100644 --- a/web/src/pages/AdminPanel/AuditLogsPage/constants/auditLogs.jsx +++ b/web/src/pages/AdminPanel/AuditLogsPage/constants/auditLogs.jsx @@ -1,16 +1,17 @@ export const DEFAULT_ACTIONS = { ROW_CREATE: 'Создание строки', ROW_DELETE: 'Удаление строки', - ACCESS_EXTEND: 'Расширение доступа', - ACCESS_WINDOW_CHANGE: 'Изменение окна доступа', + ACCESS_EXTEND: 'Продление этапа', + ACCESS_WINDOW_CHANGE: 'Изменение этапа', USER_ROLE_CHANGE: 'Изменение роли пользователя', CREATE_TASK: 'Создание задачи', - UPDATE_TASK: 'Обновление задачи', - DELETE_TASK: 'Удаление задачи', + // UPDATE_TASK: 'Обновление задачи', //сейчас нет события + // DELETE_TASK: 'Удаление задачи', USER_CREATE: 'Создание пользователя', USER_ACCESS_GRANTED: 'Предоставление доступа к ССП/РФ', USER_ACCESS_REVOKED: 'Отмена доступа к ССП/РФ', VSP_CREATE: 'Создание ВСП', + VSP_UPDATE: 'Обновление ВСП', }; export const ENTITY_RUSSIAN_NAMES = { @@ -23,8 +24,8 @@ export const ENTITY_RUSSIAN_NAMES = { TASK: 'задачи', USER_ACCESS: 'доступа к ССП/РФ', USER: 'пользователя', - ACCESS_WINDOW: 'окна доступа', - ACCESS: 'окна доступа', + ACCESS_WINDOW: 'этапа', + ACCESS: 'этапа', USER_ROLE: 'роли пользователя', VSP: 'ВСП', }; @@ -37,7 +38,7 @@ export const ACTION_RUSSIAN_NAMES = { GET: 'Получение', GRANTED: 'Предоставление', REVOKED: 'Отмена', - EXTEND: 'Расширение', + EXTEND: 'Продление', }; export const getRussianNamesForType = (name) => { diff --git a/web/src/pages/AdminPanel/AuditLogsPage/constants/configEvent.jsx b/web/src/pages/AdminPanel/AuditLogsPage/constants/configEvent.jsx index 48b0616..5ded6f4 100644 --- a/web/src/pages/AdminPanel/AuditLogsPage/constants/configEvent.jsx +++ b/web/src/pages/AdminPanel/AuditLogsPage/constants/configEvent.jsx @@ -6,12 +6,11 @@ export const EVENT_CONFIGS = { entity_id: 'Пользователь', }, VSP_CREATE: { - ssp_id: 'Региональный филиал', reg_number: 'Регистрационный номер ВСП', vsp_type: 'Вид ВСП', address: 'Адрес ВСП', - approved_format: 'Утвержденный формат', - open_date: 'Дата открытия ВСП', + format: 'Утвержденный формат', + opened_at: 'Дата открытия ВСП', notes: 'Примечания', location_form: 'Форма расположения ВСП', numbers: 'Штатная численность ВСП', @@ -22,14 +21,16 @@ export const EVENT_CONFIGS = { branch_name: 'Региональный филиал ', closed_at: 'Дата закрытия ВСП', user_email: 'Почта пользователя', + staff_count: 'Кол-во работников', + placement_type: 'Тип размещение', + is_active: 'Активный', }, VSP_UPDATE: { - ssp_id: 'Региональный филиал', reg_number: 'Регистрационный номер ВСП', vsp_type: 'Вид ВСП', address: 'Адрес ВСП', - approved_format: 'Утвержденный формат', - open_date: 'Дата открытия ВСП', + format: 'Утвержденный формат', + opened_at: 'Дата открытия ВСП', notes: 'Примечания', location_form: 'Форма расположения ВСП', numbers: 'Штатная численность ВСП', @@ -37,9 +38,12 @@ export const EVENT_CONFIGS = { rent_contract_num: 'Номер договора аренды', rent_end_date: 'Срок окончания договора аренды', system_code: 'Код РФ', - branch_name: 'Региональный филиал ', + 'changes.branch_name': 'Региональный филиал ', closed_at: 'Дата закрытия ВСП', user_email: 'Почта пользователя', + staff_count: 'Кол-во работников', + placement_type: 'Тип размещение', + is_active: 'Активный', }, USER_ACCESS_GRANTED: { entity_id: 'Пользователь', @@ -76,10 +80,26 @@ export const EVENT_CONFIGS = { "year": "Год", "org_unit_id": "СПП/РФ", }, - ACCESS_EXTEND:{ + ACCESS_EXTEND: { form_id: 'Информация о задаче', closes_at_after: 'Дата закрытия', closes_at_before: 'Дата открытия', - phase_name: 'Названия этапа' + phase_name: 'Названия этапа', + sheet: "Лист", + }, + ACCESS_WINDOW_CHANGE: { + form_id: 'Информация о задаче', + closes_at: 'Дата закрытия', + open_at: 'Дата открытия', + phase_name: 'Названия этапа', + role: 'Для роли', + column_keys: 'Колонки в этапе', + action: 'Действие', + sheet: "Лист", }, }; +export const EVENT_CONFIGS_MULT_DEPENDENTS = { + ACCESS_WINDOW_CHANGE: { + 'Колонки в этапе': ['form_id', 'sheet'] + }, +} \ No newline at end of file diff --git a/web/src/pages/AdminPanel/AuditLogsPage/utils/transformers.jsx b/web/src/pages/AdminPanel/AuditLogsPage/utils/transformers.jsx index e3d123c..ed49884 100644 --- a/web/src/pages/AdminPanel/AuditLogsPage/utils/transformers.jsx +++ b/web/src/pages/AdminPanel/AuditLogsPage/utils/transformers.jsx @@ -4,7 +4,7 @@ import { FormsApi } from '../../../../api/forms'; import { SspApi } from '../../../../api/ssp'; import { TasksApi } from '../../../../api/tasks'; import { UsersApi } from '../../../../api/users'; -import { DIRECTION_TRANSLATE, FROM_TYPE_TRANSLATE, ROLES_ID_RUSSIAN_NAME, SHEET_NAME } from '../../../../constants'; +import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, ROLES_ID_RUSSIAN_NAME, SHEET_NAME, STAGE_ROLE_RUSSIAN_NAME } from '../../../../constants'; export const VALUE_TRANSFORMERS = { Роль: transformRole, @@ -18,12 +18,28 @@ export const VALUE_TRANSFORMERS = { 'СПП/РФ': transformSsp, 'Статья расходов': transformExpense, 'Информация о задаче': transformForm, - 'Направление': transformDirection + 'Направление': transformDirection, + 'Для роли': transformRoleChanges, + 'Колонки в этапе': transformColumns, + 'Форма': transformFormType, }; export function transformRole(value) { return ROLES_ID_RUSSIAN_NAME[value] || 'Роль не найдена'; } +export function transformFormType(value) { + return FORM_TYPE_TRANSLATE[value] || 'Форма не найдена'; +} + +export function transformRoleChanges(value) { + if (typeof value === 'string') { + return STAGE_ROLE_RUSSIAN_NAME[value] + } + return { + before: STAGE_ROLE_RUSSIAN_NAME[value.before], + after: STAGE_ROLE_RUSSIAN_NAME[value.after], + } +} export async function transformUser(userId) { try { @@ -105,16 +121,90 @@ export async function transformForm(formId) { const res = await FormsApi.getForm(formId); const form = res.result; const formInfo = { - 'название': FROM_TYPE_TRANSLATE[form.form_type_code], + 'название': FORM_TYPE_TRANSLATE[form.form_type_code], 'ССП/РФ': form.org_unit.title, 'Год': form.year, } return formInfo } -export function transformDirection(direction){ - if (!direction){ +export function transformDirection(direction) { + if (!direction) { return '-'; } return DIRECTION_TRANSLATE[direction]; +} + +export async function transformColumns(cols, formId, sheet) { + if (!formId || !sheet) return cols; + const res = await FormsApi.getForm(formId); + const form = res.result; + const formType = form.form_type_code; + + const { config } = await import(`../../../../components/RealtimeTable/constants/${formType}/${sheet}.js`); + const columns = config.config.columns; + function findColumnAndHeaders(columns, targetAccessorKey) { + + // Рекурсивная функция для поиска колонки и сбора родителей + function searchRecursively(columnsArray, path = []) { + for (let column of columnsArray) { + // Проверяем текущую колонку + if (column.accessorKey === targetAccessorKey) { + // Нашли целевую колонку, возвращаем путь с заголовками + return { + found: true, + headers: [...path, column.header], + column: column + }; + } + + // Если у колонки есть вложенные колонки, ищем в них + if (column.columns && Array.isArray(column.columns)) { + const result = searchRecursively(column.columns, [...path, column.header]); + if (result.found) { + return result; + } + } + } + + return { found: false }; + } + + const result = searchRecursively(columns); + + if (result.found) { + return { + headers: result.headers, + column: result.column, + path: result.headers.join(' → ') + }; + } else { + return { + headers: [], + column: null, + path: null, + error: `Колонка с accessorKey "${targetAccessorKey}" не найдена` + }; + } + } + if (Array.isArray(cols)) { + const res = []; + cols.forEach(c => { + res.push(findColumnAndHeaders(columns, "data." + c).path || ''); + }) + return res; + } + + const translateColsAfter = []; + const translateColsBefore = []; + + cols.after.forEach(c => { + translateColsAfter.push(findColumnAndHeaders(columns, "data." + c).path || ''); + }) + cols.before.forEach(c => { + translateColsBefore.push(findColumnAndHeaders(columns, "data." + c).path || ''); + }) + + // Пример использования: + return { after: translateColsAfter, before: translateColsBefore }; } \ No newline at end of file diff --git a/web/src/pages/AdminPanel/UsersPage/ModalEditUserSsp.jsx b/web/src/pages/AdminPanel/UsersPage/ModalEditUserSsp.jsx index 08e2963..1cd01dd 100644 --- a/web/src/pages/AdminPanel/UsersPage/ModalEditUserSsp.jsx +++ b/web/src/pages/AdminPanel/UsersPage/ModalEditUserSsp.jsx @@ -20,6 +20,10 @@ export const ModalEditUserSsp = ({ const [selectedType, setSelectedType] = useState('rf'); // 'rf' или 'ssp' useEffect(() => { + if (!open) { + setCurUser(null); + return; + } setCurUser(user); try { if (user && user.role_id == ROLES_NAME_ID.executor_rf) { @@ -29,7 +33,7 @@ export const ModalEditUserSsp = ({ } catch { setSelectedType('rf'); } - }, [user]); + }, [user, open]); useEffect(() => { if (!deps) return; @@ -75,14 +79,9 @@ export const ModalEditUserSsp = ({ }, [depsData]); const sspForDfip = useMemo(() => { - if (!curUser) return []; - if (newDepsData.update.length > 0) { - return newDepsData.update.map( - (sId) => deps.filter((dep) => dep.id == sId)[0], - ); - } + if (!curUser|| !open) return []; return curUser.org_units ?? []; - }, [curUser, deps, newDepsData]); + }, [curUser, deps, newDepsData, open]); const handleTypeChange = (value) => { setSelectedType(value); diff --git a/web/src/pages/AdminPanel/UsersPage/UserTable/EditUserModal.jsx b/web/src/pages/AdminPanel/UsersPage/UserTable/EditUserModal.jsx index ef06a2b..9c32b5c 100644 --- a/web/src/pages/AdminPanel/UsersPage/UserTable/EditUserModal.jsx +++ b/web/src/pages/AdminPanel/UsersPage/UserTable/EditUserModal.jsx @@ -359,8 +359,8 @@ export const EditUserModal = ({ customSize={58.13} > - - + + {renderField('ФИО', 'full_name', editUser.full_name)} {renderField('Почта', 'email', editUser.email, 'email')} diff --git a/web/src/pages/AdminPanel/components/AutocompleteForChangeOptions.jsx b/web/src/pages/AdminPanel/components/AutocompleteForChangeOptions.jsx index 217eacc..e9684ff 100644 --- a/web/src/pages/AdminPanel/components/AutocompleteForChangeOptions.jsx +++ b/web/src/pages/AdminPanel/components/AutocompleteForChangeOptions.jsx @@ -105,7 +105,7 @@ export const AutocompleteForChangeOptions = ({ if (option.email) { label += ` (${option.email})`; } - return label; + return label ?? '-'; }} onChange={handleSelect} inputValue={inputValue} diff --git a/web/src/pages/TaskPage/TaskPage.jsx b/web/src/pages/TaskPage/TaskPage.jsx index 2993547..ae3fcb7 100644 --- a/web/src/pages/TaskPage/TaskPage.jsx +++ b/web/src/pages/TaskPage/TaskPage.jsx @@ -22,13 +22,13 @@ import { ModalCreateProject } from './ModalCreateProject'; import { ExportWithTextButton } from '../../components/common/Buttons/ButtonsActions'; import { exportSingleForm } from '../../utils/exportForm'; import TablesList from '../../components/common/TableList/TableList'; -import { DIRECTION_TRANSLATE, FROM_TYPE_TRANSLATE, SHEET_NAME } from '../../constants'; +import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, SHEET_NAME } from '../../constants'; const TaskInfo = ({ task }) => { const portalContent = ( - {FROM_TYPE_TRANSLATE[task.form_type_code] || ''} + {FORM_TYPE_TRANSLATE[task.form_type_code] || ''} ); diff --git a/web/src/pages/TasksPage/TasksPage.jsx b/web/src/pages/TasksPage/TasksPage.jsx index 0a06126..00f8933 100644 --- a/web/src/pages/TasksPage/TasksPage.jsx +++ b/web/src/pages/TasksPage/TasksPage.jsx @@ -26,7 +26,7 @@ import { FORM_TYPE_OPTIONS, ORG_UNIT_TYPE_OPTIONS, ROLES_NAME_ID, - FROM_TYPE_TRANSLATE + FORM_TYPE_TRANSLATE } from '../../constants'; import { useAuth } from '../../app/context/AuthProvider'; import { SspApi } from '../../api/ssp'; @@ -414,7 +414,7 @@ export default function TasksPage() { onChange={(e) => handleFormChange('formTypeCode', e.target.value)} renderValue={(selected) => selected ? ( - FROM_TYPE_TRANSLATE[selected] + FORM_TYPE_TRANSLATE[selected] ) : ( Не выбрана ) @@ -424,7 +424,7 @@ export default function TasksPage() { > {FORM_TYPE_OPTIONS.map((formType) => ( - {FROM_TYPE_TRANSLATE[formType]} + {FORM_TYPE_TRANSLATE[formType]} ))}