Архивация списков (#13)
Tests / frontend-test (push) Successful in 44s
Tests / backend-test (push) Failing after 17m4s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/13
This commit was merged in pull request #13.
This commit is contained in:
2026-08-19 09:08:36 +00:00
parent a2672042d7
commit 82512eb1db
56 changed files with 2587 additions and 51 deletions
@@ -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<unknown>): React.ReactNode {
return list.items.length;
}
// Партиций много на серию (архив + текущая) — считаем суммарно по всем типам
// заявок, а не количество самих серий (это и так есть в requestTypesStat).
function partitionsCount(list: AsyncList<RequestTypeListSeries>): 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<IDashboardScreenProps> = ({ api, onNavigate }) => {
const requestTypes = useRequestTypes(api);
const routes = useTemplateRoutes(api);
const listPartitions = useRequestTypeListsAll(api);
return (
<div>
<StatRow>
<StatCard label={texts.dashboard.requestTypesStat} value={statValue(requestTypes)} />
<StatCard label={texts.dashboard.routesStat} value={statValue(routes)} />
<StatCard label={texts.dashboard.listPartitionsStat} value={partitionsCount(listPartitions)} />
</StatRow>
<div className={styles.sectionsLabel}>{texts.dashboard.sections}</div>
@@ -52,6 +63,12 @@ export const DashboardScreen: React.FC<IDashboardScreenProps> = ({ api, onNaviga
description={texts.dashboard.resolveCard.description}
onClick={() => onNavigate('resolve')}
/>
<NavCard
icon="edit"
title={texts.dashboard.manageRequestsCard.title}
description={texts.dashboard.manageRequestsCard.description}
onClick={() => onNavigate('manageRequests')}
/>
</CardGrid>
</div>
);
@@ -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 {
@@ -560,6 +560,13 @@ export const ManageRequestsScreen: React.FC<IManageRequestsScreenProps> = ({ api
<Badge variant={statusVariant(matrix.request.status)}>{matrix.request.status || texts.common.empty}</Badge>
</div>
{/* Список — партиция (см. ТЗ по архивации SharePoint-списков): по мере
роста заявки одного типа переезжают на новый список, и без этой
ссылки не всегда очевидно, какой именно список сейчас открыт. */}
<a className={styles.listLink} href={matrix.dataListUrl} target="_blank" rel="noopener noreferrer">
{texts.manageRequests.listLinkLabel(matrix.dataListTitle)}
</a>
{readOnly && <Alert kind="warn">{texts.manageRequests.fullyClosedNotice}</Alert>}
{!matrix.approvalListTitle && <Alert kind="warn">{texts.manageRequests.noApprovalList}</Alert>}