Параллельные шаги (#3)
Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/3
This commit was merged in pull request #3.
This commit is contained in:
@@ -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';
|
||||
@@ -11,9 +12,12 @@ import { CATEGORIES, DEFAULT_CATEGORY_ID } from '../data/categories';
|
||||
import { REQUEST_TYPES } from '../data/requests';
|
||||
import SubmitFeature from '../features/submit/SubmitFeature';
|
||||
import ApproveFeature from '../features/approve/ApproveFeature';
|
||||
import EditFeature from '../features/edit/EditFeature';
|
||||
import { RequestsTableView } from '../features/myRequests/RequestsTableView';
|
||||
import { SearchView } from '../features/myRequests/SearchView';
|
||||
import { getPendingApproval } from '../features/myRequests/myRequests.service';
|
||||
import { IMyRequest } from '../features/myRequests/models/IMyRequest';
|
||||
import { isEditableStatus } from '../shared/approval';
|
||||
|
||||
// Оболочка портала: тёмное боковое меню (разделы) + основная область.
|
||||
// Логики фич тут нет — файл общий, меняется редко.
|
||||
@@ -68,13 +72,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 => {
|
||||
// Клик по УЖЕ активному пункту меню не меняет маршрут, значит эффект
|
||||
// загрузки в таблице не перезапустится сам. Шлём событие обновления —
|
||||
@@ -83,8 +102,28 @@ const RequestPortal: React.FC<IRequestPortalProps> = ({ hasTeamsContext, context
|
||||
if (nav === route.nav && route.mode === 'submit') emitRequestsChanged();
|
||||
navigate({ mode: 'submit', nav });
|
||||
};
|
||||
// Клик по строке таблицы. СВОЯ заявка в незавершённом статусе (черновик,
|
||||
// возвращённая, отклонённая) открывается на редактирование, всё остальное —
|
||||
// на просмотр: чужую заявку править нельзя, а свою уже ушедшую по маршруту —
|
||||
// тем более (её статус в isEditableStatus не входит).
|
||||
const openRequest = (request: IMyRequest): void =>
|
||||
navigate({
|
||||
mode: request.kind === 'own' && isEditableStatus(request.status) ? 'edit' : 'approve',
|
||||
nav: route.nav,
|
||||
formKey: request.formKey,
|
||||
itemId: request.id,
|
||||
});
|
||||
|
||||
const closeDrawer = (): void => navigate({ mode: 'submit', nav: route.nav });
|
||||
|
||||
// Закрытие панели РЕДАКТИРОВАНИЯ: там открыта форма, и её содержимое живёт
|
||||
// только в памяти — уходя без сохранения, пользователь теряет правки.
|
||||
const closeEditDrawer = (): void => {
|
||||
if (!confirmDiscardFormChanges()) return;
|
||||
markFormClean();
|
||||
closeDrawer();
|
||||
};
|
||||
|
||||
return (
|
||||
<section className={rootClasses}>
|
||||
<IconSprite />
|
||||
@@ -100,49 +139,57 @@ 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' ? (
|
||||
<SearchView
|
||||
</div>
|
||||
<SubmitFeature
|
||||
key={
|
||||
route.mode === 'create' && route.formKey && route.sourceId
|
||||
? `create-${route.formKey}-${route.sourceId}`
|
||||
: 'blank'
|
||||
}
|
||||
context={context}
|
||||
onOpen={(request) =>
|
||||
navigate({
|
||||
mode: 'approve',
|
||||
nav: route.nav,
|
||||
formKey: request.formKey,
|
||||
itemId: request.id,
|
||||
})
|
||||
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={openRequest}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(route.nav === 'approvals' || route.nav === 'own') && (
|
||||
<RequestsTableView
|
||||
context={context}
|
||||
kind={route.nav === 'approvals' ? 'approvals' : 'own'}
|
||||
onOpen={(request) =>
|
||||
navigate({
|
||||
mode: 'approve',
|
||||
nav: route.nav,
|
||||
formKey: request.formKey,
|
||||
itemId: request.id,
|
||||
})
|
||||
}
|
||||
onOpen={openRequest}
|
||||
onMyTurnCountChange={route.nav === 'approvals' ? setApprovalsCount : undefined}
|
||||
/>
|
||||
)}
|
||||
@@ -154,6 +201,21 @@ const RequestPortal: React.FC<IRequestPortalProps> = ({ hasTeamsContext, context
|
||||
<ApproveFeature context={context} formKey={route.formKey} itemId={route.itemId} />
|
||||
</Drawer>
|
||||
)}
|
||||
|
||||
{/* Своя незавершённая заявка — та же панель, но с формой. Ввод здесь тоже
|
||||
помечает состояние изменённым, поэтому обёртка ловит input/change. */}
|
||||
{route.mode === 'edit' && route.formKey && route.itemId && (
|
||||
<Drawer title={`Заявка №${route.itemId}`} onClose={closeEditDrawer}>
|
||||
<div onInput={markFormDirty} onChange={markFormDirty}>
|
||||
<EditFeature
|
||||
context={context}
|
||||
formKey={route.formKey}
|
||||
itemId={route.itemId}
|
||||
onClose={closeDrawer}
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user