121 lines
3.8 KiB
TypeScript
121 lines
3.8 KiB
TypeScript
import { FileAddOutlined, UploadOutlined } from '@ant-design/icons';
|
||
import { Alert, Button, Modal, Typography, Upload, message } from 'antd';
|
||
import type { UploadFile } from 'antd/es/upload/interface';
|
||
import { useState } from 'react';
|
||
import { ApiError } from '../../api/client';
|
||
import { summarizeUpload, uploadFiles } from '../../services/api/files';
|
||
|
||
type Props = {
|
||
legalEntityId: string;
|
||
consolidationId: string | null;
|
||
organizationName?: string | null;
|
||
open: boolean;
|
||
onClose: () => void;
|
||
onUploaded: () => void;
|
||
};
|
||
|
||
type Summary = ReturnType<typeof summarizeUpload>;
|
||
|
||
export function UploadDocumentsModal({ legalEntityId, consolidationId, organizationName, open, onClose, onUploaded }: Props) {
|
||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||
const [uploading, setUploading] = useState(false);
|
||
const [summary, setSummary] = useState<Summary | null>(null);
|
||
|
||
const close = () => {
|
||
if (uploading) return;
|
||
setFileList([]);
|
||
setSummary(null);
|
||
onClose();
|
||
};
|
||
|
||
const handleOk = async () => {
|
||
if (summary) {
|
||
close();
|
||
return;
|
||
}
|
||
const files = fileList.map((f) => f.originFileObj).filter((f): f is NonNullable<typeof f> => Boolean(f));
|
||
setUploading(true);
|
||
try {
|
||
const result = summarizeUpload(await uploadFiles(legalEntityId, files, consolidationId));
|
||
setSummary(result);
|
||
if (result.accepted.length > 0) onUploaded();
|
||
else message.error('Ни один файл не принят');
|
||
} catch (e: unknown) {
|
||
message.error(e instanceof ApiError ? e.message : 'Не удалось загрузить файлы');
|
||
} finally {
|
||
setUploading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Modal
|
||
open={open}
|
||
title='Загрузка документов'
|
||
onOk={handleOk}
|
||
onCancel={close}
|
||
okText={summary ? 'Готово' : 'Загрузить'}
|
||
okButtonProps={{
|
||
icon: summary ? undefined : <UploadOutlined />,
|
||
disabled: !summary && fileList.length === 0,
|
||
style: { background: summary || fileList.length > 0 ? '#237804' : undefined },
|
||
}}
|
||
cancelText='Отмена'
|
||
confirmLoading={uploading}
|
||
closable={!uploading}
|
||
destroyOnHidden>
|
||
{organizationName && (
|
||
<Typography.Text type='secondary' style={{ display: 'block', marginBottom: 12 }}>
|
||
{organizationName}
|
||
</Typography.Text>
|
||
)}
|
||
{summary ? (
|
||
<>
|
||
{summary.accepted.length > 0 && (
|
||
<Alert type='success' showIcon title={`Принято: ${summary.accepted.length}`} description={summary.accepted.join(', ')} />
|
||
)}
|
||
{summary.rejected.length > 0 && (
|
||
<Alert
|
||
style={{ marginTop: 8 }}
|
||
type='error'
|
||
showIcon
|
||
title={`Отклонено: ${summary.rejected.length}`}
|
||
description={
|
||
<ul style={{ margin: 0, paddingLeft: 16 }}>
|
||
{summary.rejected.map((r, i) => (
|
||
<li key={`${r.name}-${i}`}>
|
||
{r.name} — {r.reason}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
}
|
||
/>
|
||
)}
|
||
</>
|
||
) : (
|
||
<Upload.Dragger
|
||
multiple
|
||
style={{ borderStyle: 'dashed' }}
|
||
fileList={fileList}
|
||
accept='.xls,.xlsx,.xlsb,.pdf,.png,.jpg,.jpeg'
|
||
beforeUpload={(file) => {
|
||
setFileList((prev) =>
|
||
prev.some((f) => f.uid === file.uid) ? prev : [...prev, { uid: file.uid, name: file.name, originFileObj: file }],
|
||
);
|
||
return false;
|
||
}}
|
||
onRemove={(file) => setFileList((prev) => prev.filter((f) => f.uid !== file.uid))}
|
||
showUploadList={{ showRemoveIcon: !uploading }}>
|
||
<p className='ant-upload-drag-icon'>
|
||
<FileAddOutlined />
|
||
</p>
|
||
<p className='ant-upload-text'>Перетащите файлы сюда</p>
|
||
<Button>Выбрать документы</Button>
|
||
<p className='ant-upload-hint' style={{ marginTop: 12 }}>
|
||
ОСВ, ББ, ОФР: xls, xlsx, xlsb, pdf, png, jpg — тип и период определятся автоматически
|
||
</p>
|
||
</Upload.Dragger>
|
||
)}
|
||
</Modal>
|
||
);
|
||
}
|