import * as React from 'react'; import { useState, useEffect, useMemo, useRef } from 'react'; import type { WebPartContext } from '@microsoft/sp-webpart-base'; import styles from './SubmitFeature.module.scss'; import { Icon } from '@tnsu/ui-kit'; import { RequestGrid } from '../../components/RequestGrid/RequestGrid'; import { getRequestsByCategory, REQUEST_TYPES } from '../../data/requests'; import { getFormComponent } from '../../forms/registry'; import { IRequestType } from '../../models/types'; import { emitRequestsChanged } from '../../shared/requestEvents'; import { confirmDiscardFormChanges, markFormClean } from '../../shared/unsavedChanges'; import { useIsTester } from '../../shared/useIsTester'; import { useAccessibleAllRequestsFormKeys } from '../../shared/approval'; export interface ISubmitFeatureProps { context: WebPartContext; /** Активная категория. Вкладки теперь рендерятся на уровне портала (RequestPortal), * чтобы оставаться на экране и в режиме согласования — эта фича их только слушает. */ activeCategoryId: string; /** Открыть сразу форму, минуя плитки — с предзаполнением (производная заявка, * sourceId задан) или без него (пустая новая форма конкретного типа). * positionId/nomenclatureId — параметры внешней ссылки (только форма ТМЦ). */ initialForm?: { formKey: string; sourceId?: number; positionId?: string; nomenclatureId?: string; storeName?: string }; /** Открыть сразу форму на РЕДАКТИРОВАНИЕ существующей заявки (черновик/возвращена). */ editForm?: { formKey: string; itemId: number }; /** Пользователь выбрал плитку — сообщаем наверх, чтобы адрес страницы стал * ссылкой на эту пустую форму (см. newRequestLinkUrl в shared/routing). */ onFormSelected?: (formKey: string) => void; /** * Кнопка «Все заявки этого типа» на форме — только когда в админке * approval-route текущему пользователю выдан доступ к этому типу (см. * hasRequestTypeAccess ниже). Ведёт в раздел «Все заявки» (nav='allRequests'). */ onOpenAllRequests?: (requestTypeId: number) => void; } // Фича «Подать заявку»: плитки → беспоук-форма (категории/вкладки — в RequestPortal). // Владелец: автор портала. Живёт целиком в features/submit/**. const SubmitFeature: React.FC = ({ context, activeCategoryId, initialForm, editForm, onFormSelected, onOpenAllRequests, }) => { // «Создать на основе» и «редактировать» оба сразу открывают нужную форму, минуя плитки. const preOpen = initialForm ?? editForm; const initialSelected = useMemo( () => (preOpen ? REQUEST_TYPES.filter((r) => r.id === preOpen.formKey)[0] ?? null : null), [preOpen] ); const [selectedRequest, setSelectedRequest] = useState(initialSelected); // Смена категории (вкладка сверху) — возвращаемся к списку плиток. // Первый прогон пропускаем: иначе форма, открытая по initialForm «на основе» // (её категория может не совпадать с текущей вкладкой), тут же закроется. const firstCategorySync = useRef(true); useEffect(() => { if (firstCategorySync.current) { firstCategorySync.current = false; return; } setSelectedRequest((prev) => (prev && prev.categoryId !== activeCategoryId ? null : prev)); }, [activeCategoryId]); const requests = getRequestsByCategory(activeCategoryId); const isTester = useIsTester(); // Кнопка «Все заявки этого типа» — и на плитке, и на открытой форме, только // если текущему пользователю в админке approval-route выдан доступ к этому // типу (см. shared/approval/requestTypeAccess.ts). Один общий поход по всем // типам разом (formKey формы совпадает с pageUrl approval-route по // соглашению, см. useRequestTypeId) — не по одному запросу на каждую плитку. const accessibleAllRequestsIds = useAccessibleAllRequestsFormKeys(context); // Возврат к плиткам закрывает форму и теряет введённое (раздел остаётся // смонтированным, но состояние формы живёт в самом FormComponent). Спрашиваем, // если правки есть и они не сохранены; после согласия флаг снимаем — терять // уже нечего. const handleBack = (): void => { confirmDiscardFormChanges().then((ok) => { if (!ok) return; markFormClean(); setSelectedRequest(null); }).catch(() => undefined); }; const renderSelected = (request: IRequestType): React.ReactNode => { const FormComponent = getFormComponent(request.id); // Формы «в разработке» открыты только группе «Тестирование» — плитка у // остальных задизейблена, но форму можно открыть и прямой ссылкой (rptype). if (request.comingSoon && isTester === undefined) { return
Загрузка формы…
; } // Форма ещё не реализована или пока закрыта для этого пользователя — заглушка. if (!FormComponent || (request.comingSoon && !isTester)) { return (

Форма «{request.title}» ещё в разработке.

); } // sourceId — «на основе» исходной заявки; editId — правка существующей. const sourceId = initialForm && request.id === initialForm.formKey ? initialForm.sourceId : undefined; const editId = editForm && request.id === editForm.formKey ? editForm.itemId : undefined; // Параметры внешней ссылки — только когда открыта именно та форма, для // которой они пришли (formKey совпал). const external = initialForm && request.id === initialForm.formKey ? initialForm : undefined; const allRequestsTypeId = accessibleAllRequestsIds?.get(request.id); // Ленивый рендер формы: её код подтягивается только сейчас, при открытии. return ( <> {allRequestsTypeId !== undefined && onOpenAllRequests && (
)} Загрузка формы…}> ); }; // Выбор плитки — переключаем и локальный стейт (форма рисуется сразу же), и // сообщаем наверх formKey, чтобы адрес страницы стал ссылкой на эту форму // (см. onFormSelected). RequestPortal в ответ вызовет navigate(), отчего // SubmitFeature пересоздастся (другой key) — initialForm подхватит тот же // formKey, так что видимый результат не изменится, просто через новый инстанс. const handleSelect = (request: IRequestType): void => { setSelectedRequest(request); onFormSelected?.(request.id); }; return (
{selectedRequest ? ( renderSelected(selectedRequest) ) : ( )}
); }; export default SubmitFeature;