import { Table, Tooltip, Typography } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import type { ColumnOut } from '../../types/api'; import { CodeCell } from './CodeCell'; import { ColumnHeader } from './ColumnHeader'; import { SeverityIcon } from './IssuesPanel'; import { LineMenuButton } from './LineMenuButton'; import { ValueCell } from './ValueCell'; import { COLORS, rowBackground, valueBackground } from './cellView'; import type { CodeOption } from './codeSuggestions'; import { type CellConfirmState, cellConfirmState } from './editRules'; import type { LayoutRow } from './formLayout'; import { type IssuePlaces, worstSeverity } from './issuePlaces'; import type { LineMenuKey } from './lineMenu'; import type { LineAction, StatementDraft } from './statementDraft'; import { type GridRow, cellKey } from './statementGrid'; type Props = { columns: ColumnOut[]; layout: LayoutRow[]; editable: boolean; draft: Record; onDraftChange: (key: string, text: string, originalAmount: string | null) => void; /** ключи ячеек/строк/колонок с ошибкой черновика (красная рамка) */ errorKeys?: Set; /** предупреждения и нарушения у мест таблицы */ places: IssuePlaces; /** строка, которую нужно подсветить рамкой после перехода «Показать» */ highlightKey?: string | null; /** действия над строками из черновика, подсказки кода и ошибки полей кода (lineId → текст) */ lineActions?: Record; codeOptions?: CodeOption[]; lineErrors?: Record; onLineCodeChange?: (row: GridRow, code: string) => void; onLineMenu?: (row: GridRow, key: LineMenuKey) => void; /** весь черновик: подтверждения ячеек и даты колонок (суммы — в `draft` выше) */ fullDraft?: StatementDraft; onConfirmToggle?: (key: string, state: NonNullable) => void; onColumnDateChange?: (columnId: string, text: string) => void; onColumnDateConfirm?: (col: ColumnOut) => void; }; const ICON_WIDTH = 18; // значок уровня с отступом: у ребёнка вычитается из отступа, чтобы текст не съезжал const CHILD_INDENT = 20; // отступ названия строки детализации под родителем function lineWeight(r: LayoutRow): number | undefined { if (r.kind !== 'line') return undefined; if (r.grand) return 700; return r.total ? 600 : undefined; } const isUnresolved = (r: LayoutRow) => r.kind === 'line' && (r.row.lineStatus === 'unreadable' || r.row.lineStatus === 'missing'); // наименование и код: красноватый (нет кода) / оранжевый (ok с needsReview) / фиолетовый («БАЛАНС») const labelBackground = (r: LayoutRow) => (r.kind === 'line' ? (rowBackground(r.row) ?? (r.grand ? COLORS.grand : undefined)) : undefined); export function StatementTable({ columns, layout, editable, draft, onDraftChange, errorKeys, places, highlightKey = null, lineActions = {}, codeOptions = [], lineErrors = {}, onLineCodeChange, onLineMenu, fullDraft, onConfirmToggle, onColumnDateChange, onColumnDateConfirm, }: Props) { const span = columns.length + 2; // строка-заголовок раздела занимает всю ширину: первая ячейка colSpan = все колонки, остальные скрыты const sectionSpan = (r: LayoutRow, first: boolean) => (r.kind === 'section' ? { colSpan: first ? span : 0 } : {}); const tableColumns: ColumnsType = [ { key: 'name', title: 'Наименование показателя', onCell: (r) => ({ ...sectionSpan(r, true), style: r.kind === 'section' ? { background: COLORS.section, textAlign: 'center', fontWeight: r.level === 1 ? 700 : 600 } : { background: labelBackground(r) }, }), render: (_: unknown, r: LayoutRow) => { if (r.kind === 'section') return r.title; const rowIssues = places.byRow.get(r.key) ?? []; const rowWorst = worstSeverity(rowIssues); const text = ( {r.displayName || '—'} {r.secondaryName && ( {r.secondaryName} )} ); const namedText = r.fullName ? {text} : text; const named = editable ? ( <> {namedText} onLineMenu?.(r.row, key)} /> ) : ( namedText ); // отступ ребёнка — на общей обёртке: значок уровня остаётся внутри отступа и не сдвигает текст (T11.8) const indent = r.child ? CHILD_INDENT : undefined; if (!rowWorst) return
{named}
; return (
(
{i.message}
))}>
{named}
); }, }, { key: 'code', title: 'Код', width: editable ? 160 : 84, align: 'center' as const, onCell: (r) => (r.kind === 'section' ? sectionSpan(r, false) : { style: { background: labelBackground(r), whiteSpace: 'nowrap' } }), render: (_: unknown, r: LayoutRow) => { if (r.kind !== 'line') return null; const lineId = r.row.lineId; const editing = editable && lineId ? { action: lineActions[lineId], options: codeOptions, error: lineErrors[lineId], onCodeChange: (code: string) => onLineCodeChange?.(r.row, code), } : undefined; return ; }, }, ...columns.map((col) => ({ key: col.id, title: ( ), width: 128, align: 'right' as const, onCell: (r: LayoutRow) => r.kind === 'section' ? sectionSpan(r, false) : { style: { background: isUnresolved(r) ? rowBackground(r.row) : (valueBackground(r.row.cells[col.id]) ?? (r.grand ? COLORS.grand : undefined)), fontWeight: lineWeight(r), }, }, render: (_: unknown, r: LayoutRow) => { if (r.kind === 'section') return null; const value = r.row.cells[col.id]; if (!value) return null; const draftKey = cellKey(r.row.key, col.id); return ( ); }, })), ]; return ( size='small' tableLayout='fixed' bordered rowKey='key' columns={tableColumns} dataSource={layout} pagination={false} onRow={(r) => r.kind === 'line' ? { id: `row-${r.key}`, style: r.key === highlightKey ? { outline: '2px solid #1677ff', outlineOffset: -2 } : undefined } : {} } /> ); }