Andrey Epifancev d7ab4d170c feat(table): плашка контроля ошибок со счётчиком, переходом к ячейке и подсказкой (AURORA-1893)
- validationUtils.collectInvalidErrors теперь собирает ошибки по ЯЧЕЙКАМ
  (id/ruleId/columnId/lineId/rowLabel/message), а не одну запись на колонку;
  новая groupValidationErrors(errors) группирует их обратно по правилу+колонке
  со счётчиком для меню плашки.
- useValidationRules: getCellErrors(columnId, value, row) отдаёт причины
  конкретной ячейки (для подсказки), isCellInvalid выражен через него;
  сравнение стабильности errors — по id (он уже кодирует правило+колонку+строку).
- RealtimeTable: handleNavigateToError({lineId, columnId}) — находит строку по
  getRowId в table.getRowModel().flatRows (не .rows, т.к. flatRows не фильтруется
  по свёрнутым группам), раскрывает родителей через setExpanded, включает
  скрытую колонку через handleSetColumnsVisibility, выделяет ячейку как
  activeSearchMatch и скроллит к ней — по образцу navigateToSearchMatch.
- ValidationErrorsAlert: красная плашка (#FDEDED/#D32F2F) со счётчиком
  «Ошибок: N», кнопками ‹›  для прохода по всем ошибкам и меню, сгруппированным
  по правилу+колонке (до 20 строк, дальше «и ещё N»); перенесена в начало
  SettingPanel, перед группами настроек.
- tableColumns/Cell: invalidMessage (из getCellErrors) доходит до Cell и
  показывается подсказкой (MUI Tooltip) при наведении на невалидную ячейку.

Тесты (node --test .../validationUtils.test.js): 12/12, включая 5 существующих
тестов nonNegativeRule из AURORA-1892 (не менялись, подтверждают отсутствие
регрессии) и 7 новых для collectInvalidErrors/groupValidationErrors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-11-01 15:11:26 +04:00

100 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { getDataRowId } from './rowUtils.js';
// Достаёт вложенное поле из объекта строки таблицы
export const getValueByPath = (row, path) => path.split('.').reduce((value, key) => value?.[key], row);
const matchesValidationScope = (scope, { formType, sheetName }) =>
(!scope.formType || scope.formType === formType) &&
(!scope.sheetName || scope.sheetName === sheetName);
// Пустой includedScopes означает «работает везде»; исключения приоритетнее.
export const isValidationRuleEnabled = (rule, context = {}) => {
const includedScopes = rule.includedScopes || [];
const excludedScopes = rule.excludedScopes || [];
if (excludedScopes.some((scope) => matchesValidationScope(scope, context))) return false;
if (includedScopes.length && !includedScopes.some((scope) => matchesValidationScope(scope, context))) return false;
return true;
};
// Строит пути заголовков колонок
export const buildColumnHeaderPaths = (columns = [], path = [], map = {}) => {
for (const column of columns) {
const nextPath = column.header ? [...path, column.header] : path;
if (column.columns?.length) {
buildColumnHeaderPaths(column.columns, nextPath, map);
} else if (column.accessorKey) {
map[column.accessorKey] = nextPath;
}
}
return map;
};
// AURORA-1893: человекочитаемая подпись строки для меню плашки ошибок —
// название статьи/ВСП, если есть, иначе номер строки (колонка «№»).
const getRowLabel = (row) => {
const data = row?.data;
if (data?.header?.name) return data.header.name;
if (data?.name) return data.name;
if (row?.sort_order !== undefined && row?.sort_order !== null) return String(row.sort_order);
return String(getDataRowId(data) ?? '');
};
// Обходит строки и собирает ошибки по правилу валидации — по записи на каждую
// ошибочную ЯЧЕЙКУ (не одна запись на колонку, как было раньше), чтобы плашка
// могла показать счётчик и перейти к конкретной строке.
export const collectInvalidErrors = (rows, rule, errors, seen) => {
for (const row of rows) {
if (!rule.appliesToRow || rule.appliesToRow(row)) {
for (const columnKey of rule.errorColumnKeys || rule.columnKeys) {
if (!rule.isInvalid(getValueByPath(row, columnKey), row, columnKey)) continue;
const lineId = getDataRowId(row.data);
const errorId = `${rule.id}:${columnKey}:${lineId}`;
if (seen.has(errorId)) continue;
seen.add(errorId);
errors.push({
id: errorId,
ruleId: rule.id,
columnId: columnKey,
lineId,
rowLabel: getRowLabel(row),
message: rule.message,
});
}
}
collectInvalidErrors(row.subRows || [], rule, errors, seen);
}
};
// AURORA-1893: группирует ошибки по «правило + колонка» (как раньше отображалось
// в плашке) и прикладывает к каждой группе список конкретных ячеек со счётчиком.
export const groupValidationErrors = (errors) => {
const groupsByKey = new Map();
for (const error of errors) {
const key = `${error.ruleId}:${error.columnId}`;
let group = groupsByKey.get(key);
if (!group) {
group = {
id: key,
ruleId: error.ruleId,
columnId: error.columnId,
message: error.message,
cells: [],
count: 0,
};
groupsByKey.set(key, group);
}
group.cells.push(error);
group.count += 1;
}
return [...groupsByKey.values()];
};