fix(arm-web): нераспознанный текст суммы, пустые подстроки без расшифровок, свёрнутый список предупреждений (#43)
T11.5 valueCellText, T11.10 emptySublineKeys пропускает detail, T11.11 collapseIssues (3 записи + «Показать все»). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
This commit is contained in:
parent
a3199d636e
commit
88a4920d7d
@ -1,8 +1,11 @@
|
||||
import { ExclamationCircleOutlined, InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { Alert, Button, Tag } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import type { Severity } from '../../types/api';
|
||||
import { severityLabel } from '../../utils/labels';
|
||||
import type { IssueListItem } from './issuePlaces';
|
||||
import { type IssueListItem, collapseIssues } from './issuePlaces';
|
||||
|
||||
const COLLAPSED_LIMIT = 3; // сколько записей видно в свёрнутом списке (T11.11)
|
||||
|
||||
/** Значок уровня (P6-15): error — красный, info — синий. */
|
||||
export function SeverityIcon({ severity, style }: { severity: Severity; style?: React.CSSProperties }) {
|
||||
@ -21,7 +24,9 @@ type Props = { items: IssueListItem[]; mode: 'statement' | 'document'; onShow?:
|
||||
*/
|
||||
export function IssuesPanel({ items, mode, onShow }: Props) {
|
||||
const plain = mode === 'document';
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
if (items.length === 0) return null;
|
||||
const { shown, hidden } = collapseIssues(items, expanded, COLLAPSED_LIMIT);
|
||||
return (
|
||||
<Alert
|
||||
type={items.some((i) => i.severity === 'error') ? 'error' : 'info'}
|
||||
@ -29,7 +34,7 @@ export function IssuesPanel({ items, mode, onShow }: Props) {
|
||||
title={`Предупреждения (${items.length})`}
|
||||
description={
|
||||
<div style={{ maxHeight: 120, overflowY: 'auto' }}>
|
||||
{items.map((item) => (
|
||||
{shown.map((item) => (
|
||||
<div key={item.id}>
|
||||
<Tag color={item.severity === 'error' ? 'error' : 'processing'}>{severityLabel(item.severity)}</Tag>
|
||||
{item.message}
|
||||
@ -41,6 +46,11 @@ export function IssuesPanel({ items, mode, onShow }: Props) {
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{(hidden > 0 || expanded) && (
|
||||
<Button type='link' size='small' style={{ paddingInline: 0 }} onClick={() => setExpanded(!expanded)}>
|
||||
{expanded ? 'Свернуть' : `Показать все (${items.length})`}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { ValueOut } from '../../types/api';
|
||||
import { COLORS, cellTipLines, codeCellView, rowBackground, valueBackground } from './cellView';
|
||||
import { formatAmount } from '../../utils/money';
|
||||
import { COLORS, cellTipLines, codeCellView, rowBackground, valueBackground, valueCellText } from './cellView';
|
||||
import type { GridRow } from './statementGrid';
|
||||
|
||||
const val = (extra: Partial<ValueOut> = {}): ValueOut => ({
|
||||
@ -138,3 +139,24 @@ describe('фон', () => {
|
||||
expect(rowBackground(row())).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('valueCellText (T11.5)', () => {
|
||||
it('сумма есть — обычный текст', () => {
|
||||
expect(valueCellText(val({ amount: '1500.00' }))).toEqual({
|
||||
text: formatAmount('1500.00', { fraction: 'auto', empty: '-' }),
|
||||
muted: false,
|
||||
});
|
||||
});
|
||||
it('суммы нет, есть string_value — он серым', () => {
|
||||
expect(valueCellText(val({ amount: null, string_value: '211(1 |' }))).toEqual({ text: '211(1 |', muted: true });
|
||||
});
|
||||
it('ничего нет — прочерк', () => {
|
||||
expect(valueCellText(val({ amount: null }))).toEqual({ text: '-', muted: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe('cellTipLines и string_value (T11.5)', () => {
|
||||
it('без суммы показывает нераспознанный string_value', () => {
|
||||
expect(cellTipLines(val({ amount: null, string_value: 'abc' }))).toContain('Не распознано как сумма: «abc»');
|
||||
});
|
||||
});
|
||||
|
||||
@ -34,11 +34,22 @@ export function cellTipLines(value: ValueOut, issues: IssueOut[] = []): string[]
|
||||
if (value.text !== null && value.text.trim() !== '') lines.push(`Распознано: «${value.text}»`);
|
||||
const confidence = formatPercent(value.confidence);
|
||||
if (confidence !== null) lines.push(`Уверенность: ${confidence}`);
|
||||
if (value.amount === null && value.string_value !== null && value.string_value.trim() !== '') {
|
||||
lines.push(`Не распознано как сумма: «${value.string_value}»`);
|
||||
}
|
||||
if (value.is_confirmed) lines.push('Подтверждено аналитиком');
|
||||
for (const issue of issues) lines.push(issue.message);
|
||||
return lines;
|
||||
}
|
||||
|
||||
/** Текст ячейки суммы (T11.5): сумма; без неё нераспознанный string_value серым; иначе прочерк. */
|
||||
export function valueCellText(value: ValueOut): { text: string; muted: boolean } {
|
||||
if (value.amount === null && value.string_value !== null && value.string_value.trim() !== '') {
|
||||
return { text: value.string_value, muted: true };
|
||||
}
|
||||
return { text: formatAmount(value.amount, { fraction: 'auto', empty: '-' }), muted: false };
|
||||
}
|
||||
|
||||
/** Ячейка кода (P6-16): текст, серый ли он, подсказка. */
|
||||
export function codeCellView(row: GridRow): { text: string; muted: boolean; tip: string | undefined } {
|
||||
switch (row.lineStatus) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { IssueOut, StatementOut } from '../../types/api';
|
||||
import statementV2Json from './__fixtures__/statement-v2.json';
|
||||
import { issueListItems, issuePlaces, worstSeverity } from './issuePlaces';
|
||||
import { collapseIssues, issueListItems, issuePlaces, worstSeverity } from './issuePlaces';
|
||||
import { buildStatementGrid, cellKey } from './statementGrid';
|
||||
|
||||
const statement = statementV2Json as unknown as StatementOut;
|
||||
@ -82,3 +82,14 @@ describe('worstSeverity', () => {
|
||||
expect(worstSeverity([])).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('collapseIssues (T11.11)', () => {
|
||||
const items = Array.from({ length: 6 }, (_, i) => ({ id: String(i) })) as never[];
|
||||
it('свёрнуто: первые 3, скрыто 3', () => {
|
||||
expect(collapseIssues(items, false, 3)).toEqual({ shown: items.slice(0, 3), hidden: 3 });
|
||||
});
|
||||
it('развёрнуто или короткий список: всё', () => {
|
||||
expect(collapseIssues(items, true, 3)).toEqual({ shown: items, hidden: 0 });
|
||||
expect(collapseIssues(items.slice(0, 3), false, 3)).toEqual({ shown: items.slice(0, 3), hidden: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
@ -118,3 +118,9 @@ export function worstSeverity(issues: IssueOut[]): Severity | null {
|
||||
if (issues.length === 0) return null;
|
||||
return issues.some((i) => i.severity === 'error') ? 'error' : 'info';
|
||||
}
|
||||
|
||||
/** Свёрнутый список предупреждений (T11.11): первые `limit` (порядок уже «ошибки первыми») и сколько скрыто. */
|
||||
export function collapseIssues<T>(items: T[], expanded: boolean, limit: number): { shown: T[]; hidden: number } {
|
||||
if (expanded || items.length <= limit) return { shown: items, hidden: 0 };
|
||||
return { shown: items.slice(0, limit), hidden: items.length - limit };
|
||||
}
|
||||
|
||||
@ -173,6 +173,10 @@ describe('parent_code и пустые строки детализации', () =
|
||||
];
|
||||
expect([...emptySublineKeys(rows, {})]).toEqual(['11101', '11102']);
|
||||
});
|
||||
it('расшифровка (lineStatus detail) с parent_code не считается пустой подстрокой (T11.10)', () => {
|
||||
const detail = { ...gridRow('d1', '1110', { c1: cell({}) }), lineStatus: 'detail' as const };
|
||||
expect([...emptySublineKeys([detail], {})]).toEqual([]);
|
||||
});
|
||||
it('подстрока с правкой в черновике не считается пустой', () => {
|
||||
const rows = [gridRow('11101', '1110', { c1: cell({}) }), gridRow('11102', '1110', { c1: cell({}) })];
|
||||
expect([...emptySublineKeys(rows, { '11101:c1': '7' })]).toEqual(['11102']);
|
||||
|
||||
@ -88,6 +88,7 @@ export function emptySublineKeys(
|
||||
.filter(
|
||||
(row) =>
|
||||
row.parentCode !== null &&
|
||||
row.lineStatus !== 'detail' &&
|
||||
!edited.has(row.key) &&
|
||||
!(row.lineId !== null && row.lineId in lineActions) &&
|
||||
!Object.values(row.cells).some(hasValue),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user