import { CheckOutlined, EditOutlined } from '@ant-design/icons'; import { Alert, Button, Space, Splitter, Switch, message } from 'antd'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { ApiError } from '../../api/client'; import { acceptChecks, confirmDocument, getStatement, patchStatement } from '../../services/api/documents'; import type { DocumentOut, FailedCheckOut, StatementOut, StatementPatch } from '../../types/api'; import { parseAmountInput } from '../../utils/money'; import { ChecksPanel, failureText } from './ChecksPanel'; import { ReviewCard, confirmButtonStyle } from './ReviewCard'; import { SourcePreview } from './SourcePreview'; import { type HeaderDraft, StatementHeaderBlock } from './StatementHeaderBlock'; import { StatementTable } from './StatementTable'; import { confirmErrorInfo } from './confirmError'; import { reviewActions } from './reviewView'; import { applyDraftInput, buildStatementGrid, draftToPatch, emptySublineKeys } from './statementGrid'; const errorText = (e: unknown, fallback: string) => (e instanceof ApiError ? e.message : fallback); export function StatementReview({ document, onChanged }: { document: DocumentOut; onChanged: () => void }) { const [statement, setStatement] = useState(null); const [loadError, setLoadError] = useState(null); const [draft, setDraft] = useState>({}); const [header, setHeader] = useState({}); const [editing, setEditing] = useState(false); const [saving, setSaving] = useState(false); const [confirmFailures, setConfirmFailures] = useState([]); const [confirmMessage, setConfirmMessage] = useState(null); const [showEmptySublines, setShowEmptySublines] = useState(false); const load = useCallback(async () => { try { setStatement(await getStatement(document.id)); setLoadError(null); } catch (e: unknown) { setLoadError(errorText(e, 'Не удалось получить форму')); } }, [document.id]); useEffect(() => { void load(); }, [load]); const grid = useMemo(() => (statement ? buildStatementGrid(statement) : null), [statement]); // пустые строки детализации скрыты по умолчанию (только показ: PATCH и черновик работают по всем строкам формы) const emptySublines = useMemo(() => (grid ? emptySublineKeys(grid.rows, draft) : new Set()), [grid, draft]); const shownRows = useMemo(() => { if (!grid) return []; return showEmptySublines ? grid.rows : grid.rows.filter((row) => !emptySublines.has(row.key)); }, [grid, showEmptySublines, emptySublines]); const actions = reviewActions(statement?.document.status ?? 'ERROR', editing); // ячейки редактируются только в режиме правки; принять нарушения можно, пока документ не подтверждён const changeable = statement !== null && statement.document.status !== 'CONFIRMED' && statement.document.status !== 'ERROR'; const dirty = Object.keys(draft).length > 0 || Object.keys(header).length > 0; const save = async () => { const values: Record = {}; for (const [key, text] of Object.entries(draft)) { const parsed = parseAmountInput(text); if (!parsed.ok) { message.error('Исправьте суммы с неверным форматом'); return; } values[key] = parsed.value; } const body: StatementPatch = {}; if (Object.keys(values).length > 0) { body.values = draftToPatch(values, grid?.rows ?? []); } const headerPatch: HeaderDraft = {}; for (const field of ['inn', 'report_date', 'units'] as const) { const text = header[field]?.trim(); if (text) { headerPatch[field] = text; } } if (Object.keys(headerPatch).length > 0) { body.header = headerPatch; } if (!body.values && !body.header) { cancel(); return; } setSaving(true); try { // ответ сервера целиком заменяет состояние: он пересчитал проверки и мог сбросить акцепт setStatement(await patchStatement(document.id, body)); setDraft({}); setHeader({}); setEditing(false); setConfirmFailures([]); setConfirmMessage(null); message.success('Изменения сохранены'); onChanged(); } catch (e: unknown) { message.error(errorText(e, 'Не удалось сохранить изменения')); } finally { setSaving(false); } }; const cancel = () => { setDraft({}); setHeader({}); setEditing(false); }; const accept = async (comment: string) => { try { setStatement(await acceptChecks(document.id, comment)); onChanged(); setConfirmFailures([]); setConfirmMessage(null); } catch (e: unknown) { message.error(errorText(e, 'Не удалось принять нарушения')); throw e; } }; const confirm = async () => { try { await confirmDocument(document.id); message.success('Документ подтверждён'); setConfirmFailures([]); setConfirmMessage(null); onChanged(); await load(); } catch (e: unknown) { const info = confirmErrorInfo(e); setConfirmFailures(info.failures); setConfirmMessage(info.code === 'PERIOD_REQUIRED' ? `${info.message}. Исправьте дату отчёта в шапке и сохраните.` : info.message); } }; if (loadError) { return void load()}>Повторить} />; } if (!statement || !grid) { return null; } const cardActions = editing ? ( ) : ( ); return (
setHeader((prev) => ({ ...prev, [field]: text }))} /> {confirmMessage && ( 0 ? confirmFailures.map((f) =>
{failureText(f)}
) : undefined } /> )} {emptySublines.size > 0 && ( )} setDraft((prev) => applyDraftInput(prev, key, text, original, parseAmountInput))} />
); }