Merge remote-tracking branch 'origin/main' into archive

# Conflicts:
#	src/webparts/requestPortal/RequestPortalWebPart.ts
#	src/webparts/requestPortal/forms/Clearance/ClearanceForm.tsx
#	src/webparts/requestPortal/forms/Clearance/submit.ts
This commit is contained in:
gorbenko.pavel
2026-08-18 14:16:45 +05:00
45 changed files with 1787 additions and 265 deletions
@@ -1,11 +1,7 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
type IPropertyPaneConfiguration,
PropertyPaneDropdown,
PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { type IPropertyPaneConfiguration, PropertyPaneDropdown } from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { IReadonlyTheme } from '@microsoft/sp-component-base';
@@ -14,15 +10,22 @@ import RequestPortal from './components/RequestPortal';
import { IRequestPortalProps } from './components/IRequestPortalProps';
import { initSP } from './shared/pnp/pnpjs';
import { installErrorLogging } from './shared/diagnostics';
import { setImpersonatedEmail } from './shared/impersonation';
import { setImpersonatedIdentity } from './shared/impersonation';
import { APPROVAL_API_BASE_URL_LOCAL, APPROVAL_API_BASE_URL_PROD, setApprovalApiBaseUrl } from './shared/config';
import { initListPartitions } from './shared/approval/listPartitions';
import { ErrorOverlay } from './shared/ui';
import { PropertyPaneDebugUser } from './propertyPane/PropertyPaneDebugUser';
export interface IRequestPortalWebPartProps {
description: string;
/** Отладочная подмена личности — см. shared/impersonation.ts. На бою пусто. */
debugUserEmail: string;
/**
* Отладочная подмена личности — см. shared/impersonation.ts. На бою пусто.
* Одно поле на оба случая: e-mail, введённый напрямую, хранится как есть;
* ФИО, найденное поиском по имени (см. propertyPane/DebugUserField), после
* резолва заменяется собой же на найденный e-mail — отдельное поле под
* «сырой» ввод не нужно, DebugUserField ведёт его в своём локальном состоянии.
*/
debugUserQuery: string;
debugUserDisplayName: string;
/** Куда шлём запросы approval-route (маршруты согласования) — прод или локальный backend. */
approvalApiBaseUrl: string;
}
@@ -35,7 +38,7 @@ export default class RequestPortalWebPart extends BaseClientSideWebPart<IRequest
public render(): void {
// Именно здесь, а не в onInit: свойство меняется в панели без перезапуска
// веб-части, и render вызывается на каждое такое изменение.
setImpersonatedEmail(this.properties.debugUserEmail);
setImpersonatedIdentity(this.properties.debugUserQuery, this.properties.debugUserDisplayName);
this.properties.approvalApiBaseUrl = this.properties.approvalApiBaseUrl || APPROVAL_API_BASE_URL_PROD;
setApprovalApiBaseUrl(this.properties.approvalApiBaseUrl);
@@ -43,7 +46,11 @@ export default class RequestPortalWebPart extends BaseClientSideWebPart<IRequest
ErrorOverlay,
undefined,
React.createElement<IRequestPortalProps>(RequestPortal, {
description: this.properties.description,
// key на подменённом e-mail: смена подмены в property pane должна
// пересобрать раздел заново — иначе счётчики в боковом меню и открытые
// таблицы остались бы от прежнего пользователя (их эффекты завязаны на
// монтирование/context, а не на identity подмены).
key: this.properties.debugUserQuery || '__self__',
isDarkTheme: this._isDarkTheme,
environmentMessage: this._environmentMessage,
hasTeamsContext: !!this.context.sdks.microsoftTeams,
@@ -80,7 +87,7 @@ export default class RequestPortalWebPart extends BaseClientSideWebPart<IRequest
if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook
return this.context.sdks.microsoftTeams.teamsJs.app.getContext()
.then(context => {
let environmentMessage: string = '';
let environmentMessage: string;
switch (context.app.host.name) {
case 'Office': // running in Office
environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment;
@@ -140,13 +147,29 @@ export default class RequestPortalWebPart extends BaseClientSideWebPart<IRequest
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
}),
PropertyPaneTextField('debugUserEmail', {
label: strings.DebugUserEmailFieldLabel,
description: strings.DebugUserEmailFieldDescription,
placeholder: 'employee@tnsu.kz'
PropertyPaneDebugUser('debugUserQuery', {
label: strings.DebugUserFieldLabel,
description: strings.DebugUserFieldDescription,
context: this.context,
initialQuery: this.properties.debugUserQuery,
onResolved: (query, email, displayName) => {
// Сохраняем ТОЛЬКО e-mail (введённый напрямую или найденный по
// ФИО) — свойство одно и то же используется как e-mail для
// подмены (см. render() выше), так что писать в него сырое ФИО
// нельзя. Пустой query — поле очистили, подмену выключаем.
// А вот «email пуст, но query не пуст» — ФИО ещё не нашлось
// (недописано/не существует): ничего не трогаем, иначе неудачный
// промежуточный поиск молча выключил бы уже работающую подмену.
if (email) {
this.properties.debugUserQuery = email;
this.properties.debugUserDisplayName = displayName;
this.render();
} else if (!query) {
this.properties.debugUserQuery = '';
this.properties.debugUserDisplayName = '';
this.render();
}
}
})
]
},