Files
tnsu-approval-route/frontend/src/webparts/approvalAdmin/components/app/AdminApp.tsx
T
2026-08-19 16:22:50 +05:00

163 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import * as React from 'react';
import styles from './AdminApp.module.scss';
import '@tnsu/ui-kit/styles.css';
import { IconSprite, Sidebar, SidebarToggle } from '@tnsu/ui-kit';
import type { ISidebarItem } from '@tnsu/ui-kit';
import { ScreenKey } from './types';
import { ApprovalRouteApi } from '../../services/api';
import { PortalDictionaryClient } from '../../services/portalDictionary';
import { SharePointListsClient } from '../../services/sharePointLists';
import { RequestManageService } from '../../services/requestManageService';
import { RequestType, TemplateRoute } from '../../models';
import { navigate, useRoute } from '../../routing';
import { texts } from '../../texts';
import { DashboardScreen } from '../screens/DashboardScreen';
import { RequestTypesScreen } from '../screens/RequestTypesScreen';
import { RoutesScreen } from '../screens/RoutesScreen';
import { RouteStepsScreen } from '../screens/RouteStepsScreen';
import { ResolveScreen } from '../screens/ResolveScreen';
import { ManageRequestsScreen } from '../screens/ManageRequestsScreen';
import { ManageByUserScreen } from '../screens/ManageByUserScreen';
// Тот же боковой рейл (@tnsu/ui-kit Sidebar), что и в request-portal — единый
// стиль навигации во всех веб-частях Tnsu. 'routeSteps' сюда не входит: это не
// отдельная вкладка URL, а состояние вкладки 'routes' при открытых шагах
// конкретного маршрута (см. routing.ts) — подсвечиваем в этом случае 'routes'.
const NAV_ITEMS: ISidebarItem[] = [
{ id: 'dashboard', title: texts.nav.dashboard, icon: 'chart' },
{ id: 'requestTypes', title: texts.nav.requestTypes, icon: 'document' },
{ id: 'routes', title: texts.nav.routes, icon: 'send' },
{ id: 'resolve', title: texts.nav.resolve, icon: 'search' },
{ id: 'manageRequests', title: texts.nav.manageRequests, icon: 'clipboard' },
{ id: 'manageByUser', title: texts.nav.manageByUser, icon: 'user-check' }
];
function sidebarActiveId(tab: ScreenKey): string {
return tab === 'routeSteps' ? 'routes' : tab;
}
export interface IAdminAppProps {
api: ApprovalRouteApi;
/** Undefined, пока SPFx выдаёт AAD-клиент для api.tnsu.kz. */
dictionary?: PortalDictionaryClient;
sharePoint: SharePointListsClient;
requestManage: RequestManageService;
currentUserEmail?: string;
}
export const AdminApp: React.FC<IAdminAppProps> = ({ api, dictionary, sharePoint, requestManage, currentUserEmail }) => {
const route = useRoute();
const [selectedRoute, setSelectedRoute] = React.useState<TemplateRoute | undefined>();
const [selectedRequestType, setSelectedRequestType] = React.useState<RequestType | undefined>();
const [navCollapsed, setNavCollapsed] = React.useState(false);
const [mobileNavOpen, setMobileNavOpen] = React.useState(false);
const navigateTab = (key: ScreenKey): void => {
setMobileNavOpen(false);
navigate({ tab: key });
};
const openSteps = (r: TemplateRoute): void => {
setSelectedRoute(r);
setSelectedRequestType(undefined);
api.requestTypes.get(r.requestTypeId)
.then(setSelectedRequestType)
.catch(() => setSelectedRequestType(undefined));
// routesFilter несём с собой: возврат «к маршрутам» должен показать тот же
// отфильтрованный список, из которого ушли (см. onBack ниже).
navigate({ tab: 'routes', routeId: r.id, routesFilter: route.routesFilter });
};
// Открытие по прямой ссылке / обновление страницы / кнопка «назад»: в URL
// лежит только aaroute (число), а RouteStepsScreen ждёт полный TemplateRoute —
// дочитываем его через API. Если сюда пришли через openSteps (клик в
// RoutesScreen), selectedRoute уже тот самый — повторный запрос не нужен.
React.useEffect(() => {
if (route.tab !== 'routes' || !route.routeId) {
setSelectedRoute(undefined);
setSelectedRequestType(undefined);
return;
}
if (selectedRoute && selectedRoute.id === route.routeId) return;
let alive = true;
api.templateRoutes.get(route.routeId)
.then(r => {
if (!alive) return;
setSelectedRoute(r);
return api.requestTypes.get(r.requestTypeId).then(rt => {
if (alive) setSelectedRequestType(rt);
});
})
.catch(() => {
if (alive) setSelectedRoute(undefined);
});
return () => {
alive = false;
};
// selectedRoute — намеренно не в зависимостях: иначе повторный fetch
// запускался бы сразу после того, как этот же эффект его и выставил.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [api, route.tab, route.routeId]);
return (
<div className={styles.shell}>
<IconSprite />
<div className={styles.side}>
<Sidebar
items={NAV_ITEMS}
activeId={sidebarActiveId(route.tab)}
onChange={id => navigateTab(id as ScreenKey)}
brand={texts.nav.title}
collapsed={navCollapsed}
onToggleCollapsed={() => setNavCollapsed(v => !v)}
mobileOpen={mobileNavOpen}
onMobileClose={() => setMobileNavOpen(false)}
/>
</div>
<main className={styles.main}>
<div className={styles.mobileNav}>
<SidebarToggle onClick={() => setMobileNavOpen(true)} />
</div>
<div className={styles.content}>
{route.tab === 'dashboard' && <DashboardScreen api={api} onNavigate={navigateTab} />}
{route.tab === 'requestTypes' && <RequestTypesScreen api={api} sharePoint={sharePoint} />}
{route.tab === 'routes' && !route.routeId && (
<RoutesScreen
api={api}
dictionary={dictionary}
initialFilterRequestTypeId={route.routesFilter}
onOpenSteps={openSteps}
/>
)}
{route.tab === 'routes' && route.routeId && selectedRoute && (
<RouteStepsScreen
api={api}
route={selectedRoute}
requestType={selectedRequestType}
dictionary={dictionary}
sharePoint={sharePoint}
onBack={() => navigate({ tab: 'routes', routesFilter: route.routesFilter })}
/>
)}
{route.tab === 'resolve' && (
<ResolveScreen
api={api}
dictionary={dictionary}
sharePoint={sharePoint}
currentUserEmail={currentUserEmail}
route={route.resolve}
/>
)}
{route.tab === 'manageRequests' && (
<ManageRequestsScreen api={api} dictionary={dictionary} requestManage={requestManage} route={route.manage} />
)}
{route.tab === 'manageByUser' && (
<ManageByUserScreen api={api} dictionary={dictionary} requestManage={requestManage} />
)}
</div>
</main>
</div>
);
};