Управление заявками (#7)
Tests / frontend-test (push) Successful in 43s
Tests / backend-test (push) Successful in 2m36s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/7
This commit was merged in pull request #7.
This commit is contained in:
2026-08-07 09:49:38 +00:00
parent 9d9f390548
commit 0ffa032ade
42 changed files with 2436 additions and 65 deletions
@@ -5,58 +5,103 @@ 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 { 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';
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, currentUserEmail }) => {
const [screen, setScreen] = React.useState<ScreenKey>('dashboard');
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 navigate = (key: ScreenKey): void => {
setScreen(key);
};
const navigateTab = (key: ScreenKey): void => navigate({ tab: key });
const openSteps = (route: TemplateRoute): void => {
setSelectedRoute(route);
const openSteps = (r: TemplateRoute): void => {
setSelectedRoute(r);
setSelectedRequestType(undefined);
api.requestTypes.get(route.requestTypeId)
api.requestTypes.get(r.requestTypeId)
.then(setSelectedRequestType)
.catch(() => setSelectedRequestType(undefined));
setScreen('routeSteps');
navigate({ tab: 'routes', routeId: r.id });
};
// Открытие по прямой ссылке / обновление страницы / кнопка «назад»: в 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}>
<TopNav active={screen} onSelect={navigate} />
<TopNav active={route.tab} onSelect={navigateTab} />
<div className={styles.content}>
{screen === 'dashboard' && <DashboardScreen api={api} onNavigate={navigate} />}
{screen === 'requestTypes' && <RequestTypesScreen api={api} sharePoint={sharePoint} />}
{screen === 'routes' && <RoutesScreen api={api} dictionary={dictionary} onOpenSteps={openSteps} />}
{screen === 'routeSteps' && selectedRoute && (
{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} onOpenSteps={openSteps} />
)}
{route.tab === 'routes' && route.routeId && selectedRoute && (
<RouteStepsScreen
api={api}
route={selectedRoute}
requestType={selectedRequestType}
dictionary={dictionary}
sharePoint={sharePoint}
onBack={() => setScreen('routes')}
onBack={() => navigate({ tab: 'routes' })}
/>
)}
{screen === 'resolve' && (
<ResolveScreen api={api} dictionary={dictionary} sharePoint={sharePoint} currentUserEmail={currentUserEmail} />
{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} />
)}
</div>
</div>