diff --git a/web/src/utils/consolidations.test.ts b/web/src/utils/consolidations.test.ts
index 9dd568e..240bc2d 100644
--- a/web/src/utils/consolidations.test.ts
+++ b/web/src/utils/consolidations.test.ts
@@ -281,6 +281,15 @@ describe('resolveLeaveAfterFetch (T7.3)', () => {
});
});
+describe('reportTabNavigation: вкладка «Таблица корректировки» (#206, T4.13)', () => {
+ it('шахматка → таблица корректировки: меняется последний сегмент, запись истории заменяется', () => {
+ expect(reportTabNavigation('/groups/g/versions/v/chess', 'chess', 'correction')).toEqual({
+ to: '/groups/g/versions/v/correction',
+ options: { replace: true },
+ });
+ });
+});
+
describe('reportTabNavigation: адрес без search (T7.4)', () => {
it('?holder=&missing= не переносятся на другую вкладку', () => {
expect(reportTabNavigation('/groups/g/versions/v/osv', 'osv', 'chess').to).toBe('/groups/g/versions/v/chess');
diff --git a/web/src/widgets/CorrectionTable/CorrectionContent.tsx b/web/src/widgets/CorrectionTable/CorrectionContent.tsx
index e19c371..29fc5ef 100644
--- a/web/src/widgets/CorrectionTable/CorrectionContent.tsx
+++ b/web/src/widgets/CorrectionTable/CorrectionContent.tsx
@@ -28,6 +28,9 @@ function readOnlyHint(t: CorrectionTableOut): string | null {
return READ_ONLY_HINTS[t.read_only_reason];
}
+/** Решения по строкам и окно корректировки подключаются в Task 5–6: тогда флаг становится true (одна правка). */
+const ROW_ACTIONS_READY = false;
+
const LOAD_ERROR_KEY = 'corrections-load-error';
type Props = { versionId: string; card?: VersionCard | null };
@@ -61,6 +64,9 @@ export function CorrectionContent({ versionId, card = null }: Props) {
});
}, [error, notification, reload]);
+ // ушли с вкладки — уведомление об ошибке закрываем, оно относится к этому экрану
+ useEffect(() => () => notification.destroy(LOAD_ERROR_KEY), [notification]);
+
useEffect(() => {
const onChange = () => {
if (!document.fullscreenElement) setFullscreen(false);
@@ -134,7 +140,7 @@ export function CorrectionContent({ versionId, card = null }: Props) {
rules={table.rules}
editable={table.editable}
readOnlyHint={readOnlyHint(table)}
- actionsEnabled={false}
+ actionsEnabled={ROW_ACTIONS_READY && table.editable}
actions={actions}
filters={filters}
onFiltersChange={setFilters}
diff --git a/web/src/widgets/CorrectionTable/CorrectionTable.tsx b/web/src/widgets/CorrectionTable/CorrectionTable.tsx
index ea09a8b..bc20665 100644
--- a/web/src/widgets/CorrectionTable/CorrectionTable.tsx
+++ b/web/src/widgets/CorrectionTable/CorrectionTable.tsx
@@ -57,6 +57,8 @@ const BG_CHANGED = '#fff6e5';
/** «394.27» → «394,27%»; строка не переводится в число. */
const percentText = (p: string | null): string => (p === null ? '—' : `${p.replace('.', ',')}%`);
+const UNAVAILABLE_HINT = 'Недоступно в этой версии экрана';
+
const showLine = (line: string): string => articleCodes(line).join(' / ');
const sideSuffix = (row: TableRow) => {
@@ -106,15 +108,26 @@ export function CorrectionTable(props: Props) {
else if (key === 'reset') actions.onAllocate({ ...base, action: 'RESET' });
};
- const withHint = (node: ReactNode) => (readOnlyHint ? {node} : node);
+ // почему контрол неактивен: сервер закрыл правки — его причина; иначе действие ещё не подключено в этой версии экрана
+ const inactiveHint = readOnlyHint ?? (actionsEnabled ? null : UNAVAILABLE_HINT);
+ const withHint = (node: ReactNode, hint: string | null = inactiveHint) =>
+ hint ? (
+
+ {node}
+
+ ) : (
+ node
+ );
const lines = (list: CorrectionLineOut[] | undefined, pick: (l: CorrectionLineOut) => string): ReactNode =>
- list && list.length > 0 ? list.map((l) =>
{pick(l)}
) : '—';
+ list && list.length > 0 ? list.map((l, i) => {pick(l)}
) : '—';
const cell = (key: ColKey, row: TableRow): ReactNode => {
if (row.kind === 'aggregate') {
const a = row.aggregate;
- const red = a.exceeds_threshold ? { color: COLOR_OVER } : undefined;
+ // красным — только значение выше порога; прочерк («нет значения») не красим
+ const redIf = (text: string): ReactNode =>
+ a.exceeds_threshold && text !== '—' ? {text} : text;
switch (key) {
case 'include':
return withHint(
@@ -127,6 +140,7 @@ export function CorrectionTable(props: Props) {
)}
,
+ readOnlyHint,
);
case 'd_entity':
return a.company_1.name;
@@ -145,9 +159,9 @@ export function CorrectionTable(props: Props) {
case 'k_amount':
return format(a.side_2.amount);
case 'dev_percent':
- return {percentText(a.deviation_percent)};
+ return redIf(percentText(a.deviation_percent));
case 'dev_amount':
- return {format(a.deviation_amount)};
+ return redIf(format(a.deviation_amount));
case 'c_d_line':
return lines(a.correction?.debit, (l) => l.line_code);
case 'c_d_amount':
@@ -254,7 +268,7 @@ export function CorrectionTable(props: Props) {
width,
ellipsis,
render: (_, row) => cell(key, row),
- onCell: (row) => ({ style: { background: rowBg(row) ?? tint(key), opacity: row.kind === 'entry' && row.entry.excluded ? 0.5 : 1 } }),
+ onCell: (row) => ({ style: { background: rowBg(row) ?? tint(key), opacity: (row.kind === 'entry' || row.kind === 'unallocated') && row.entry.excluded ? 0.5 : 1 } }),
});
const filtersTitle = (label: string, active: boolean, body: ReactNode) => (
@@ -323,6 +337,7 @@ export function CorrectionTable(props: Props) {
type='text'
size='small'
aria-label={expanded.has(row.key) ? 'Свернуть' : 'Раскрыть'}
+ aria-expanded={expanded.has(row.key)}
icon={expanded.has(row.key) ? : }
onClick={() => onToggle(row.key)}
/>
diff --git a/web/src/widgets/CorrectionTable/HeaderFilter.tsx b/web/src/widgets/CorrectionTable/HeaderFilter.tsx
index 0f4d2e6..1631b07 100644
--- a/web/src/widgets/CorrectionTable/HeaderFilter.tsx
+++ b/web/src/widgets/CorrectionTable/HeaderFilter.tsx
@@ -3,7 +3,7 @@ import { Input, Popover, Select } from 'antd';
import type { ReactNode } from 'react';
type Props = {
- label: ReactNode;
+ label: string;
/** Фильтр задан — значок выделяется. */
active: boolean;
children: ReactNode;
@@ -18,7 +18,7 @@ export function HeaderFilter({ label, active, children }: Props) {
{/* стопим всплытие, чтобы клик не запускал выбор заголовка */}