arm-core/web/src/widgets/DocumentReview/StatementTable.tsx
Andrey Epifancev d5118ba3a6 fix(web): перенос правки шаблонных строк во встроенный фронт (#80)
Правка arm-web 9e89b9f перенесена в arm-core/web: id и origin у строк и ячеек
формы, ключ достроенной строки code-<код>, отправка line_code, раскладка
formLayout. Без неё встроенный фронт отправлял бы line_id "null" (I-1, R-12).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-09 12:33:00 +04:00

115 lines
4.4 KiB
TypeScript

import { Input, Table, Tooltip } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import type { ColumnOut, DocumentType, ValueOut } from '../../types/api';
import { formatAmount, parseAmountInput } from '../../utils/money';
import { type LayoutRow, formLayout } from './formLayout';
import { columnTitle } from './reviewView';
import { type GridRow, cellFlags, cellKey } from './statementGrid';
type Props = {
documentType: DocumentType;
columns: ColumnOut[];
rows: GridRow[];
editable: boolean;
draft: Record<string, string>;
onDraftChange: (key: string, text: string, originalAmount: string | null) => void;
};
const ATTENTION_BG = '#fff7e6';
const MODIFIED_BG = '#e6f4ff';
function cellBackground(value: ValueOut | undefined): string | undefined {
const flags = cellFlags(value);
if (flags.modified) {
return MODIFIED_BG;
}
return flags.attention ? ATTENTION_BG : undefined;
}
function cellTip(value: ValueOut): string | undefined {
if (value.is_modified) {
return `Исходное: ${formatAmount(value.original_amount, { fraction: 'auto', empty: '-' })}`;
}
return value.confidence ? `Уверенность: ${value.confidence}%` : undefined;
}
const SECTION_BG = '#f2f4f7';
const GRAND_BG = '#f9f0ff'; // строки «БАЛАНС»: фиолетовый не занят (серый — разделы, синий — исправлено, оранжевый — внимание)
function lineWeight(r: LayoutRow): number | undefined {
if (r.kind !== 'line') return undefined;
if (r.grand) return 700;
return r.total ? 600 : undefined;
}
const lineBackground = (r: LayoutRow) => (r.kind === 'line' && r.grand ? GRAND_BG : undefined);
export function StatementTable({ documentType, columns, rows, editable, draft, onDraftChange }: Props) {
const layout = formLayout(documentType, rows);
const span = columns.length + 2;
// строка-заголовок раздела занимает всю ширину: первая ячейка colSpan = все колонки, остальные скрыты
const sectionSpan = (r: LayoutRow, first: boolean) => (r.kind === 'section' ? { colSpan: first ? span : 0 } : {});
const tableColumns: ColumnsType<LayoutRow> = [
{
key: 'name',
title: 'Наименование показателя',
onCell: (r) => ({
...sectionSpan(r, true),
style:
r.kind === 'section'
? { background: SECTION_BG, textAlign: 'center', fontWeight: r.level === 1 ? 700 : 600 }
: { background: lineBackground(r) },
}),
render: (_: unknown, r: LayoutRow) => {
if (r.kind === 'section') return r.title;
const text = <span style={{ fontWeight: lineWeight(r) }}>{r.displayName || '—'}</span>;
return r.fullName ? <Tooltip title={`Распознано: ${r.fullName}`}>{text}</Tooltip> : text;
},
},
{
key: 'code',
title: 'Код',
width: 60,
align: 'center' as const,
onCell: (r) => (r.kind === 'section' ? sectionSpan(r, false) : { style: { background: lineBackground(r) } }),
render: (_: unknown, r: LayoutRow) => (r.kind === 'line' ? <span style={{ fontWeight: lineWeight(r) }}>{r.row.code}</span> : null),
},
...columns.map((col) => ({
key: col.id,
title: columnTitle(col),
width: 112,
align: 'right' as const,
onCell: (r: LayoutRow) =>
r.kind === 'section'
? sectionSpan(r, false)
: { style: { background: cellBackground(r.row.cells[col.id]) ?? lineBackground(r), fontWeight: lineWeight(r) } },
render: (_: unknown, r: LayoutRow) => {
if (r.kind === 'section') return null;
const row = r.row;
const value = row.cells[col.id];
if (!value) {
return null;
}
const key = cellKey(row.key, col.id);
const tip = cellTip(value);
if (!editable) {
return <Tooltip title={tip}>{formatAmount(value.amount, { fraction: 'auto', empty: '-' })}</Tooltip>;
}
const text = draft[key] ?? value.amount ?? '';
const invalid = key in draft && !parseAmountInput(text).ok;
return (
<Tooltip title={invalid ? 'Неверный формат суммы' : tip}>
<Input
size='small'
status={invalid ? 'error' : undefined}
value={text}
onChange={(e) => onDraftChange(key, e.target.value, value.amount)}
style={{ textAlign: 'right' }}
/>
</Tooltip>
);
},
})),
];
return <Table<LayoutRow> size='small' bordered rowKey='key' columns={tableColumns} dataSource={layout} pagination={false} />;
}