From ab3593c17001c85b6284b26e847f946d76638093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B8=D0=BC=D1=83=D1=80=20=D0=93=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D1=86=D0=BA=D0=B8=D0=B9?= Date: Tue, 29 Sep 2026 12:42:42 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B8=D1=81=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=81=D0=BE=D0=B3=D0=BB=D0=B0=D1=81=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=20=D0=BA=D0=BD=D0=BE=D0=BF?= =?UTF-8?q?=D0=BA=D0=B5=20=D0=B2=20=D1=81=D1=82=D1=80=D0=BE=D0=BA=D0=B5=20?= =?UTF-8?q?=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=20=D0=B7=D0=B0=D1=8F=D0=B2?= =?UTF-8?q?=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В каждой строке таблиц заявок — кнопка «История согласования»: окно с той же лентой, что в карточке, без открытия самой заявки. Колонка действий теперь есть всегда (история у всех строк, корзина — у своих черновиков). Co-Authored-By: Claude Opus 5.5 (1M context) --- .../myRequests/RequestHistoryDialog.tsx | 42 +++++++++++++++++ .../myRequests/RequestsTableView.module.scss | 7 +++ .../features/myRequests/RequestsTableView.tsx | 45 ++++++++++++++----- 3 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 src/webparts/requestPortal/features/myRequests/RequestHistoryDialog.tsx diff --git a/src/webparts/requestPortal/features/myRequests/RequestHistoryDialog.tsx b/src/webparts/requestPortal/features/myRequests/RequestHistoryDialog.tsx new file mode 100644 index 0000000..6347441 --- /dev/null +++ b/src/webparts/requestPortal/features/myRequests/RequestHistoryDialog.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import { Alert, Dialog } from '@tnsu/ui-kit'; +import { ApprovalHistoryFeed, ApprovalListService, approvalServiceFor, HISTORY_TEXT } from '../../shared/approval'; +import { friendlyErrorMessage } from '../../shared/diagnostics'; +import { IMyRequest } from './models/IMyRequest'; + +interface Props { + /** Заявка, чью историю показываем; undefined — окно закрыто. */ + request: IMyRequest | undefined; + onClose: () => void; +} + +/** + * Список согласования ИМЕННО той партиции, где лежит заявка (listId строки), — + * как и остальные дочитывания в таблице. Если партицию не удалось сопоставить + * с формой, approvalServiceFor бросает — показываем ошибку в окне, а не роняем таблицу. + */ +function approvalFor(request: IMyRequest): { approval?: ApprovalListService; error?: string } { + try { + return { approval: approvalServiceFor(request.formKey, request.listId) }; + } catch (e) { + return { error: friendlyErrorMessage(e) }; + } +} + +/** + * «История согласования» заявки прямо из таблицы — по кнопке в строке, без + * открытия самой заявки. Та же лента, что в карточке (ApprovalHistoryFeed). + */ +export const RequestHistoryDialog: React.FC = ({ request, onClose }) => { + const target = React.useMemo(() => (request ? approvalFor(request) : {}), [request]); + const number = request ? request.code || `#${request.id}` : ''; + + return ( + + {request && target.error && {target.error}} + {request && target.approval && ( + + )} + + ); +}; diff --git a/src/webparts/requestPortal/features/myRequests/RequestsTableView.module.scss b/src/webparts/requestPortal/features/myRequests/RequestsTableView.module.scss index d55f86c..5480bc6 100644 --- a/src/webparts/requestPortal/features/myRequests/RequestsTableView.module.scss +++ b/src/webparts/requestPortal/features/myRequests/RequestsTableView.module.scss @@ -79,6 +79,13 @@ overflow: hidden; } +// Кнопки действий строки (история, корзина) — в ряд, прижаты вправо. +.rowActions { + display: flex; + justify-content: flex-end; + gap: $sp-1; +} + // Пустой комментарий в колонке «Причина возврата/отклонения». .commentEmpty { color: $gray-400; diff --git a/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx b/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx index aefb240..776fd37 100644 --- a/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx +++ b/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx @@ -29,6 +29,7 @@ import { useRoutedQuery } from '../../shared/usePersistedQuery'; import { IPage, pageKey, pageSignature, readPage, writePage } from './pageCache'; import { Pager } from './Pager'; import { RequestsFilterBar } from './RequestsFilterBar'; +import { RequestHistoryDialog } from './RequestHistoryDialog'; import { SortableHeader } from './SortableHeader'; import { filterRows, hasActiveFilters, ISortState, SortKey, sortRows } from './requestsTableFilters'; import { useTableRefinement } from './useTableRefinement'; @@ -237,9 +238,11 @@ const Table: React.FC<{ sort: ISortState; onSort: (key: SortKey) => void; onOpen: (request: IMyRequest) => void; + /** Показать историю согласования заявки (окно, без открытия заявки). */ + onShowHistory: (request: IMyRequest) => void; /** Удалить черновик; задан только там, где в таблице бывают свои черновики. */ onDelete?: (request: IMyRequest) => void; -}> = ({ rows, view, statusFilter, sort, onSort, onOpen, onDelete }) => { +}> = ({ rows, view, statusFilter, sort, onSort, onOpen, onShowHistory, onDelete }) => { const cols = columnsFor(view, statusFilter); const sortable = (label: string, key: SortKey): React.ReactNode => ( @@ -297,27 +300,41 @@ const Table: React.FC<{ render: (r) => r.decisionComment || Без комментария }); } - if (onDelete && rows.some(canDeleteRow)) { - columns.push({ - key: 'actions', - title: '', - width: '56px', - render: (r) => - canDeleteRow(r) ? ( + // Действия строки: история согласования — у всех, корзина — только у своих + // черновиков. Клик по всей строке открывает заявку — кнопкам это не нужно, + // поэтому stopPropagation. + const hasDeletable = !!onDelete && rows.some(canDeleteRow); + columns.push({ + key: 'actions', + title: '', + // Место под одну или две иконки-кнопки (sm) с зазором. + width: hasDeletable ? '92px' : '56px', + render: (r) => ( +
+ { + e.stopPropagation(); + onShowHistory(r); + }} + /> + {onDelete && canDeleteRow(r) && ( { - // Клик по всей строке открывает заявку — кнопке это не нужно. e.stopPropagation(); onDelete(r); }} /> - ) : null - }); - } + )} +
+ ) + }); return (
@@ -379,6 +396,8 @@ export const RequestsTableView: React.FC = ({ const [totalRows, setTotalRows] = React.useState(0); const [error, setError] = React.useState(''); const [deleteError, setDeleteError] = React.useState(''); + // Заявка, чья история согласования открыта в окне; undefined — окно закрыто. + const [historyFor, setHistoryFor] = React.useState(undefined); // Список перечитается сам: confirmAndDeleteDraft шлёт rp-requests-changed. const handleDelete = (request: IMyRequest): void => { @@ -729,11 +748,13 @@ export const RequestsTableView: React.FC = ({ sort={sort} onSort={refinement.onSort} onOpen={onOpen} + onShowHistory={setHistoryFor} onDelete={view === 'own:created' ? handleDelete : undefined} /> )} + setHistoryFor(undefined)} /> ); };