Правка 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>
115 lines
4.4 KiB
TypeScript
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} />;
|
|
}
|