From 63251b287aacf1159a1859b4d6a8645d2802f9df Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 27 Oct 2026 00:23:11 +0400 Subject: [PATCH] =?UTF-8?q?AURORA-1683=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0:=20?= =?UTF-8?q?=D1=81=D0=BF=D0=B8=D1=81=D0=BE=D0=BA=20=D0=BA=D0=BE=D0=BC=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B5=D0=B2=20=D0=B2=20?= =?UTF-8?q?=D0=B1=D0=BE=D0=BA=D0=BE=D0=B2=D0=BE=D0=B9=20=D0=BF=D0=B0=D0=BD?= =?UTF-8?q?=D0=B5=D0=BB=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- web/src/layouts/DocumentPage.tsx | 6 +- .../widgets/DocumentComments/CommentsList.tsx | 25 ++++++++ .../DocumentCommentsDrawer.tsx | 63 +++++++++++++++++++ .../DocumentComments/useDocumentComments.ts | 30 +++++++++ .../widgets/DocumentReview/CommentModal.tsx | 52 --------------- 5 files changed, 121 insertions(+), 55 deletions(-) create mode 100644 web/src/widgets/DocumentComments/CommentsList.tsx create mode 100644 web/src/widgets/DocumentComments/DocumentCommentsDrawer.tsx create mode 100644 web/src/widgets/DocumentComments/useDocumentComments.ts delete mode 100644 web/src/widgets/DocumentReview/CommentModal.tsx diff --git a/web/src/layouts/DocumentPage.tsx b/web/src/layouts/DocumentPage.tsx index dda82ca..ff3a110 100644 --- a/web/src/layouts/DocumentPage.tsx +++ b/web/src/layouts/DocumentPage.tsx @@ -11,7 +11,7 @@ import { getDocument } from '../services/api/documents'; import { getFile, listLegalEntityFiles } from '../services/api/files'; import type { DocumentOut } from '../types/api'; import { documentTypeLabel, periodLabel } from '../utils/labels'; -import CommentModal from '../widgets/DocumentReview/CommentModal'; +import { DocumentCommentsDrawer } from '../widgets/DocumentComments/DocumentCommentsDrawer'; import { OsvDocument } from '../widgets/DocumentReview/OsvDocument'; import { StatementReview } from '../widgets/DocumentReview/StatementReview'; import { documentPageTitle, documentSubtitle, optionStatusBadge, statusBadge } from '../widgets/DocumentReview/reviewView'; @@ -116,7 +116,7 @@ export function DocumentPage() { />
} {kind === 'none' && }
- setCommentOpen(false)} /> + setCommentOpen(false)} onAdded={load} /> ); } diff --git a/web/src/widgets/DocumentComments/CommentsList.tsx b/web/src/widgets/DocumentComments/CommentsList.tsx new file mode 100644 index 0000000..c5614ac --- /dev/null +++ b/web/src/widgets/DocumentComments/CommentsList.tsx @@ -0,0 +1,25 @@ +import { Spin, Typography } from 'antd'; +import { formatMoscowDateTime } from '../../utils/dates'; +import type { CommentsState } from './useDocumentComments'; + +/** Список комментариев документа: автор, дата и время по Москве, текст с переносами строк. */ +export function CommentsList({ state, emptyText }: { state: CommentsState | null; emptyText: string }) { + if (state === null || state.status === 'loading') return ; + if (state.status === 'error') return Не удалось загрузить комментарии; + if (state.comments.length === 0) return {emptyText}; + return ( +
+ {state.comments.map((c) => ( +
+
+ {c.author.full_name} + + {formatMoscowDateTime(c.created_at)} + +
+
{c.text}
+
+ ))} +
+ ); +} diff --git a/web/src/widgets/DocumentComments/DocumentCommentsDrawer.tsx b/web/src/widgets/DocumentComments/DocumentCommentsDrawer.tsx new file mode 100644 index 0000000..ece504e --- /dev/null +++ b/web/src/widgets/DocumentComments/DocumentCommentsDrawer.tsx @@ -0,0 +1,63 @@ +import { Button, Drawer, Input, message } from 'antd'; +import { useEffect, useState } from 'react'; +import { ApiError } from '../../api/client'; +import { postComment } from '../../services/api/documents'; +import { CommentsList } from './CommentsList'; +import { useDocumentComments } from './useDocumentComments'; + +type DocumentCommentsDrawerProps = { documentId: string; open: boolean; onClose: () => void; onAdded: () => void }; + +/** Боковая панель экрана документа: список комментариев и добавление нового (правки и удаления нет). */ +export function DocumentCommentsDrawer({ documentId, open, onClose, onAdded }: DocumentCommentsDrawerProps) { + const { state, load, reset } = useDocumentComments(documentId); + const [text, setText] = useState(''); + const [saving, setSaving] = useState(false); + + // список — при каждом открытии; при закрытии сбрасываем, чтобы не показать чужой список после смены документа + useEffect(() => { + if (open) load(); + else reset(); + }, [open, load, reset]); + + const handleAdd = async () => { + setSaving(true); + try { + await postComment(documentId, text.trim()); + message.success('Комментарий добавлен'); + setText(''); + load(); + onAdded(); + } catch (e: unknown) { + // текст в поле не трогаем: можно поправить и повторить + message.error(e instanceof ApiError ? e.message : 'Не удалось добавить комментарий'); + } finally { + setSaving(false); + } + }; + + return ( + + setText(e.target.value)} + autoSize={{ minRows: 3, maxRows: 8 }} + maxLength={4000} + showCount + placeholder='Опишите результат проверки или внесенные правки' + /> + + + }> + + + ); +} diff --git a/web/src/widgets/DocumentComments/useDocumentComments.ts b/web/src/widgets/DocumentComments/useDocumentComments.ts new file mode 100644 index 0000000..0f685d6 --- /dev/null +++ b/web/src/widgets/DocumentComments/useDocumentComments.ts @@ -0,0 +1,30 @@ +import { useCallback, useRef, useState } from 'react'; +import { getComments } from '../../services/api/documents'; +import type { CommentOut } from '../../types/api'; + +export type CommentsState = { status: 'loading' } | { status: 'error' } | { status: 'done'; comments: CommentOut[] }; + +/** Комментарии документа по запросу: `load` перечитывает список, `reset` сбрасывает его (при закрытии панели или поповера). */ +export function useDocumentComments(documentId: string | null) { + const [state, setState] = useState(null); + // номер последнего запроса: ответ устаревшего запроса или пришедший после reset игнорируется + const requestNo = useRef(0); + + const load = useCallback(() => { + requestNo.current += 1; + const mine = requestNo.current; + if (!documentId) return; + // при перечитывании после добавления старый список остаётся на экране до ответа + setState((prev) => (prev?.status === 'done' ? prev : { status: 'loading' })); + getComments(documentId) + .then((comments) => mine === requestNo.current && setState({ status: 'done', comments })) + .catch(() => mine === requestNo.current && setState({ status: 'error' })); + }, [documentId]); + + const reset = useCallback(() => { + requestNo.current += 1; + setState(null); + }, []); + + return { state, load, reset }; +} diff --git a/web/src/widgets/DocumentReview/CommentModal.tsx b/web/src/widgets/DocumentReview/CommentModal.tsx deleted file mode 100644 index bfc491c..0000000 --- a/web/src/widgets/DocumentReview/CommentModal.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Form, Input, Modal, message } from 'antd'; -import { useState } from 'react'; -import { ApiError } from '../../api/client'; -import { postComment } from '../../services/api/documents'; - -type CommentModalProps = { documentId: string; open: boolean; onClose: () => void }; - -export default function CommentModal({ documentId, open, onClose }: CommentModalProps) { - const [comment, setComment] = useState(''); - const [saving, setSaving] = useState(false); - const close = () => { - setComment(''); - onClose(); - }; - const handleOk = async () => { - setSaving(true); - try { - await postComment(documentId, comment.trim()); - message.success('Комментарий добавлен'); - close(); - } catch (e: unknown) { - // окно не закрываем: текст не теряется, можно повторить - message.error(e instanceof ApiError ? e.message : 'Не удалось добавить комментарий'); - } finally { - setSaving(false); - } - }; - return ( - -
- Комментарий}> - setComment(e.target.value)} - autoSize={{ minRows: 3 }} - maxLength={4000} - placeholder='Опишите результат проверки или внесенные правки' - /> - -
-
- ); -}