Перенос, декомпозиция и перекраска форм CT/KPS/OID на @tnsu/ui-kit, config-driven дашборд-реестр по трём типам документов. Из отслеживания исключены артефакты сборки (lib-commonjs/jest-output/release) — забыты в .gitignore ранее.
20 KiB
OID — перенос в ct-forms-portal (Фаза 4)
Порядок (тот же, что и CT): Этап 1 перенос 1:1 → Этап 2 декомпозиция под структуру KPS → Этап 3 перекраска на ui-kit.
Правило: сохранить 100% поведения; build после каждого этапа. Эталон структуры — forms/kps/, карта резки — docs/CT-PORT.md (Step2/3/4 у OID структурно близки к CT).
Источник: oid-create-form/src/webparts/oidCreateForm/. 10 файлов, самый большой — Step3ComparisonTable.tsx (2533 стр, ещё крупнее, чем был у CT).
Разведка (перед переносом)
Step3ComparisonTable.tsx(2533),Step4Approval.tsx(844),Step1WorksInput.tsx(646),Step2CommercialOffers.tsx(533),hooks/useOidForm.ts(299),OidService.ts(1470).- Шаг 1 у OID устроен ИНАЧЕ, чем у CT:
Step1WorksInput— это ручной ввод сгруппированных работ (PositionGroup → OIDPosition[], поляparentGroup/priceSmeta/summaSmeta,ComboBoxдля группы), а не выборка из SP-списка потребностей (какRequestsTable+useRequestsу CT). Ближе к паттерну ItemsTable из «СЗ на перемещение ТМЦ». Для Шага 1 нужен свой чертёж декомпозиции на Этапе 2 — карта CT/KPS сюда не подходит. - Step2/Step3/Step4 структурно близки к CT (коммерческие предложения / матрица сравнения / согласование) — карта резки CT применима с поправкой на домен (parentGroup/priceSmeta вместо disciplineCode/SMR/agskCode).
- 0% ui-kit — в отличие от CT (который уже частично сидел на
@tnsu/ui-kit@0.4.0), OID полностью на raw Fluent. На Этапе 3 конвертировать всё, форы нет.
Этап 1 — перенос 1:1 — ✅ ЗАВЕРШЁН
- ✅ Скопированы
components/иservices/→forms/oid/{components,services}/(17 файлов, 1:1, без правок логики). - ✅
OidService.tsНЕ ТРЕБУЕТ параметризации siteUrl (в отличие от Ct/KpsCtFormService!): модуль-константаSITE_URL = "https://tnsukz.sharepoint.com/sites/sed"уже прописана как раз на целевой сайт портала — OID изначально жил на /sites/sed, миграции сайта (в отличие от CT/KPS) не было. Все ~15 обращений к SP-листам используют эту константу напрямую (неthis.baseUrl), socontext.pageContext.web.absoluteUrlтоже будет верно резолвиться при работе внутри портала. Решение: оставитьOidService.tsполностью нетронутым — любая правка (даже добавление неиспользуемогоsiteUrl?) была бы риском без пользы. - ✅
IOidCreateFormProps.ts:hasTeamsContextбыл объявлен, но нигде не читался (мёртвый пропс) — сделан опциональным (hasTeamsContext?: boolean), чтобы порталу не нужно было его прокидывать.siteUrl?НЕ добавлен (сервису он не нужен — см. п.2, не добавляем неиспользуемый пропс). - ✅ Обвязка портала:
oidDocumentConfig.ts: добавленыinPortal: true+buildInPortalParams(условиеЧерновик/formed→oidId, иначе →oidid— сверено сuseOidForm.ts, который читает именно так; условие шире, чем у CT, т.к.oidDocumentConfigуже поддерживал статусformedвbuildLinkUrl).CtFormsPortal.tsx: добавлен импортOidCreateForm+ веткаroute.form === 'oid'→<OidCreateForm>+ «← К реестру» (по образцу CT/KPS-веток).FORM_ID_PARAMSуже содержалoidId/oidid(задел на будущее).Dashboard/config/index.ts— уже были готовы (createOidDocumentConfig()зарегистрирован,Dashboard.tsxполностью generic поDOCUMENT_TYPES, без хардкода typeKey) — правок не потребовалось.
- ✅
heft build— зелёный (после фикса ниже), запущено и открывается в workbench.
Build-фикс (tsconfig): первый прогон дал ошибки TS2550 flatMap/Object.entries does not exist +
каскадные TS7006 implicit any. Причина: у исходного oid-create-form/tsconfig.json был явный
"lib": ["es2019", "dom", "dom.iterable"], а у ct-forms-portal/tsconfig.json — только базовый
rig-конфиг (dom, es5, es2015.core/collection/iterable/promise/proxy, без ES2017/ES2019). Код OID
использует Array.flatMap/Object.entries, которых там нет. Решение: добавлен тот же lib-оверрайд
в ct-forms-portal/tsconfig.json (а не переписывание кода OID под ES5-совместимые аналоги) — так
проще, ничего не меняет в поведении, и это чистая надстройка типов (не влияет на CT/KPS, которые эти
методы не используют). Файлы OID остались нетронутыми.
Этап 2 — декомпозиция
Статус: [x] Step2CommercialOffers ✅ | [x] Step4Approval ✅ | [ ] Step1WorksInput | [ ] Step3ComparisonTable
✅ Step2CommercialOffers (533) — СДЕЛАНО (build+workbench ок)
533 → контейнер Step2CommercialOffers.tsx (165) + useKpProcessing.ts (248, конвейер Syntex/Flow +
стейт uploadedFiles/suppliers/kpFiles) + step2Persist.ts (54, handleSave→функция, возвращает patch)
- вьюхи
SupplierList.tsx(43) /UploadedKpList.tsx(45) /AddNoOfferDialog.tsx(53, диалог «Добавить без КП» — держит своё UI-состояние формы). Отличие от CT: список поставщиков ведётся прямо на Шаге 2 (не как у CT), диалог вместо инлайн-формы добавления. Сохранён silent-guardif (!formData.oid) return;перед вызовом persist (было тихим no-op, не стало видимой ошибкой).
✅ Step4Approval (844) — СДЕЛАНО (build+workbench ок)
844 → useStep4Approval.ts (478: цепочка-операции, add/delete/replace, handleActionConfirm,
send/export/restart, производные флаги + удобные методы selectAddSuggestion/cancelAddRow/openReplace/…)
- view
Step4Approval.tsx(426, только разметка + helpers STATUS_LABELS/getStatusClass/formatDate; Fluent). Структурно почти идентично CT (кромеhandleExport≈CThandleSendToIncore, методы сервиса называются иначе:generateOidReport/updateOIDStatus/resetApprovalChainToDraft). Финальное дробление вьюхи (add-member/replace-dialog поиск в под-компоненты) — отложено на Этап 3, как у CT.
✅ Step1WorksInput (646) — СДЕЛАНО (build ожидается)
646 → useStep1WorksInput.ts (445, перенос ВЕРБАТИМ: весь стейт/рефы(projectSearchTimer/groupsRef/
isMounted/dirtyRef/syncTimer/pendingOidRef/persistRef/scheduleSyncRef)/эффекты(sessionStorage-sync,
гидратация из formData с dirty-guard, cleanup-flush на unmount)/хендлеры(loadProjects/persistPositions/
scheduleSync/markDirtyAndSync/handleWorkNameBlur/handleProjectChange/updateGroupName/updatePosition/
addGroup/removeGroup/addPosition/removePosition/validate/handleSave)) + view Step1WorksInput.tsx
(239, только разметка). Риск: клубок рефов/дебаунс-синхронизации позиций в SP + защита от потери правок
при F5/гидратации (dirtyRef) — того же класса, что CP4 у CT, только меньшего масштаба. dirtyRef
экспортирован из хука как есть (вьюха дёргает dirtyRef.current = true инлайн в одном месте — сохранено
дословно, не оборачивал в сеттер). ✅ build+workbench-тест пройден пользователем (создание группы/позиции, debounce-запись, F5, blur
наименования работ → авто-создание ОИД) — без регрессий.
✅ Step3ComparisonTable (2533) — СДЕЛАНО по чекпоинтам (build ожидается)
Разведка подтвердила: структура похожа на CT (Step3+Step4-согласование смешаны), но домен другой (группы позиций с колонкой «Группа» в шапке, нет валют/тумблера ед.изм., смета price/summa как read-only референс) — и два независимых debounce-автосейва (офферы + критерии, оба по 1500мс) поверх unmount-persist и beforeunload-persist — риск выше, чем был у CT.
- ✅ CP1 согласование →
useStep3Approval.ts+InlineApprovalChain.tsx(голосование/делегат, 1:1, NormalPeoplePicker/IPersonaProps оставлены как есть — Fluent, трогать на Этапе 3). - ✅ CP2 AI/отчёт →
useAiComment.ts(комментарий ИИ + отдельная генерация Excel-отчётаhandleGenerateReport/generateOidReport— своя фича, отсутствует в CT в таком виде) +AiCommentBlock.tsx(проще, чем у CT — нет удаления комментария). - ✅ CP3 хелперы/типы →
matrixTypes.ts(CriteriaEntry/CellEdit/EditableOffers/ExtraSupplier/ ExtraPosition/PositionRow) +matrixHelpers.ts(FIXED_CRITERIA/FIXED_COLS/genId/fmt/fmtKzt/ buildOfferKey). - ✅ CP4 ядро матрицы →
useStep3Matrix.ts(1186 строк, перенос ВЕРБАТИМ: весь стейт/рефы/эффекты ОБОИХ автосейвов(офферы+критерии)/unmount-persist/beforeunload-persist/мемо/хендлеры/handleSave).extraIdCounter— модульная переменная в этом файле (как у CT).getPosRowSpan(для rowSpan группы в шапке таблицы) тоже переехал в хук и возвращается наружу. - Контейнер: 2533 → 877 строк. Финальное дробление render (таблица цен как отдельные под-компоненты) сознательно НЕ делали — как и у CT/OID Step1/2/4, откладывается на Этап 3 вместе с перекраской (не трогать вьюху дважды).
- ✅ Build+workbench-тест подтверждён пользователем — без регрессий (матрица, оба автосейва, F5, unmount-persist, добавление/удаление поставщика/позиции, согласование, отчёт, AI).
✅✅✅ ЭТАП 2 ДЛЯ OID ПОЛНОСТЬЮ ЗАВЕРШЁН
Все 4 god-файла (Step1WorksInput/Step2CommercialOffers/Step3ComparisonTable/Step4Approval) разобраны,
build+workbench-тест пройден по каждому. Самый рискованный кусок всего OID (Step3, два автосейва) —
подтверждён рабочим. Дальше — Этап 3: перекраска на @tnsu/ui-kit (полный объём, 0% ui-kit форы нет,
как у KPS изначально; не забыть полный проход по цветовым токенам + проверить локальные компоненты
типа Breadcrumbs на скрытые Fluent-цвета вне @fluentui-импортов — урок с CT).
Доменные отличия OID (НЕ терять при декомпозиции!)
- Шаг 1 — ручной ввод групп работ (
PositionGroup/OIDPosition), не SP-список потребностей. - Позиции:
parentGroup,positionType('Услуга' | 'Дополнительные затраты'),priceSmeta/summaSmeta(сметная цена — своё, у CT такого нет). - Списки SP: суффикс «ОИД» уже в названиях (
Сравнительная таблица ОИД,Позиции СТ ОИД, ...) — коллизий с CT/KPS нет. SupplierOfferв OID проще, чем у CT: нетcurrencyCode/мульти-валюты в базовых полях (нетdeliveryDistanceKmтоже) — проверить при декомпозиции, не тащить из CT лишнюю доменку.
✅✅ Этап 3 — перекраска (механика) ЗАВЕРШЕНА
Все 13 файлов с Fluent → @tnsu/ui-kit (0 импортов @fluentui в forms/oid/, включая .tsx и .scss —
проверено grep). Порядок: тривиальные (Step2 IconButton-файлы) → простые (AiCommentBlock/
AddNoOfferDialog/Step2CommercialOffers) → Step1WorksInput → сложные (useStep3Approval+InlineApprovalChain,
Step4Approval) → главный контейнер Step3ComparisonTable.tsx (877 строк) → OidCreateForm+Breadcrumbs → токены.
- ComboBox не тянется на ui-kit (нет автокомплита с готовыми опциями) — сделан локальный
Step1WorksInput/ComboField.tsx(свободный ввод + подсказки, паттерн как у поиска участника в Step4Approval: TextInput/native input +<ul>подсказок, debounce уже был в хуке). Используется для «Проект» (серверный поиск черезa.loadProjects) и «Ед. изм.» (локальная фильтрация статичного списка). Эскейп-хэтч: поле «Наименование работ» тоже осталось на native<input>(не TextInput) — нуженonBlur(создаёт ОИД по потере фокуса), которого у ui-kitTextInputнет. TextInput/TextAreaиз ui-kit не имеют пропаclassName— все места, где раньше вешали кастомный класс на инпут (ячейки матрицыofferInput/qtyInput/dataCellName, критерии, тулбар) переведены на обёртку<div className={styles.xxx}><TextInput .../></div>(как в CTMatrixRowView/CriteriaTable).- useStep3Approval/InlineApprovalChain:
NormalPeoplePicker+IPersonaPropsзаменены на паттерн CT —delegateDialog.query/suggestions/selected+ debounce 300мс черезservice.searchUsers,Dialogopen/title/subText/footer вместоDialogType.normal/DialogFooter. - Иконки: Fluent→ui-kit спрайт (Add→plus, Delete/Cancel→trash/close, ChevronLeft→arrow-left, ExcelDocument→document, Forward→chevron-right, Accept→check, Up/Down→arrow-up/arrow-down, Switch→swap, AddFriend→(без иконки, нет аналога), Lightbulb→(без иконки, как у CT)).
- Токены: создан
forms/oid/components/common/_theme.scss— точное зеркало CT/KPS-моста (тот же navy/gold маппинг). Полный проход по hex-литералам (~30 уникальных кодов, ~200+ вхождений) → bulk-sed по всем*.module.scss(кромеOidCreateForm.module.scss, где 2 литерала#1b1a19/#252423сознательно оставлены как есть в.dark-блоке — тот же паттерн, что и у CT, dark-theme не токенизируем). По ходу найдены и убраны 2 мёртвых класса (.winBtn/.winBtnActive, не использовались в разметке).OidCreateForm.module.scssпереписан по образцуCtCreateForm.module.scss(root/content/dark), FluentReferences.scss-импорт и мёртвые классы.welcome/.welcomeImage/.linksубраны. - ✅ Build+workbench-тест перекраски пройден пользователем. По итогам визуальной проверки — 2 доп. фикса:
- Билд-гоча: SPFx typings-генератор для scss выкидывает из
IExportStylesпустые CSS-правила (.class {}без объявлений) — TS2339 "Property does not exist", хотя класс использован в TSX. Столкнулись дважды: (1).addRowBtnв Step4Approval.module.scss забыт при переносе JSX-паттерна из CT (там кнопки стилизовались инлайново); (2) при удалении заливки.extraRow(см. ниже) правило стало пустым — пришлось оставить сbackground: transparent;вместо полного удаления класса. Написан bash/awk-скрипт для проверки styles.X (tsx) ↔ определение класса (scss) по всему дереву — полезно на будущее при переносе JSX между формами. - Цвет:
.colSel/.thSupplierSel(подсветка выбранного поставщика) были на$warn-bg— заменены наrgba($gold-500, 0.02), как у CT..extraRow(доп. затраты) — заливка$warn-bgубрана полностью (толькоbackground: transparent, класс сохранён пустым не может — см. гочу выше).
- Билд-гоча: SPFx typings-генератор для scss выкидывает из
- ✅✅ Финальное дробление render
Step3ComparisonTable.tsxЗАВЕРШЕНО (877→138 строк контейнер), по точному образцу CT:ComparisonMatrix.tsx(228, вся<table>)/MatrixRowView.tsx(258, обычная позиция+bundle, с доменной ячейкой «Группа» rowSpan — которой нет у CT)/ExtraPositionRow.tsx(123, доп.затраты, без bundle)/AddExtraRow.tsx(73)/CriteriaTable.tsx(125). Логика не трогалась — чистая перекладка JSX, единый пропmatrix: Step3Matrix(уже был экспортирован из useStep3Matrix.ts). Порядок строк в<tbody>сохранён точно: позиции по группам →AddExtraRow→ subtotal обычных → extra-позиции → subtotal доп.затрат → grand total. Проверка styles.X↔scss после дробления — чисто,@fluentui— 0 вхождений. - ✅ Build + беглый workbench-прогон (Шаг 1→4) подтверждён пользователем после дробления — без регрессий.
✅✅✅ ФОРМА OID ПОЛНОСТЬЮ ЗАВЕРШЕНА
Этап 1 (перенос) + Этап 2 (декомпозиция логики) + Этап 3 (перекраска на @tnsu/ui-kit) + финальное дробление render — всё сделано и подтверждено сборкой/workbench. OID теперь на одном уровне готовности с CT. Три формы портала (CT/KPS/OID) в состоянии Fluent→ui-kit переведены полностью.