diff --git a/src/App.tsx b/src/App.tsx index 24faf7a..736da23 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -43,7 +43,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> diff --git a/src/components/PageTitle.tsx b/src/components/PageTitle.tsx index c79a8cf..a7f5688 100644 --- a/src/components/PageTitle.tsx +++ b/src/components/PageTitle.tsx @@ -19,8 +19,16 @@ const crumbs = (location: string | string[]) => export default function PageTitle({ location, header, description, backable = false, extra }: PageTitleProps) { const navigate = useNavigate(); return ( -
-
+
+ {/*
*/} +
{backable && (
- {extra &&
{extra}
} + {extra && ( +
+ {extra} +
+ )}
); } diff --git a/src/index.tsx b/src/index.tsx index 207a715..dd1f6db 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -29,7 +29,9 @@ createRoot(rootElement).render( components: { Layout: { siderBg: '#fff', + fontSize: 13, }, + Button: { defaultBorderColor: '#c7cdd5', defaultHoverBg: '#f8fafc', @@ -46,6 +48,10 @@ createRoot(rootElement).render( headerColor: '#667085', headerBg: 'rgb(248, 250, 252)', }, + Form: { + labelFontSize: 12, + labelColor: '#665087', + }, }, }}> diff --git a/src/layouts/DocumentPage.tsx b/src/layouts/DocumentPage.tsx index 676f51e..10f4e41 100644 --- a/src/layouts/DocumentPage.tsx +++ b/src/layouts/DocumentPage.tsx @@ -1,30 +1,51 @@ -import { Button, Empty, Result, Spin, Tag } from 'antd'; +import { Button, Empty, Form, message, Result, Select, Spin, Tooltip } from 'antd'; import { useCallback, useEffect, useState } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; +import { useNavigate, useSearchParams } from 'react-router-dom'; import { ApiError } from '../api/client'; import PageTitle from '../components/PageTitle'; import { fetchLegalEntity } from '../services/api/directories/company_groups'; import { getDocument } from '../services/api/documents'; -import { getFile } from '../services/api/files'; +import { getFile, listLegalEntityFiles } from '../services/api/files'; import type { DocumentOut } from '../types/api'; import { documentTypeLabel, periodLabel } from '../utils/labels'; import { OsvDocument } from '../widgets/DocumentReview/OsvDocument'; import { StatementReview } from '../widgets/DocumentReview/StatementReview'; -import { documentPageTitle, documentSubtitle, statusBadge } from '../widgets/DocumentReview/reviewView'; +import { documentPageTitle, documentSubtitle, optionStatusBadge, statusBadge } from '../widgets/DocumentReview/reviewView'; import { documentViewKind } from '../widgets/DocumentReview/viewKind'; +import { buildDocumentRows } from '../widgets/FilesDrawer/documentRows'; +import { TableTag } from '../components/TableTag/TableTag'; +import { TAG_PRESETS } from '../components/TableTag/presets'; +import { CommentOutlined } from '@ant-design/icons'; +import CommentModal from '../widgets/DocumentReview/CommentModal'; export function DocumentPage() { - const { document_id } = useParams(); + const [searchParams, setSearchParams] = useSearchParams(); + const document_id = searchParams.get('document_id'); + const legal_entity_id = searchParams.get('legal_entity_id'); + const consolidation_id = searchParams.get('consolidation_id'); const navigate = useNavigate(); const [doc, setDoc] = useState(null); const [entityName, setEntityName] = useState(''); const [subtitle, setSubtitle] = useState(null); const [error, setError] = useState(null); + const [filesOptions, setFilesOptions] = useState([]); + const [commentOpen, setCommentOpen] = useState(false); const load = useCallback(async () => { if (!document_id) return; try { const d = await getDocument(document_id); + const filesJson = await listLegalEntityFiles(legal_entity_id, consolidation_id); + const documentOptions = buildDocumentRows(filesJson); + + const options = documentOptions.map((d) => ({ + disabled: d.documentId === null, + label: d.name, + value: d.documentId, + ...d, + title: '', + })); + setFilesOptions(options); setDoc(d); setError(null); fetchLegalEntity(d.legal_entity_id) @@ -36,7 +57,7 @@ export function DocumentPage() { } catch (e: unknown) { setError(e instanceof ApiError ? e.message : 'Не удалось открыть документ'); } - }, [document_id]); + }, [document_id, legal_entity_id, consolidation_id]); useEffect(() => { void load(); @@ -53,7 +74,14 @@ export function DocumentPage() { // Документ в статусе ERROR — данных формы/строк нет: показываем карточку и понятный текст, а не пустой экран. const broken = doc.status === 'ERROR' && kind !== 'none'; return ( -
+
- {badge.label} - +
+
+ + {document_id && ( + + )} + +
+
+ +
+
} /> + {true && ( +
+ +
+ )}
} {kind === 'none' && }
+ { + setCommentOpen(false); + }} + />
); } diff --git a/src/services/api/consolidations.ts b/src/services/api/consolidations.ts index e794227..e20dcf8 100644 --- a/src/services/api/consolidations.ts +++ b/src/services/api/consolidations.ts @@ -8,5 +8,6 @@ export const getConsolidations = (groupId: string) => api.get( export const getVersionCard = (versionId: string) => api.get(`/consolidations/versions/${versionId}`); export const createConsolidation = (body: ConsolidationCreate) => api.post('/consolidations', body); export const downloadChecklistCsv = (versionId: string) => apiFetchBlob(`/consolidations/versions/${versionId}/checklist?format=csv`); +export const getChecklist = (versionId: string) => api.get(`/consolidations/versions/${versionId}/checklist`); /** «Взять в работу»: 201 — взята, 200 — уже ваша; чужая — 409 `LOCKED_BY_OTHER` (перехват — с `takeover_reason`). */ export const lockVersion = (versionId: string) => api.post(`/consolidations/versions/${versionId}/lock`); diff --git a/src/services/api/documents.ts b/src/services/api/documents.ts index fa4547e..30de97c 100644 --- a/src/services/api/documents.ts +++ b/src/services/api/documents.ts @@ -21,3 +21,8 @@ export async function getAllEntries(id: string): Promise { } } } + +export function postComment(document_id: string, comment: string) { + const body = { text: comment }; + return api.post(`/documents/${document_id}/comments/`, body); +} diff --git a/src/services/api/files.ts b/src/services/api/files.ts index 921f864..5d0b9d2 100644 --- a/src/services/api/files.ts +++ b/src/services/api/files.ts @@ -1,4 +1,4 @@ -import { ApiError, apiFetch, apiFetchBlob } from '../../api/client'; +import { api, ApiError, apiFetch, apiFetchBlob } from '../../api/client'; import type { FileOut, Page, UploadResult } from '../../types/api'; export function isUploadResult(body: unknown): body is UploadResult { @@ -56,3 +56,7 @@ export function fetchFileContent(fileId: string): Promise<{ blob: Blob; filename export function getFile(id: string): Promise { return apiFetch(`/files/${id}`); } + +export function removeFile(fileId: string) { + return api.del(`/files/${fileId}`); +} diff --git a/src/store/slices/consolidations/consolidationsSlice.ts b/src/store/slices/consolidations/consolidationsSlice.ts index 1beef35..4f873c2 100644 --- a/src/store/slices/consolidations/consolidationsSlice.ts +++ b/src/store/slices/consolidations/consolidationsSlice.ts @@ -6,6 +6,7 @@ const initialState: ConsolidationsSliceType = { consolidations: [], current_version_id: null, current_legal_entity_id: null, + current_legal_entity_name: null, loading: false, error: null, }; @@ -17,9 +18,12 @@ const consolidationsSlice = createSlice({ setSelectedVersion: (state, action: PayloadAction) => { state.current_version_id = action.payload; }, - setSelectedLegalEntity: (state, action: PayloadAction) => { + setSelectedLegalEntityId: (state, action: PayloadAction) => { state.current_legal_entity_id = action.payload; }, + setSelectedLegalEntityName: (state, action: PayloadAction) => { + state.current_legal_entity_name = action.payload; + }, }, extraReducers: (builder) => { builder @@ -39,4 +43,4 @@ const consolidationsSlice = createSlice({ }); export default consolidationsSlice.reducer; -export const { setSelectedVersion, setSelectedLegalEntity } = consolidationsSlice.actions; +export const { setSelectedVersion, setSelectedLegalEntityId, setSelectedLegalEntityName } = consolidationsSlice.actions; diff --git a/src/store/slices/consolidations/types.ts b/src/store/slices/consolidations/types.ts index 3a4ef02..ab6a346 100644 --- a/src/store/slices/consolidations/types.ts +++ b/src/store/slices/consolidations/types.ts @@ -6,4 +6,5 @@ export type ConsolidationsSliceType = { current_version_id: string | null; error: string | null; current_legal_entity_id: string | null; + current_legal_entity_name: string | null; }; diff --git a/src/types/consolidations/version_members.ts b/src/types/consolidations/version_members.ts index 1834baf..18a559a 100644 --- a/src/types/consolidations/version_members.ts +++ b/src/types/consolidations/version_members.ts @@ -35,6 +35,24 @@ type ChecklistItem = { account?: string | null; }; +type ChecklistRow = { + cells: ChecklistItem[]; + legal_entity_id: string; + legal_entity_name: string; + legal_entity_inn: string; +}; + +type ChecklistRule = { + document_type: DocumentType; + account: string; + is_mandatory: boolean; +}; + +export type ChecklistBackendShape = { + rows: ChecklistRow[]; + rules: ChecklistRule[]; +}; + type VersionMember = { id: string; name: string; diff --git a/src/widgets/ConsoleLegalEntities/ChecklistModal.tsx b/src/widgets/ConsoleLegalEntities/ChecklistModal.tsx new file mode 100644 index 0000000..63724c6 --- /dev/null +++ b/src/widgets/ConsoleLegalEntities/ChecklistModal.tsx @@ -0,0 +1,81 @@ +import { useEffect, useMemo, useState } from 'react'; +import { getChecklist } from '../../services/api/consolidations'; +import { message, Modal, Table, TableColumnsType } from 'antd'; +import { ChecklistBackendShape, type IVersionCard } from '../../types/consolidations/version_members'; +import { DownloadOutlined } from '@ant-design/icons'; +import { buildChecklistColumns, getMarksItems } from './checklist_utils'; +// import { useMe } from '../../auth/useMe'; + +type ChecklistProps = { + open: boolean; + onDownload: () => void; + consolidationData: IVersionCard; + onClose: () => void; +}; + +export default function ChecklistModal({ open, onDownload, consolidationData, onClose }: ChecklistProps) { + const [checklistData, setChecklistData] = useState(null); + + const displayedData = useMemo(() => { + if (!checklistData) return; + const result = []; + for (const row of checklistData.rows) { + const rowObj = { + name: row.legal_entity_name, + inn: row.legal_entity_inn, + ofr: 'MISSING', + balance: 'MISSING', + }; + for (const cell of row.cells) { + if (cell.document_type === 'OSV') { + const code = cell.account; + const key = `osv_${code}`; + rowObj[key] = cell.status; + } else if (cell.document_type === 'BALANCE') { + rowObj.balance = cell.status; + } else if (cell.document_type === 'OFR') { + rowObj.ofr = cell.status; + } + } + result.push(rowObj); + } + return result; + }, [checklistData]); + const columns: TableColumnsType = useMemo(() => { + if (!checklistData) return []; + return buildChecklistColumns(checklistData); + }, [checklistData]); + + useEffect(() => { + async function fetchChecklist() { + if (!consolidationData.id || !open) return; + try { + const res = await getChecklist(consolidationData.id); + setChecklistData(res); + } catch (e) { + message.error(e.message); + } + } + fetchChecklist(); + }, [open, consolidationData.id]); + return ( + { + onDownload(); + onClose(); + }} + okText='Скачать чек-лист' + okButtonProps={{ icon: }}> +
+ {getMarksItems()} + + + + ); +} diff --git a/src/widgets/ConsoleLegalEntities/ConsoleLegalEntities.tsx b/src/widgets/ConsoleLegalEntities/ConsoleLegalEntities.tsx index d21cf2f..bc59727 100644 --- a/src/widgets/ConsoleLegalEntities/ConsoleLegalEntities.tsx +++ b/src/widgets/ConsoleLegalEntities/ConsoleLegalEntities.tsx @@ -6,18 +6,24 @@ import { ColumnSettings, DataTable, useFilteredRows, useVisibleColumns } from '. import { useTableFilters } from '../../components/GenericTable/useTableFilters'; import { downloadChecklistCsv } from '../../services/api/consolidations'; import { getVersionCard } from '../../services/api/consolidations'; -import { setSelectedLegalEntity, setSelectedVersion } from '../../store/slices/consolidations/consolidationsSlice'; +import { + setSelectedLegalEntityId, + setSelectedLegalEntityName, + setSelectedVersion, +} from '../../store/slices/consolidations/consolidationsSlice'; import { useAppDispatch, useAppSelector } from '../../store/store'; import type { IVersionCard, MemberRow } from '../../types/consolidations/version_members'; import { saveBlob } from '../../utils/download'; import { membersConfig } from './config'; import { normalizeData } from './normalize'; import { checklistFilename, responsibleName } from './panelInfo'; +import ChecklistModal from './ChecklistModal'; export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawerOpen: () => void; reloadKey: number }) { const dispatch = useAppDispatch(); const [loading, setLoading] = useState(false); const consolidation_id = useAppSelector((s) => s.consolidations.current_version_id); + const [checklistOpen, setChecklistOpen] = useState(false); const [consolidationData, setConsolidationData] = useState(null); @@ -47,6 +53,10 @@ export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawe message.error(e instanceof ApiError ? e.message : 'Не удалось скачать чек-лист'); } }; + const viewChecklist = () => { + if (!consolidationData) return; + setChecklistOpen(true); + }; const { visibleKeys, setVisibleKeys } = useVisibleColumns(membersConfig); const { rawQuery, columnFilters, setColumnFilters, sort, setSort, resetAll, hasActiveFilters } = useTableFilters(); // single filtered array — title count and table share it @@ -54,67 +64,85 @@ export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawe const onRow = (row: MemberRow) => ({ onClick: () => { dispatch(setSelectedVersion(row.version_id)); - dispatch(setSelectedLegalEntity(row.id)); + dispatch(setSelectedLegalEntityId(row.id)); + dispatch(setSelectedLegalEntityName(row.name)); onDrawerOpen(); }, }); return ( -
- -
-

- Организации -

- {rawRows.length} шт. - {consolidationData && ( - // «Утверждён в РФ» / «Верифицирован в ГО» — маршрут согласования, Срез 7 - - Ответственный: {responsibleName(consolidationData)}   Утверждён в РФ: —   Верифицирован в ГО: — - - )} -
-
- {hasActiveFilters && ( - + )} + + - )} - - -
-
- -
+ + + + + {consolidationData && ( + { + setChecklistOpen(false); + }} + onDownload={downloadChecklist} + consolidationData={consolidationData} + /> + )} + ); } diff --git a/src/widgets/ConsoleLegalEntities/checklist_utils.tsx b/src/widgets/ConsoleLegalEntities/checklist_utils.tsx new file mode 100644 index 0000000..2314758 --- /dev/null +++ b/src/widgets/ConsoleLegalEntities/checklist_utils.tsx @@ -0,0 +1,94 @@ +import { + CheckCircleOutlined, + ClockCircleOutlined, + EllipsisOutlined, + ExclamationCircleOutlined, + EyeOutlined, + MinusCircleOutlined, +} from '@ant-design/icons'; +import { TAG_PRESETS } from '../../components/TableTag/presets'; +import { ChecklistBackendShape, FileStatus } from '../../types/consolidations/version_members'; +import { Avatar, TableColumnsType } from 'antd'; +import { statusMapping } from './consts'; + +export const mapStatus = (status: FileStatus) => { + const presetMapping = { + MISSING: TAG_PRESETS['default'], + CONFIRMED: TAG_PRESETS['green'], + NEEDS_CHECK: TAG_PRESETS['orange'], + RECOGNIZED: TAG_PRESETS['gold'], + ERROR: TAG_PRESETS['red'], + PROCESSING: TAG_PRESETS['geekblue'], + }; + const preset = presetMapping[status]; + + const iconMapping = { + MISSING: , + NEEDS_CHECK: , + ERROR: , + CONFIRMED: , + RECOGNIZED: , + PROCESSING: , + }; + return ( + + ); +}; +export function getMarksItems() { + return ( +
+ Условные обозначения + {Object.entries(statusMapping).map((item) => { + const avatar = mapStatus(item[0] as FileStatus); + return ( +
+ {avatar} {item[1]} +
+ ); + })} +
+ ); +} +export function buildChecklistColumns(checklistData: ChecklistBackendShape) { + const osv_children = []; + const osv_codes = new Set(); + for (const row of checklistData.rows) { + for (const cell of row.cells) { + if (cell.document_type === 'OSV') { + const code = cell.account; + if (!osv_codes.has(code)) { + osv_children.push({ key: `osv_${code}`, dataIndex: `osv_${code}`, title: code, render: (v) => mapStatus(v) }); + osv_codes.add(code); + } + } + } + } + const result: TableColumnsType = [ + { + key: 'name', + dataIndex: 'leName', + title: 'ОРГАНИЗАЦИЯ', + render: (v, r) => ( +
+
+ {r.name}: {r.inn} +
+
+ ), + }, + { key: 'balance', dataIndex: 'balance', title: 'БУХ.БАЛАНС', render: (v) => mapStatus(v) }, + { key: 'ofr', dataIndex: 'ofr', title: 'ОФР', render: (v) => mapStatus(v) }, + { title: 'ОСВ', children: osv_children, render: (v) => mapStatus(v) }, + ]; + + return result; +} diff --git a/src/widgets/DocumentReview/CommentModal.tsx b/src/widgets/DocumentReview/CommentModal.tsx new file mode 100644 index 0000000..48c5af5 --- /dev/null +++ b/src/widgets/DocumentReview/CommentModal.tsx @@ -0,0 +1,43 @@ +import { Form, Input, message, Modal } from 'antd'; +import { useState } from 'react'; +import { postComment } from '../../services/api/documents'; + +export default function CommentModal({ + document_id, // () => void + open, + onClose, +}) { + const [comment, setComment] = useState(''); + const [isLoading, setIsloading] = useState(false); + const handleOk = async () => { + setIsloading(true); + try { + const res = await postComment(document_id, comment); + } catch (e) { + message.error(e.message); + } finally { + setIsloading(false); + onClose(); + } + }; + return ( + Добавить комментарий} + okText='Добавить' + onOk={handleOk} + okButtonProps={{ disabled: comment.length === 0 }} + open={open} + cancelText='Отмена' + onCancel={onClose} + destroyOnHidden> +
+ Комментарий}> + setComment(e.target.value)} + placeholder='Опишите результат проверки или внесенные правки'> + + +
+ ); +} diff --git a/src/widgets/DocumentReview/StatementReview.tsx b/src/widgets/DocumentReview/StatementReview.tsx index 5ac0ffd..893e21a 100644 --- a/src/widgets/DocumentReview/StatementReview.tsx +++ b/src/widgets/DocumentReview/StatementReview.tsx @@ -295,6 +295,11 @@ export function StatementReview({ document, onChanged }: { document: DocumentOut )} {serverErrorOffScreen && } + {grid.changedValues !== null && ( +

+ Измененных значений: {grid.changedValues}. Значений, требующих проверки: {grid.unrecognizedValues} +

+ )} ): string { return [file.original_name, file.file_type, formatMoscowDate(file.uploaded_at)].filter((p) => p).join(' · '); diff --git a/src/widgets/DocumentReview/statementGrid.ts b/src/widgets/DocumentReview/statementGrid.ts index a80c53d..4c232f3 100644 --- a/src/widgets/DocumentReview/statementGrid.ts +++ b/src/widgets/DocumentReview/statementGrid.ts @@ -28,7 +28,39 @@ export type GridRow = { /** Ключ строки: id из БД, у достроенной строки (id = null) — код; двоеточия в ключе нет (разделитель cellKey). */ const rowKey = (id: string | null, code: string | null) => id ?? `code-${code ?? ''}`; -export function buildStatementGrid(statement: StatementOut): { columns: ColumnOut[]; rows: GridRow[] } { +export function buildStatementGrid(statement: StatementOut): { + columns: ColumnOut[]; + rows: GridRow[]; + changedValues: number | null; + unrecognizedValues: number | null; +} { + let changedValues: number | null = 0; + let unrecognizedValues: number | null = 0; + if (statement.document.status === 'NEEDS_CHECK') { + for (const key of ['inn', 'report_date', 'units']) { + const val = statement.header[key]; + if (!val['verified'] && !val['modified']) { + unrecognizedValues++; + } + if (val['modified']) { + changedValues++; + } + } + for (const l of statement.lines) { + for (const c of l.values) { + if (!c.is_recognized && !c.is_modified) { + unrecognizedValues++; + } + if (c.is_modified) { + changedValues++; + } + } + } + } else { + unrecognizedValues = null; + changedValues = null; + } + const columns = [...statement.columns].sort((a, b) => a.column_index - b.column_index); const rows = statement.lines.map((line) => { const cells: Record = {}; @@ -54,7 +86,7 @@ export function buildStatementGrid(statement: StatementOut): { columns: ColumnOu cells, }; }); - return { columns, rows }; + return { columns, rows, changedValues, unrecognizedValues }; } /** diff --git a/src/widgets/FilesDrawer/DeleteModal.tsx b/src/widgets/FilesDrawer/DeleteModal.tsx new file mode 100644 index 0000000..1d81742 --- /dev/null +++ b/src/widgets/FilesDrawer/DeleteModal.tsx @@ -0,0 +1,77 @@ +import React, { useCallback, useState } from 'react'; +import { Modal, Button, Typography, theme, message } from 'antd'; +import { removeFile } from '../../services/api/files'; + +const { Text } = Typography; + +const OptionCard = ({ title, detail, action }) => { + const { token } = theme.useToken(); + return ( +
+
+ {title} + {detail} +
+ {action} +
+ ); +}; + +export default function DeleteReasonModal({ + targetRow, + open, + onCancel, + onDelete, // () => void + onReplace, // () => void +}) { + const [isDeleting, setIsDeleting] = useState(false); + const handleDelete = useCallback(async () => { + setIsDeleting(true); + try { + await removeFile(targetRow.key); + message.success('Файл удалён'); + } catch (e) { + message.error(e.message); + } finally { + setIsDeleting(false); + onDelete(); + } + }, [targetRow.key, onDelete]); + return ( + +
+

Выберите причину удаления документа «{targetRow.name}»

+

Будет удален/заменён сам файл и все его документы

+ + + Удалить + + } + /> + + + Заменить + + } + /> +
+
+ ); +} diff --git a/src/widgets/FilesDrawer/FilesDrawer.tsx b/src/widgets/FilesDrawer/FilesDrawer.tsx index 3026379..789da12 100644 --- a/src/widgets/FilesDrawer/FilesDrawer.tsx +++ b/src/widgets/FilesDrawer/FilesDrawer.tsx @@ -1,5 +1,5 @@ import { CloseOutlined, UploadOutlined } from '@ant-design/icons'; -import { Button, Drawer, Switch, Tooltip, Typography } from 'antd'; +import { Button, Drawer, message, Switch, Tooltip, Typography } from 'antd'; import { useEffect, useState } from 'react'; import { fetchLegalEntity } from '../../services/api/directories/company_groups'; import { isInWork, useCurrentConsolidation } from '../../store/slices/consolidations/selectors'; @@ -9,6 +9,9 @@ import { FilesList } from './FilesList'; import { UploadDocumentsModal } from './UploadDocumentsModal'; import { consolidationLabel } from './consolidationLabel'; import { useLegalEntityFiles } from './useLegalEntityFiles'; +import { DocumentRow } from './documentRows'; +import DeleteReasonModal from './DeleteModal'; +import ReplaceDocModal from './ReplaceDocModal'; const UPLOAD_DISABLED_HINT = 'Загрузка доступна только в версии в работе'; @@ -22,7 +25,21 @@ export function DocumentsDrawer({ open, onClose }: { open: boolean; onClose: () const desk = current !== null && !showAll; const consolidationId = desk ? current.consolidation_id : null; const { files, loading, reload } = useLegalEntityFiles(current_legal_entity_id, consolidationId, open); + const [targetRow, setTargetRow] = useState(null); + const [removalOpen, setRemovalOpen] = useState(false); const canUpload = current !== null && isInWork(current.version_status); + const [replaceOpen, setReplaceOpen] = useState(false); + const [replacing, setReplacing] = useState(false); + + const onDelete = () => { + void reload(); + setRemovalOpen(false); + }; + + const handleReplaceClick = () => { + setRemovalOpen(false); + setReplaceOpen(true); + }; const labelFor = desk ? undefined : (f: FileOut) => consolidationLabel(f.consolidation_id, consolidations, current?.consolidation_id ?? null); @@ -44,6 +61,23 @@ export function DocumentsDrawer({ open, onClose }: { open: boolean; onClose: () }; }, [open, current_legal_entity_id]); + const handleRowDelete = (row: DocumentRow) => { + setTargetRow(row); + setRemovalOpen(true); + }; + const handleReplaceSubmit = ({ file, comment }) => { + try { + setReplacing(true); + message.success('Документ заменен'); + setReplaceOpen(false); + reload(); + } catch (e) { + message.error(e.message); + } finally { + setReplacing(false); + } + }; + const uploadButton = ( + + + + ); +} diff --git a/src/widgets/FilesDrawer/documentRows.ts b/src/widgets/FilesDrawer/documentRows.ts index d5873ee..369e959 100644 --- a/src/widgets/FilesDrawer/documentRows.ts +++ b/src/widgets/FilesDrawer/documentRows.ts @@ -20,6 +20,8 @@ export interface DocumentRow { statusLabel: string; errorReason: string | null; linkable: boolean; + consolidationId: string | null; + legalEntityId: string | null; /** предупреждения разбора файла (общие для всех документов файла) */ fileWarnings: FileWarningOut[]; mappingWarnings: string[]; @@ -72,6 +74,8 @@ export function buildDocumentRows(files: FileOut[], labelFor?: (f: FileOut) => s statusLabel: failed ? 'Ошибка' : 'Обработка', errorReason: errorReason(f), linkable: false, + consolidationId: f.consolidation_id, + legalEntityId: f.legal_entity_id, fileWarnings, mappingWarnings, issueCounts: { error: 0, info: 0 }, @@ -86,7 +90,10 @@ export function buildDocumentRows(files: FileOut[], labelFor?: (f: FileOut) => s const status = documentRowStatus(d.status); return { key: d.id, + consolidationId: f.consolidation_id, documentId: d.id, + file_id: f.id, + legalEntityId: f.legal_entity_id, name, fileType: f.file_type, subtitle: join([typeText, f.file_type, date, extra]),