From 8df1b86e1ba081f6e6edc06844ee296530187baf Mon Sep 17 00:00:00 2001 From: "gorbenko.pavel" Date: Mon, 17 Aug 2026 16:05:51 +0500 Subject: [PATCH] =?UTF-8?q?=D1=84=D1=80=D0=BE=D0=BD=D1=82=20=D0=B0=D0=B4?= =?UTF-8?q?=D0=BC=D0=B8=D0=BD=D0=BA=D0=B8=20=D0=B2=D1=80=D0=BE=D0=B4=D0=B5?= =?UTF-8?q?=20=D0=B3=D0=BE=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/screens/DashboardScreen.tsx | 11 +++++++++++ .../screens/ManageRequestsScreen.module.scss | 15 +++++++++++++++ .../components/screens/ManageRequestsScreen.tsx | 7 +++++++ .../src/webparts/approvalAdmin/models/index.ts | 1 + .../approvalAdmin/models/requestManage.ts | 7 +++++++ .../approvalAdmin/models/requestTypeLists.ts | 16 ++++++++++++++++ .../src/webparts/approvalAdmin/services/api.ts | 10 +++++++++- .../services/requestManageService.ts | 13 +++++++++++-- frontend/src/webparts/approvalAdmin/texts.ts | 2 ++ 9 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 frontend/src/webparts/approvalAdmin/models/requestTypeLists.ts diff --git a/frontend/src/webparts/approvalAdmin/components/screens/DashboardScreen.tsx b/frontend/src/webparts/approvalAdmin/components/screens/DashboardScreen.tsx index dd12dd5..34848e5 100644 --- a/frontend/src/webparts/approvalAdmin/components/screens/DashboardScreen.tsx +++ b/frontend/src/webparts/approvalAdmin/components/screens/DashboardScreen.tsx @@ -1,9 +1,11 @@ import * as React from 'react'; import styles from './DashboardScreen.module.scss'; import { ApprovalRouteApi } from '../../services/api'; +import { RequestTypeListSeries } from '../../models'; import { ScreenKey } from '../app/types'; import { texts } from '../../texts'; import { useRequestTypes } from '../../hooks/useRequestTypes'; +import { useRequestTypeListsAll } from '../../hooks/useRequestTypeListsAll'; import { useTemplateRoutes } from '../../hooks/useTemplateRoutes'; import { AsyncList } from '../../hooks/useAsyncList'; import { CardGrid, NavCard, StatCard, StatRow } from '../ui'; @@ -20,15 +22,24 @@ function statValue(list: AsyncList): React.ReactNode { return list.items.length; } +// Партиций много на серию (архив + текущая) — считаем суммарно по всем типам +// заявок, а не количество самих серий (это и так есть в requestTypesStat). +function partitionsCount(list: AsyncList): React.ReactNode { + if (list.isLoading || list.error) return texts.common.empty; + return list.items.reduce((sum, series) => sum + series.partitions.length, 0); +} + export const DashboardScreen: React.FC = ({ api, onNavigate }) => { const requestTypes = useRequestTypes(api); const routes = useTemplateRoutes(api); + const listPartitions = useRequestTypeListsAll(api); return (
+
{texts.dashboard.sections}
diff --git a/frontend/src/webparts/approvalAdmin/components/screens/ManageRequestsScreen.module.scss b/frontend/src/webparts/approvalAdmin/components/screens/ManageRequestsScreen.module.scss index a3aa861..b1c987e 100644 --- a/frontend/src/webparts/approvalAdmin/components/screens/ManageRequestsScreen.module.scss +++ b/frontend/src/webparts/approvalAdmin/components/screens/ManageRequestsScreen.module.scss @@ -32,6 +32,21 @@ margin-top: 6px; } +// Ссылка на физический список-партицию — второстепенная деталь под заголовком +// заявки, поэтому приглушённая, а не акцентная (как обычная ссылка $blue-600). +.listLink { + display: inline-block; + margin-top: 4px; + font-size: 12px; + color: $gray-500; + text-decoration: none; + + &:hover { + color: $blue-600; + text-decoration: underline; + } +} + // Колонка с номером шага — узкая, содержимое по центру (важно при rowSpan на // параллельный блок: иначе номер прижимался бы к верхней строке блока). .stepCol { diff --git a/frontend/src/webparts/approvalAdmin/components/screens/ManageRequestsScreen.tsx b/frontend/src/webparts/approvalAdmin/components/screens/ManageRequestsScreen.tsx index 757bc97..e14cb84 100644 --- a/frontend/src/webparts/approvalAdmin/components/screens/ManageRequestsScreen.tsx +++ b/frontend/src/webparts/approvalAdmin/components/screens/ManageRequestsScreen.tsx @@ -560,6 +560,13 @@ export const ManageRequestsScreen: React.FC = ({ api {matrix.request.status || texts.common.empty}
+ {/* Список — партиция (см. ТЗ по архивации SharePoint-списков): по мере + роста заявки одного типа переезжают на новый список, и без этой + ссылки не всегда очевидно, какой именно список сейчас открыт. */} + + {texts.manageRequests.listLinkLabel(matrix.dataListTitle)} + + {readOnly && {texts.manageRequests.fullyClosedNotice}} {!matrix.approvalListTitle && {texts.manageRequests.noApprovalList}} diff --git a/frontend/src/webparts/approvalAdmin/models/index.ts b/frontend/src/webparts/approvalAdmin/models/index.ts index 6a3c328..c800bf9 100644 --- a/frontend/src/webparts/approvalAdmin/models/index.ts +++ b/frontend/src/webparts/approvalAdmin/models/index.ts @@ -2,6 +2,7 @@ export * from './enums'; export * from './dictionaries'; export * from './requestTypes'; +export * from './requestTypeLists'; export * from './templateRoutes'; export * from './templateRouteSteps'; export * from './resolve'; diff --git a/frontend/src/webparts/approvalAdmin/models/requestManage.ts b/frontend/src/webparts/approvalAdmin/models/requestManage.ts index c02fec6..19f9078 100644 --- a/frontend/src/webparts/approvalAdmin/models/requestManage.ts +++ b/frontend/src/webparts/approvalAdmin/models/requestManage.ts @@ -127,6 +127,13 @@ export interface ManagedRequestMatrix { siteUrl: string; /** Title списка «… — данные» (для тела запроса к потоку оповещения, см. RequestManageService.notifyRow). */ dataListTitle: string; + /** + * Абсолютный URL списка данных — заявки нумерованные (списки-партиции, см. + * ТЗ по архивации), и по мере роста текущий список меняется. Ссылка нужна, + * чтобы администратор видел, в каком физическом списке лежит открытая + * заявка, и мог перейти туда напрямую, не выясняя это вручную. + */ + dataListUrl: string; /** Title списка «… — согласование» (вычислен из Title списка данных). */ approvalListTitle: string; /** Обе фазы вместе, упорядочены по StepOrder. */ diff --git a/frontend/src/webparts/approvalAdmin/models/requestTypeLists.ts b/frontend/src/webparts/approvalAdmin/models/requestTypeLists.ts new file mode 100644 index 0000000..9462388 --- /dev/null +++ b/frontend/src/webparts/approvalAdmin/models/requestTypeLists.ts @@ -0,0 +1,16 @@ +// DTO эндпоинта api/request-types/lists (Application/ListPartitioning/*.cs) — +// нумерованная серия SharePoint-списков (партиций) по типу заявки: архив + текущий. +export interface RequestListPartition { + number: number; + siteUrl: string; + listId: string; + // eslint-disable-next-line @rushstack/no-new-null -- зеркалит nullable JSON-контракт бэкенда (Title? в RequestListPartition) + title: string | null; + isCurrent: boolean; + createdAt: string; +} + +export interface RequestTypeListSeries { + requestTypeId: number; + partitions: RequestListPartition[]; +} diff --git a/frontend/src/webparts/approvalAdmin/services/api.ts b/frontend/src/webparts/approvalAdmin/services/api.ts index 42a254f..adc80bc 100644 --- a/frontend/src/webparts/approvalAdmin/services/api.ts +++ b/frontend/src/webparts/approvalAdmin/services/api.ts @@ -7,6 +7,7 @@ import { PatchTemplateRouteInput, PatchTemplateRouteStepInput, RequestType, + RequestTypeListSeries, ResolveTemplateRouteQuery, ResolveTemplateRouteResult, TemplateRoute, @@ -39,7 +40,14 @@ export class ApprovalRouteApi { this.http.patch(`/api/request-types/${id}`, body), remove: (id: number): Promise => - this.http.delete(`/api/request-types/${id}`) + this.http.delete(`/api/request-types/${id}`), + + // Серии партиций СРАЗУ по всем типам заявок (см. + // ListPartitionService.ListAllAsync) — для дашборда (счётчик партиций) и + // портала, которому нужен весь список адресов разом, а не по одному запросу + // на тип. + listsAll: (): Promise => + this.http.get('/api/request-types/lists') }; public readonly templateRoutes = { diff --git a/frontend/src/webparts/approvalAdmin/services/requestManageService.ts b/frontend/src/webparts/approvalAdmin/services/requestManageService.ts index cbdd7e8..47f6b42 100644 --- a/frontend/src/webparts/approvalAdmin/services/requestManageService.ts +++ b/frontend/src/webparts/approvalAdmin/services/requestManageService.ts @@ -207,8 +207,17 @@ export class RequestManageService { this.listTitles(sp, requestType.listId) ]); + const dataListUrl = listAbsUrl(requestType.siteUrl, requestType.listId); + if (!approvalListTitle) { - return { request: toSummary(requestRow), siteUrl: requestType.siteUrl, dataListTitle, approvalListTitle: '', rows: [] }; + return { + request: toSummary(requestRow), + siteUrl: requestType.siteUrl, + dataListTitle, + dataListUrl, + approvalListTitle: '', + rows: [] + }; } const approvalList = sp.web.lists.getByTitle(approvalListTitle); @@ -255,7 +264,7 @@ export class RequestManageService { }; }); - return { request: toSummary(requestRow), siteUrl: requestType.siteUrl, dataListTitle, approvalListTitle, rows }; + return { request: toSummary(requestRow), siteUrl: requestType.siteUrl, dataListTitle, dataListUrl, approvalListTitle, rows }; } /** email → числовой Id пользователя сайта (Person-поле требует Id, не email). */ diff --git a/frontend/src/webparts/approvalAdmin/texts.ts b/frontend/src/webparts/approvalAdmin/texts.ts index 6dc7f2b..fa8ab54 100644 --- a/frontend/src/webparts/approvalAdmin/texts.ts +++ b/frontend/src/webparts/approvalAdmin/texts.ts @@ -89,6 +89,7 @@ export const texts = { dashboard: { requestTypesStat: 'Типов заявок', routesStat: 'Маршрутов согласования', + listPartitionsStat: 'Партиций списков SharePoint', sections: 'Разделы', requestTypesCard: { title: 'Типы заявок', @@ -385,6 +386,7 @@ export const texts = { requestDisabledHint: 'Сначала выберите тип заявки.', noApprovalList: 'У этого типа заявки список согласования не найден (Title списка данных не оканчивается на «— данные»).', requestStatusLabel: 'Статус заявки', + listLinkLabel: (listTitle: string): string => `Список: ${listTitle}`, fullyClosedNotice: 'Заявка полностью закрыта — маршрут доступен только для просмотра.', approvalPhaseTitle: 'Согласование', executionPhaseTitle: 'Исполнение',