Разные фиксы #8
@@ -35,6 +35,11 @@ export default class RequestPortalWebPart extends BaseClientSideWebPart<IRequest
|
||||
ErrorOverlay,
|
||||
undefined,
|
||||
React.createElement<IRequestPortalProps>(RequestPortal, {
|
||||
// key на подменённом e-mail: смена подмены в property pane должна
|
||||
// пересобрать раздел заново — иначе счётчики в боковом меню и открытые
|
||||
// таблицы остались бы от прежнего пользователя (их эффекты завязаны на
|
||||
// монтирование/context, а не на identity подмены).
|
||||
key: this.properties.debugUserEmail || '__self__',
|
||||
isDarkTheme: this._isDarkTheme,
|
||||
environmentMessage: this._environmentMessage,
|
||||
hasTeamsContext: !!this.context.sdks.microsoftTeams,
|
||||
|
||||
@@ -50,15 +50,26 @@
|
||||
// Раскладка: боковой рейл (side) на всю высоту + основная область (main).
|
||||
.layout {
|
||||
display: flex;
|
||||
align-items: stretch; // рейл тянется на высоту контента
|
||||
align-items: flex-start; // рейл больше НЕ тянется по контенту — см. .side
|
||||
width: 100%;
|
||||
min-height: 78vh; // «присутствие» рейла даже когда контента мало
|
||||
}
|
||||
|
||||
// Рейл-колонка: сам Sidebar тянется по высоте (flex-стретч).
|
||||
// Рейл-колонка: раньше тянулась по высоте .main (align-items: stretch), из-за
|
||||
// чего высота рейла плавала вместе с содержимым соседней колонки — например, у
|
||||
// вкладки «Инициированные мной» появляется доп. фильтр по статусу, .main
|
||||
// становится чуть выше, и градиентный фон Sidebar (посчитанный от своей же
|
||||
// высоты) заметно менял оттенок. Прибиваем рейл к высоте окна (sticky + 100vh):
|
||||
// теперь он не зависит от того, что показано рядом. На мобильном брейкпоинте
|
||||
// сам Sidebar уходит в position:fixed (см. @tnsu/ui-kit) — там эти правила уже
|
||||
// не действуют.
|
||||
.side {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
align-self: flex-start;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.main {
|
||||
|
||||
@@ -134,7 +134,7 @@ const RequestPortal: React.FC<IRequestPortalProps> = ({ hasTeamsContext, context
|
||||
// загрузки в таблице не перезапустится сам. Шлём событие обновления —
|
||||
// таблица на него подписана и перечитает данные фоном, не убирая с экрана
|
||||
// текущие строки (см. RequestsTableView/SearchView).
|
||||
if (nav === route.nav && route.mode === 'submit') emitRequestsChanged();
|
||||
if (nav === route.nav && route.mode === 'submit') emitRequestsChanged(true);
|
||||
// Всё восстанавливаемое состояние раздела передаём ЯВНО, из запомненного.
|
||||
// parseRoute подставил бы его и сам, но тогда оно осталось бы только в
|
||||
// состоянии и не попало бы в адрес: ссылка на раздел вела бы на первую
|
||||
|
||||
@@ -416,7 +416,14 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
|
||||
};
|
||||
|
||||
load().catch(() => undefined); // ошибки уже обработаны внутри load через setError
|
||||
const unsubscribe = onRequestsChanged(() => {
|
||||
const unsubscribe = onRequestsChanged((force) => {
|
||||
// Явный повторный клик по уже открытому пункту меню — пользователь ждёт
|
||||
// видимого обновления, а не молчаливой подмены данных за спиной. Прежние
|
||||
// строки прячем сразу; свежие подставит load().
|
||||
if (force) {
|
||||
setRows(undefined);
|
||||
setLoadedKey('');
|
||||
}
|
||||
load().catch(() => undefined);
|
||||
});
|
||||
// Пока вкладка браузера скрыта, обновляться незачем — при возврате к ней
|
||||
|
||||
@@ -200,7 +200,8 @@ export const SearchView: React.FC<ISearchViewProps> = ({
|
||||
};
|
||||
|
||||
load().catch(() => undefined);
|
||||
const unsubscribe = onRequestsChanged(() => {
|
||||
const unsubscribe = onRequestsChanged((force) => {
|
||||
if (force) setRows(undefined);
|
||||
load().catch(() => undefined);
|
||||
});
|
||||
const timer = window.setInterval(() => {
|
||||
|
||||
@@ -4,12 +4,21 @@
|
||||
// достаточно для нечастых событий (сохранение формы, решение согласующего).
|
||||
const EVENT_NAME = 'rp-requests-changed';
|
||||
|
||||
export function emitRequestsChanged(): void {
|
||||
window.dispatchEvent(new Event(EVENT_NAME));
|
||||
/**
|
||||
* force=true — повторный клик по УЖЕ открытому пункту меню (см. goto в
|
||||
* RequestPortal.tsx): пользователь явно просит обновить список, поэтому таблица
|
||||
* прячет прежние строки и показывает «Загрузка…», пока не придёт свежий ответ.
|
||||
* force=false/не задан — фоновые обновления (создание заявки, решение
|
||||
* согласующего, десятиминутный автообновление): строки остаются на экране, чтобы
|
||||
* не моргать, если данные не изменились.
|
||||
*/
|
||||
export function emitRequestsChanged(force?: boolean): void {
|
||||
window.dispatchEvent(new CustomEvent<{ force?: boolean }>(EVENT_NAME, { detail: { force } }));
|
||||
}
|
||||
|
||||
/** Подписка на изменения. Возвращает функцию отписки (вызвать в cleanup эффекта). */
|
||||
export function onRequestsChanged(handler: () => void): () => void {
|
||||
window.addEventListener(EVENT_NAME, handler);
|
||||
return () => window.removeEventListener(EVENT_NAME, handler);
|
||||
export function onRequestsChanged(handler: (force?: boolean) => void): () => void {
|
||||
const listener = (e: Event): void => handler((e as CustomEvent<{ force?: boolean }>).detail?.force);
|
||||
window.addEventListener(EVENT_NAME, listener);
|
||||
return () => window.removeEventListener(EVENT_NAME, listener);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user