109 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Alert, Button, Input, Modal, Space, Tooltip, Typography } from 'antd';
import { useState } from 'react';
import type { ChecksOut, FailedCheckOut } from '../../types/api';
import { expectedActualText, failureKey, failureText } from './checkText';
import CustomModalCloseIcon from '../../components/CustomModalCloseIcon';
type Props = {
checks: ChecksOut;
editable: boolean;
dirty: boolean;
onAccept: (comment: string) => Promise<void>;
onShow?: (key: string) => void;
};
// soft_failed — мягкие соотношения §7 (2400, 2500, «сумма подстрок = родитель») и жёсткие в пределах округления;
// подтверждение они не блокируют
// длинный список нарушений (OCR) не должен выталкивать таблицу формы за экран
const failureList = (list: FailedCheckOut[], onShow?: (key: string) => void) => (
<div style={{ maxHeight: 120, overflowY: 'auto' }}>
{list.map((f) => (
<div key={failureKey(f)}>
{failureText(f)}
{onShow && (f.line_ids ?? []).length > 0 && (
<Button type='link' size='small' onClick={() => onShow(f.line_ids[0])}>
Показать
</Button>
)}
</div>
))}
</div>
);
export function ChecksPanel({ checks, editable, dirty, onAccept, onShow }: Props) {
const [open, setOpen] = useState(false);
const [comment, setComment] = useState('');
const [saving, setSaving] = useState(false);
const canAccept = editable && checks.failed.length > 0 && checks.accepted === null;
const submit = async () => {
setSaving(true);
try {
await onAccept(comment.trim());
setOpen(false);
setComment('');
} catch {
// ошибку уже показал вызывающий; окно остаётся открытым, чтобы не потерять комментарий
} finally {
setSaving(false);
}
};
return (
<Space orientation='vertical' size={8} style={{ width: '100%' }}>
{checks.failed.length === 0 && checks.soft_failed.length === 0 && !checks.cross_form && (
<Alert type='success' showIcon title='Проверки пройдены' />
)}
{checks.failed.length > 0 && (
<Alert type='error' showIcon title={`Нарушения (${checks.failed.length})`} description={failureList(checks.failed, onShow)} />
)}
{checks.soft_failed.length > 0 && (
<Alert
type='warning'
showIcon
title={`Предупреждения проверок (${checks.soft_failed.length})`}
description={failureList(checks.soft_failed, onShow)}
/>
)}
{checks.cross_form && (
<Alert
type='warning'
showIcon
title='Межформенная проверка ББ↔ОФР'
description={`${checks.cross_form.relation}: ${expectedActualText(checks.cross_form)}`}
/>
)}
{checks.accepted && (
<Alert
type='info'
showIcon
title='Нарушения приняты'
description={`${new Date(checks.accepted.at).toLocaleString('ru-RU')}: ${checks.accepted.comment}`}
/>
)}
{canAccept && (
<Tooltip title={dirty ? 'Сначала сохраните изменения' : undefined}>
<Button disabled={dirty} onClick={() => setOpen(true)}>
Принять с комментарием
</Button>
</Tooltip>
)}
<Modal
open={open}
title='Принять нарушения'
okText='Принять'
cancelText='Отмена'
confirmLoading={saving}
centered
closeIcon={<CustomModalCloseIcon />}
okButtonProps={{ disabled: comment.trim().length === 0 }}
onOk={submit}
onCancel={() => setOpen(false)}>
<Typography.Paragraph>Комментарий обязателен (до 2000 символов).</Typography.Paragraph>
<Input.TextArea rows={4} maxLength={2000} value={comment} onChange={(e) => setComment(e.target.value)} />
</Modal>
</Space>
);
}