основное готово

This commit is contained in:
gorbenko.pavel
2026-08-06 16:01:33 +05:00
parent 6acb22dd95
commit 13833217d0
15 changed files with 426 additions and 83 deletions
@@ -73,6 +73,14 @@
margin-bottom: $sp-6;
}
// Раздел «Новая заявка», когда открыта другая вкладка. Именно скрытие, а не
// размонтирование: заполненная форма должна пережить переключение вкладок
// (см. RequestPortal.tsx). display:none выводит содержимое и из потока, и из
// последовательности табуляции — скрытая форма не ловит фокус с клавиатуры.
.hiddenPane {
display: none;
}
// На узких экранах рейл уходит наверх.
@media (max-width: 900px) {
.layout {
@@ -4,6 +4,7 @@ import type { IRequestPortalProps } from './IRequestPortalProps';
import { IconSprite } from '../shared/ui/Icon/IconSprite';
import { navigate, useRoute, NavSection } from '../shared/routing';
import { onRequestsChanged, emitRequestsChanged } from '../shared/requestEvents';
import { confirmDiscardFormChanges, markFormClean, markFormDirty } from '../shared/unsavedChanges';
import { SideNav } from './SideNav/SideNav';
import { Drawer } from './Drawer/Drawer';
import { CategoryTabs } from './CategoryTabs/CategoryTabs';
@@ -68,13 +69,28 @@ const RequestPortal: React.FC<IRequestPortalProps> = ({ hasTeamsContext, context
// Переход «создать на основе» (в т.ч. из открытой заявки в панели): переключаем
// вкладку категорий на категорию целевой формы, чтобы она совпала с открытой формой.
// Форма при этом пересоздаётся (см. key у SubmitFeature), поэтому прежние
// несохранённые правки к ней уже не относятся — снимаем флаг.
React.useEffect(() => {
if (route.mode === 'create' && route.formKey) {
const found = REQUEST_TYPES.filter((r) => r.id === route.formKey)[0];
if (found) setActiveCategoryId(found.categoryId);
markFormClean();
}
}, [route.mode, route.formKey]);
// Сохранение (в том числе черновика) шлёт rp-requests-changed — после него
// терять нечего, предупреждать при «создать на основе» больше не о чем.
React.useEffect(() => onRequestsChanged(markFormClean), []);
// Смена категории закрывает открытую форму (SubmitFeature сбрасывает выбор,
// когда категория перестаёт совпадать) — значит введённое пропадёт.
const changeCategory = (categoryId: string): void => {
if (!confirmDiscardFormChanges()) return;
markFormClean();
setActiveCategoryId(categoryId);
};
const goto = (nav: NavSection): void => {
// Клик по УЖЕ активному пункту меню не меняет маршрут, значит эффект
// загрузки в таблице не перезапустится сам. Шлём событие обновления —
@@ -100,26 +116,46 @@ const RequestPortal: React.FC<IRequestPortalProps> = ({ hasTeamsContext, context
</div>
<main className={styles.main}>
{route.nav === 'new' ? (
<>
<div className={styles.tabsWrap}>
<CategoryTabs
categories={CATEGORIES}
activeId={activeCategoryId}
onChange={setActiveCategoryId}
/>
</div>
<SubmitFeature
context={context}
activeCategoryId={activeCategoryId}
initialForm={
route.mode === 'create' && route.formKey && route.sourceId
? { formKey: route.formKey, sourceId: route.sourceId }
: undefined
}
{/* Раздел «Новая заявка» держим СМОНТИРОВАННЫМ и просто прячем стилем,
когда открыта другая вкладка. Иначе React размонтировал бы форму, и
всё введённое пропадало бы при переключении на «Мои заявки» и
обратно. Ключ завязан на «создать на основе»: обычные переключения
вкладок его не меняют (состояние живёт), а вот открытие новой
заявки на основе исходной пересоздаёт форму — SubmitFeature читает
initialForm только при монтировании. */}
<div
className={route.nav === 'new' ? undefined : styles.hiddenPane}
// Делегирование: любой пользовательский ввод внутри раздела помечает
// форму изменённой. Так не нужно инструментировать каждую форму по
// отдельности, и программное заполнение (предзаполнение «на основе»)
// сюда не попадает — React выставляет значения без этих событий.
onInput={markFormDirty}
onChange={markFormDirty}
>
<div className={styles.tabsWrap}>
<CategoryTabs
categories={CATEGORIES}
activeId={activeCategoryId}
onChange={changeCategory}
/>
</>
) : route.nav === 'search' ? (
</div>
<SubmitFeature
key={
route.mode === 'create' && route.formKey && route.sourceId
? `create-${route.formKey}-${route.sourceId}`
: 'blank'
}
context={context}
activeCategoryId={activeCategoryId}
initialForm={
route.mode === 'create' && route.formKey && route.sourceId
? { formKey: route.formKey, sourceId: route.sourceId }
: undefined
}
/>
</div>
{route.nav === 'search' && (
<SearchView
context={context}
onOpen={(request) =>
@@ -131,7 +167,9 @@ const RequestPortal: React.FC<IRequestPortalProps> = ({ hasTeamsContext, context
})
}
/>
) : (
)}
{(route.nav === 'approvals' || route.nav === 'own') && (
<RequestsTableView
context={context}
kind={route.nav === 'approvals' ? 'approvals' : 'own'}