fix(table): минус красный и в нередактируемых ячейках строки ввода (AURORA-1892)

Возврат тестирования 06.11, критерий «любое отрицательное число выделено
цветом»: NOT_EDITABLE_INPUT_STYLES (AURORA-1890) разворачивался после цвета
текста и перекрашивал минус в серый #5F6B7A — в расчётных колонках вне
перечня отрицательные значения оставались серыми. Цвет текста теперь
решает resolveCellTextColor (utils/cellTextColor.js, с тестами): минус —
красный всегда, серый — только для неотрицательных значений.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-11-08 17:09:29 +04:00
parent 5690b756e9
commit 20f1bed336
3 changed files with 77 additions and 31 deletions

View File

@ -1,6 +1,7 @@
import { Tooltip } from '@mui/material';
import React, { useMemo, useCallback, useState } from 'react';
import { useCellLock } from '../../contexts/RealtimeContext';
import { resolveCellTextColor } from '../../utils/cellTextColor';
import { formatNumber } from '../../utils/numberFormat';
import { TEXT_COLUMN_IDS } from '../../utils/moneyUtils';
@ -33,11 +34,6 @@ const INVALID_STYLES = {
color: '#C60C0C',
};
// AURORA-1892: отрицательные суммы должны быть явно отличимы от нуля и обычных чисел,
// в том числе на тёмных фонах палитры (например ROOT красной палитры #933634).
const NEGATIVE_COLOR_ON_LIGHT = '#C62828';
const NEGATIVE_COLOR_ON_DARK = '#FFCDD2';
// AURORA-1890: редактируемая ячейка строки INPUT — белый фон и заметная
// синяя рамка (вместо прежней почти незаметной полупрозрачной 2px #d8e4ea94).
// Толщина рамки растёт при наведении (1px → 2px, см. isHovered в CellComponent), курсор — text.
@ -50,10 +46,11 @@ const EDITABLE_BORDER_COLOR = '#7FA7C9';
// AURORA-1890: нередактируемая ячейка строки INPUT (вычисляемая или вне
// текущего этапа) — приглушённый фон и текст, чтобы визуально отличаться от
// редактируемой белой ячейки без отдельной палитры цветов.
// Цвет текста (в т.ч. приглушённый серый) задаёт resolveCellTextColor: здесь его нет,
// чтобы стиль не перекрывал красный цвет отрицательных значений (AURORA-1892).
const NOT_EDITABLE_INPUT_STYLES = {
cursor: 'default',
backgroundColor: '#F4F5F7',
color: '#5F6B7A',
};
// Строки ITEM/GROUP/ROOT: вне скоупа AURORA-1890 — сохраняем прежнее поведение.
@ -104,26 +101,6 @@ const OVER_LIMIT_CHIP_STYLES = {
const NAME_COLUMN_IDS = new Set(['data.header.name', 'data.name']);
const getColorBrightness = (hexColor) => {
if (!hexColor) return 255;
const color = hexColor.replace('#', '');
let r;
let g;
let b;
if (color.length === 3) {
r = Number.parseInt(color[0] + color[0], 16);
g = Number.parseInt(color[1] + color[1], 16);
b = Number.parseInt(color[2] + color[2], 16);
} else if (color.length === 6) {
r = Number.parseInt(color.substring(0, 2), 16);
g = Number.parseInt(color.substring(2, 4), 16);
b = Number.parseInt(color.substring(4, 6), 16);
} else {
return 255;
}
return 0.299 * r + 0.587 * g + 0.114 * b;
};
// Оптимизированная функция подсветки
const createHighlightedContent = (originalValue, displayValue, searchQueries) => {
const cleanQueries = searchQueries.filter(Boolean);
@ -213,11 +190,11 @@ const CellComponent = ({
// AURORA-1890: занятая коллегой ячейка больше не красится в серый —
// обычный brightness-based цвет текста, отличие только в рамке/бейдже.
const textColor = useMemo(() => {
const brightness = getColorBrightness(backgroundColor);
if (isNegative) return brightness < 128 ? NEGATIVE_COLOR_ON_DARK : NEGATIVE_COLOR_ON_LIGHT;
return brightness < 128 ? '#ffffff' : '#000000';
}, [backgroundColor, isNegative]);
const isMutedInput = isInputRow && !isEditable;
const textColor = useMemo(
() => resolveCellTextColor({ backgroundColor, isNegative, isMutedInput }),
[backgroundColor, isNegative, isMutedInput],
);
// AURORA-1890: нередактируемый стиль зависит от типа строки (INPUT получает
// приглушённую заливку, ITEM/GROUP/ROOT — прежнее поведение без изменений).

View File

@ -0,0 +1,40 @@
// Цвет текста ячейки таблицы (вынесено из Cell.jsx, чтобы покрыть тестами без DOM).
// AURORA-1892: отрицательные суммы должны быть явно отличимы от нуля и обычных чисел,
// в том числе на тёмных фонах палитры (например ROOT красной палитры #933634).
export const NEGATIVE_COLOR_ON_LIGHT = '#C62828';
export const NEGATIVE_COLOR_ON_DARK = '#FFCDD2';
// AURORA-1890: приглушённый текст нередактируемой ячейки строки ввода (фон #F4F5F7).
export const MUTED_INPUT_TEXT_COLOR = '#5F6B7A';
export const getColorBrightness = (hexColor) => {
if (!hexColor) return 255;
const color = hexColor.replace('#', '');
let r;
let g;
let b;
if (color.length === 3) {
r = Number.parseInt(color[0] + color[0], 16);
g = Number.parseInt(color[1] + color[1], 16);
b = Number.parseInt(color[2] + color[2], 16);
} else if (color.length === 6) {
r = Number.parseInt(color.substring(0, 2), 16);
g = Number.parseInt(color.substring(2, 4), 16);
b = Number.parseInt(color.substring(4, 6), 16);
} else {
return 255;
}
return 0.299 * r + 0.587 * g + 0.114 * b;
};
// AURORA-1892 (возврат 06.11): минус красный в любой ячейке. Раньше серый цвет
// нередактируемой ячейки строки ввода (AURORA-1890) перекрывал красный, и в расчётных
// колонках отрицательные значения оставались серыми. Фон такой ячейки всегда светлый
// (#F4F5F7), поэтому для неё — красный для светлого фона.
export const resolveCellTextColor = ({ backgroundColor, isNegative, isMutedInput }) => {
if (isMutedInput) return isNegative ? NEGATIVE_COLOR_ON_LIGHT : MUTED_INPUT_TEXT_COLOR;
const isDark = getColorBrightness(backgroundColor) < 128;
if (isNegative) return isDark ? NEGATIVE_COLOR_ON_DARK : NEGATIVE_COLOR_ON_LIGHT;
return isDark ? '#ffffff' : '#000000';
};

View File

@ -0,0 +1,29 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
MUTED_INPUT_TEXT_COLOR,
NEGATIVE_COLOR_ON_DARK,
NEGATIVE_COLOR_ON_LIGHT,
resolveCellTextColor,
} from './cellTextColor.js';
// AURORA-1892 (возврат 06.11): в расчётных (нередактируемых) ячейках строки ввода
// минус оставался серым — серый цвет AURORA-1890 перекрывал красный.
test('a negative value in a non-editable input cell is red, not muted grey', () => {
assert.equal(resolveCellTextColor({ backgroundColor: '#FFFFFF', isNegative: true, isMutedInput: true }), NEGATIVE_COLOR_ON_LIGHT);
});
test('a non-negative value in a non-editable input cell keeps the muted grey', () => {
assert.equal(resolveCellTextColor({ backgroundColor: '#FFFFFF', isNegative: false, isMutedInput: true }), MUTED_INPUT_TEXT_COLOR);
});
test('negative values follow background brightness outside muted input cells', () => {
assert.equal(resolveCellTextColor({ backgroundColor: '#FFFFFF', isNegative: true, isMutedInput: false }), NEGATIVE_COLOR_ON_LIGHT);
assert.equal(resolveCellTextColor({ backgroundColor: '#933634', isNegative: true, isMutedInput: false }), NEGATIVE_COLOR_ON_DARK);
});
test('regular values are black on light and white on dark backgrounds', () => {
assert.equal(resolveCellTextColor({ backgroundColor: '#FFFFFF', isNegative: false, isMutedInput: false }), '#000000');
assert.equal(resolveCellTextColor({ backgroundColor: '#933634', isNegative: false, isMutedInput: false }), '#ffffff');
assert.equal(resolveCellTextColor({ backgroundColor: undefined, isNegative: false, isMutedInput: false }), '#000000');
});