объединение CT, KPS, OID и дашборда в один портал

Перенос, декомпозиция и перекраска форм CT/KPS/OID на @tnsu/ui-kit,
config-driven дашборд-реестр по трём типам документов. Из отслеживания
исключены артефакты сборки (lib-commonjs/jest-output/release) — забыты
в .gitignore ранее.
This commit is contained in:
Тимур Городецкий
2026-08-28 16:21:34 +05:00
parent baf8fa357d
commit 109053e3af
210 changed files with 27016 additions and 11730 deletions
+174
View File
@@ -0,0 +1,174 @@
# CT — перенос в ct-forms-portal (Фаза 3)
Порядок (утверждён): **Этап 1 перенос 1:1 → Этап 2 декомпозиция под структуру KPS → Этап 3 перекраска на ui-kit.**
Правило: сохранить 100% поведения; build после каждого этапа. Эталон структуры — `forms/kps/`.
Связано: `docs/REPAINT-MAP.md` (перекраска), `docs/BEHAVIORS.md`.
Источник: `ct-create-form/src/webparts/ctCreateForm/`. CT = 6953 стр, god-компоненты.
## Этап 1 — перенос 1:1 — ✅ ЗАВЕРШЁН (heft build зелёный)
1. ✅ Скопированы `components/` и `services/` → `forms/ct/{components,services}/` (17 файлов, 1:1).
2. ✅ Правки для сборки в портале (минимум, поведение не менялось):
- `CtService`: конструктор `(context, siteUrl?)`, `baseUrl = siteUrl ?? absoluteUrl` (как KPS).
`kpFolder` тоже параметризован через `siteUrl` (было жёстко на `context.pageContext.web.serverRelativeUrl`).
- `ICtCreateFormProps` + `siteUrl?`; `CtCreateForm` → `new CtService(context, siteUrl)`.
- Web-part-специфичных зависимостей (loc strings) в components/services не было — ничего убирать не пришлось.
3. ✅ Обвязка портала:
- `ctDocumentConfig.ts`: `inPortal: true` + `buildInPortalParams: (row) => ({ [row.status==='Черновик'?'ctId':'ctid']: row.id })`
(сверено с `useCtForm.ts` — читает именно `ctId`/`ctid`, совпало 1:1). siteUrl уже `/sites/sed`.
- `CtFormsPortal.tsx`: ветка `form==='ct'` → `<CtCreateForm .../>` + «← К реестру» (по образцу KPS-ветки).
4. ✅ `heft build` зелёный.
**Открытие при переносе:** CT уже частично зависел от `@tnsu/ui-kit@^0.4.0` — 3 файла
(`Step1PositionSelection.tsx`, `Step2CommercialOffers.tsx`, `CurrencyRatesForm.tsx`) уже используют
`Button`/`Alert` из кита. API совпадает 1:1 с текущим `0.11.0` (variant="secondary"/"accent", Alert
variant="err") — конфликтов не было, работает как есть. Ускорит Этап 3.
## Этап 2 — декомпозиция (карта резки god-компонентов)
**Статус:** [x] Step2CommercialOffers ✅ | [x] Step4Approval ✅ | [~] Step3ComparisonTable (в работе)
### [~] Step3ComparisonTable (2217) — В РАБОТЕ, по чекпоинтам
- ✅ **CP1 согласование** (build ок): `useStep3Approval.ts` (голосование/делегат, 1:1) + `InlineApprovalChain.tsx`
(вью цепочки + диалоги решение/делегат + баннер notifyWarning; хелперы APPROVAL_STATUS_*). Контейнер 2217→1918.
- ✅ **CP2 AI/PDF** (build ок): `useAiComment.ts` (aiComment/isAiLoading/aiError/isPdfLoading/showAiPromptDialog +
handleAiComment/handleGeneratePdfReport/handleDeleteAiComment + loadCtComment-effect + decodeHtmlEntities,
buildPayload передаётся аргументом из контейнера — матричные данные не копируются в хук) + `AiCommentBlock.tsx`
(только блок отображения, диалог-промпт остался в контейнере — как у KPS). PDF-ошибка идёт через колбэк
`onPdfError` в общий `saveError` (сохранили родной баннер).
- ✅ **CP3 хелперы/типы** (build ок): типы (ExtraSupplier/ExtraPosition/PositionRow/CellEdit/EditableOffers) →
`matrixTypes.ts`; константы (SUPPLIER_STATUS_OPTIONS/DELIVERY_TYPE_OPTIONS) + чистые ф-ции
(formatKzt/Num/Qty/round2Str/getSubCols/computeOffer/buildOfferKey) → `matrixHelpers.ts`.
`extraIdCounter` (мутабельный) оставлен в контейнере — уедет вместе с CP4.
- **Контейнер после CP1-3: 2217 → 1735 строк (−22%).**
- ✅ **CP4 ядро матрицы** (build ок): весь стейт/рефы(~15)/эффекты(loadCriteria/loadOffers/refs-sync/beforeunload/
unmount-persist)/мемо(suppliersWithOffer/allPositions/supplierCurrencies/computedOffers/minTotalByPosition/
recommendedSupplierId)/все update*/add*/delete*-хендлеры/handleSave → `useStep3Matrix.ts` (856, перенос
ВЕРБАТИМ). В контейнере — **деструктуризация** результата, поэтому render (984) НЕ менялся. `CriteriaEntry`
переехал в matrixTypes (общий). approval+ai хуки остались в контейнере (берут значения из деструктуризации).
- **Контейнер: 2217 → 984 строки (−56%). Логика матрицы вся в хуке. Этап 2 для CT ЗАВЕРШЁН.**
- ✅ **CP4 happy-path проверен в workbench**: полный проход по всем 4 шагам (создание СТ → загрузка КП → матрица →
согласование) прошёл без ошибок. Основной риск (грубый регресс/краш) снят.
⚠️ Не проверены детально: bundle-строки (комплект), перевод ед. изм. (тумблер), доп.расходы/логистика,
F5 (beforeunload-persist), критерии/AI-комментарий, делегат/замена в согласовании. Смотреть при случае,
не блокирует дальнейшую работу.
- ⏭️ **CP4 матрица-ядро** (САМЫЙ РИСК): ~15 рефов + эффекты (loadCriteria/loadOffers/beforeunload/unmount-persist) +
мемо (suppliersWithOffer/allPositions/supplierCurrencies/computedOffers/minTotalByPosition/recommendedSupplierId) +
все update*/add*/delete*-хендлеры + handleSave → `useStep3Matrix.ts` (перенос ВЕРБАТИМ, не переписывать!).
⚠️ Клубок рефов; компилятор не ловит регресс поведения. ЖЕЛАТЕЛЬНО делать при возможности workbench-теста
(после провижининга списков). Рендер таблицы (thead/tbody/итоги/критерии) дробить на ComparisonMatrix/
MatrixRowView/OfferCells/CriteriaTable/AddExtraRow — на Этапе 3 вместе с перекраской.
Метод: чисто механический перенос блоков (логику не трогать), Fluent оставляем (перекраска — Этап 3),
build после каждого god-компонента.
**Уточнённая граница Этап2/Этап3:** Этап 2 = вынос ЛОГИКИ (хуки/persist), «раз-god-ивание». Финальное
дробление оставшейся ВЬЮХИ на под-компоненты (диалоги/списки) + размеры <200 — на Этапе 3 вместе с
перекраской (диалоги всё равно → ui-kit Dialog), чтобы не трогать файлы дважды.
### ✅ Step4Approval (886) — СДЕЛАНО (build ок)
886 → `useStep4Approval.ts` (вся логика: chain-ops, add/delete/replace, handleActionConfirm, send/incore/restart,
производные флаги + удобные методы selectAddSuggestion/cancelAddRow/openReplace/…) + view `Step4Approval.tsx`
(~370, только разметка + helpers STATUS_LABELS/getStatusClass/formatDate; Fluent). Вьюху добьём на Этапе 3.
### ✅ Step2CommercialOffers (597) — СДЕЛАНО (build ок, 2 чекпоинта)
597 → контейнер `Step2CommercialOffers.tsx` (~155) + `kpTypes.ts` + `useKpProcessing.ts` (конвейер Syntex/Flow)
+ `step2Persist.ts` (handleSave→функция, возвращает patch) + вьюхи `SuppliersWithoutKp.tsx` (владеет add-стейтом)
/ `UploadedKpList.tsx`. Одно семантич. уточнение: в persist `formDataRef.current`→`formData` (в момент save совпадают).
### Step3ComparisonTable.tsx (2217) → по образцу `Step3BuildCt/*` + отдать approval в Step4
| Секции CT (строки) | Куда (аналог KPS) |
|---|---|
| 25–179 типы/хелперы/формат | `types/domain.ts`, `utils/format.ts`, `useStep3Matrix.ts` |
| 196–435 стейт матрицы | `useStep3Matrix.ts` |
| 285–394 approval-хендлеры | → Step4 `useStep4Approval.ts` |
| 396–405, 947–1017 AI/PDF | `useAiComment.ts` + `AiCommentBlock.tsx` |
| 439–604 useEffect загрузка/persist | `step3Persist.ts` + `useStep3Matrix.ts` |
| 606–706 useMemo производные | `useStep3Matrix.ts` |
| 708–789 редактируемые ячейки | `OfferCells.tsx` |
| 791–945 хендлеры строк | `AddExtraRow.tsx` + `useStep3Matrix.ts` |
| 1019–1193 handleSave | `step3Persist.ts` |
| 1197–1239 тулбар + тумблер ед.изм. | `Step3BuildCt.tsx` (+ доменное `unitConversion`) |
| 1241–1337 thead | `ComparisonMatrix.tsx` |
| 1339–1806 tbody + итоги | `MatrixRowView.tsx` + `OfferCells.tsx` + `ComparisonMatrix.tsx` |
| 1807–1864 инлайн-добавление | `AddExtraRow.tsx` |
| 1865–2029 критерии + AI + PDF | `CriteriaTable.tsx` + `AiCommentBlock.tsx` |
| 2030–2114 инлайн-цепочка (Teams) | → Step4 `InlineApprovalChain.tsx` |
| 2115–2211 диалоги (делегирование/решение) | ui-kit `Dialog` в `Step4Approval.tsx` |
### Step4Approval.tsx (886) → `useStep4Approval.ts` + `Step4Approval.tsx` + `InlineApprovalChain.tsx`
(стейт+манипуляции цепочкой 94–352, handleActionConfirm/Send/Incore/Restart 239–474 → хук; render 476–763; диалоги 764–885 → ui-kit Dialog)
### Step2CommercialOffers.tsx (597) → `useKpProcessing.ts`/`kpPipeline.ts`/`kpOffers.ts`/`step2Persist.ts` + view (`Step2LoadKp`,`KpFileList`,`CurrencyRatesForm`,`SuppliersWithoutKp`,`UploadedKpList`)
### CtService.ts (1532) — ввести `siteUrl?` (крит.) + при желании обобщённые CRUD-примитивы как в KPS. Создать `common/*` (StepShell/FormHeader/Pill) + единый `StepProps`.
## Доменные отличия CT (НЕ терять при декомпозиции!)
- Шаг 1 — из **SP-списка** (`fetchRequests(days)`), не из TNSU API (KPS).
- Колонки позиций: **disciplineCode (марка), smrType (SMR), agskCode**, uniqueCode, organization.
- `CT.number` — строковый, `generateCtNumber(projectName)` (per-project).
- `utils/unitConversion.ts` — пересчёт в ед. изм. заявки (тумблер) — нет в KPS.
- `SUPPLIER_STATUS_OPTIONS` (Завод/Дистрибьютор…) — нет в KPS.
- CurrencyRate/SupplierOffer — иные поля (rateToKzt; deliveryCostKzt/additionalCostsKzt/totalWithExtraKzt/pricePerUnitKzt).
- `getCurrentUserCompany()==='astel-k'` → `isRestrictedView` (скрытие цен).
## Этап 3 — перекраска (карта A/B/C из REPAINT-MAP)
Fluent в CT: PrimaryButton/DefaultButton→Button, TextField→TextInput/TextArea, MessageBar→Alert,
IconButton/Icon, Spinner→Spinner, Dialog→ui-kit Dialog, Checkbox→Checkbox, статусы→Pill/Badge.
Особые: **Toggle** (пересчёт ед.изм.) и **NormalPeoplePicker** (делегирование) — прямых аналогов в ките нет:
Toggle → ui-kit Toggle (есть!); people picker → TextInput + searchUsers()+suggestions (как KPS Step4).
**✅✅ Этап 3 ЗАВЕРШЁН (механическая перекраска) — Fluent полностью удалён из CT (0 импортов @fluentui
во всей `forms/ct/`), heft build ожидается пользователем.**
- Все 12 файлов с Fluent переведены на `@tnsu/ui-kit`: RequestsTable/FileUploadArea/UploadedKpList
(Checkbox/IconButton), SuppliersWithoutKp/CurrencyRatesForm/Step1PositionSelection/
Step2CommercialOffers/AiCommentBlock (TextInput/Spinner/Alert/IconButton/FormField),
useStep3Approval/InlineApprovalChain/Step4Approval (делегат/замена — NormalPeoplePicker заменён
на паттерн KPS: query+debounce(300ms)+suggestions+select, `useStep4Approval`/`useStep3Approval`
получили `handleAddQueryChange(val)`/`setDelegateQuery(val)` вместо Fluent-сигнатуры (event, val)),
и главный контейнер `Step3ComparisonTable.tsx` (984 строки, вся матрица/критерии/футер/AI-диалог).
- Правило замен применено: `TextField`→`TextInput`/`TextArea` (multiline→TextArea rows=1, автовысоты
нет — принято как в KPS `CriteriaTable`); без `className`→обёрнуто в `<div>`; `Toggle` без `label`→
обёрнут в `<label>` вручную (span рядом); иконки Fluent→спрайт (Delete→trash, Cancel→close,
Add→plus, Accept/CheckMark→check, ChevronLeft→arrow-left, PDF→document, Forward→chevron-right,
AddFriend/Switch→plus/swap, DoubleChevron*→chevron-left/right повторно, Lightbulb→без иконки).
- SCSS: все `:global(.ms-TextField-*)`-селекторы (таргетили Fluent DOM) заменены на `input`/`textarea`
(таргетят нативный тег внутри обёртки ui-kit — рабочий приём, т.к. хэши классов ui-kit непредсказуемы).
- ⚠️ **Осталось на будущее (сознательно НЕ делали сейчас):** финальное дробление `Step3ComparisonTable.tsx`
render на под-компоненты `ComparisonMatrix`/`OfferCells`/`MatrixRowView`/`AddExtraRow`/`CriteriaTable`
по образцу KPS `Step3BuildCt/*`. Причина отказа сейчас: KPS-хук `useStep3Matrix` даёт чистый API
(`matrix.rows`/`matrix.rateOf`/`matrix.editNumber`...), а CT-хук `useStep3Matrix` (856 строк, CP4)
перенесён ВЕРБАТИМ с сырыми именами (`updateEdit`/`updatePriceKzt`...) и НЕ верифицирован в runtime —
редизайн его публичного API сейчас добавил бы риск поверх уже непроверенного риска. Делать этот шаг
ТОЛЬКО после workbench-теста CP4 (см. ниже), отдельным заходом, вместе с рефакторингом API хука под
форму KPS.
- ✅ **Workbench happy-path пройден** (все 4 шага, создание→согласование, без ошибок) — основной риск CP4+перекраски
снят. Не проверены детально: bundle-строки, Toggle перевода ед.изм., доп.расходы/логистика, критерии/AI,
делегат/замена в согласовании, F5-persist. Смотреть по возможности, не блокирует дальнейшую работу.
- Отдельно найден и починен степпер (`Breadcrumbs.tsx`) — жил вне грепа по `@fluentui` (свой хендрол на
Fluent-синих литералах без Fluent-импорта), переведён на адаптер `Stepper`+`FormHeader`+`Badge` (как KPS).
- ✅✅ **Полный проход по литералам-цветам ЗАВЕРШЁН** (весь набор Fluent-палитры, не только синий):
создан мост `forms/ct/components/common/_theme.scss` (точное зеркало `forms/kps/.../common/_theme.scss`,
тот же маппинг: $accent/$accent-bg/$accent-border/$accent-dark/$accent-hover ← Fluent-синий,
$text/$muted/$muted-2/$border/$border-strong/$head-bg/$hover/$surface ← Fluent-серый,
$ok/$ok-bg/$danger/$danger-bg/$warn-fg/$warn-bg ← Fluent-статусы). Импорт `@import '../common/theme'`
добавлен в 9 файлов (Breadcrumbs, CtCreateForm, RequestsTable, Step1PositionSelection, CurrencyRatesForm,
FileUploadArea, Step2CommercialOffers, Step3ComparisonTable, Step4Approval) — bulk sed-заменой всех
Fluent hex-литералов на семантические переменные (~150+ подстановок). Один найденный баг при массовой
замене: в `CtCreateForm.module.scss` есть SPFx-плейсхолдер темы `"[theme:bodyText, default: #hex]"` —
это НЕ SCSS-код, а строка, которую парсит SPFx-лоадер во время сборки; sed случайно подставил туда
`$text` вместо литерала — исправлено обратно на реальный hex (`#111827`, эквивалент `$gray-900`).
Оставлены как есть (сознательно, вне палитры Fluent): dark-theme цвета (`#1b1a19`/`#252423`),
один `#fefefe` (незначим).
- Отдельно найден и починен степпер (`Breadcrumbs.tsx`) — жил вне грепа по `@fluentui` (свой хендрол на
Fluent-синих литералах без Fluent-импорта), переведён на адаптер `Stepper`+`FormHeader`+`Badge` (как KPS).
- Точечные UX-правки после визуальной проверки пользователем: степпер сужен до `30vw` (обёртка
`.stepperWrap`, прижат влево, откат на 100% ширины ниже `$bp-md`) — применено и в CT, и в KPS;
подсветка «выбран поставщик» (`.colSelected`) переведена с `$warn-bg` (семантически неверно —
«предупреждение») на полупрозрачную золотую заливку `rgba($gold-500, 0.02)`; заливка строк
Логистика/Доп.расходы (`.extraRow`) убрана полностью (были на `$warn-bg`, читались слишком ярко).
## ✅✅✅ ФОРМА CT ПОЛНОСТЬЮ ЗАВЕРШЕНА
Этап 1 (перенос) → Этап 2 (декомпозиция, включая рискованный CP4) → Этап 3 (перекраска компонентов +
полный проход по цветовым токенам + финальное дробление render Step3ComparisonTable) — всё сделано,
собрано и подтверждено пользователем в workbench (полный проход по 4 шагам после каждого крупного этапа).
Дальше по плану — **OID**, тем же маршрутом: перенос 1:1 → декомпозиция под структуру KPS → перекраска.
+164
View File
@@ -0,0 +1,164 @@
# 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 — ✅ ЗАВЕРШЁН
1. ✅ Скопированы `components/` и `services/` → `forms/oid/{components,services}/` (17 файлов, 1:1, без правок логики).
2. ✅ **`OidService.ts` НЕ ТРЕБУЕТ параметризации siteUrl** (в отличие от Ct/KpsCtFormService!): модуль-константа
`SITE_URL = "https://tnsukz.sharepoint.com/sites/sed"` уже прописана как раз на целевой сайт портала —
OID изначально жил на /sites/sed, миграции сайта (в отличие от CT/KPS) не было. Все ~15 обращений к SP-листам
используют эту константу напрямую (не `this.baseUrl`), so `context.pageContext.web.absoluteUrl` тоже будет
верно резолвиться при работе внутри портала. Решение: оставить `OidService.ts` полностью нетронутым —
любая правка (даже добавление неиспользуемого `siteUrl?`) была бы риском без пользы.
3. ✅ `IOidCreateFormProps.ts`: `hasTeamsContext` был объявлен, но нигде не читался (мёртвый пропс) —
сделан опциональным (`hasTeamsContext?: boolean`), чтобы порталу не нужно было его прокидывать.
`siteUrl?` НЕ добавлен (сервису он не нужен — см. п.2, не добавляем неиспользуемый пропс).
4. ✅ Обвязка портала:
- `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) — правок не потребовалось.
5. ✅ `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-guard
`if (!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`≈CT `handleSendToIncore`, методы сервиса
называются иначе: `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-kit `TextInput` нет.
- **`TextInput`/`TextArea` из ui-kit не имеют пропа `className`** — все места, где раньше вешали кастомный
класс на инпут (ячейки матрицы `offerInput`/`qtyInput`/`dataCellName`, критерии, тулбар) переведены на
обёртку `<div className={styles.xxx}><TextInput .../></div>` (как в CT `MatrixRowView`/`CriteriaTable`).
- **useStep3Approval/InlineApprovalChain**: `NormalPeoplePicker`+`IPersonaProps` заменены на паттерн CT —
`delegateDialog.query/suggestions/selected` + debounce 300мс через `service.searchUsers`, `Dialog`
open/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), Fluent
`References.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`, класс сохранён пустым не может — см. гочу выше).
- ✅✅ **Финальное дробление 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 переведены полностью.
+126
View File
@@ -0,0 +1,126 @@
# REPAINT-MAP — перекраска форм CT/OID/KPS на @tnsu/ui-kit
Живая карта работ по переводу форм портала на общий UI-пакет. Ведётся по ходу.
Связано: `docs/BEHAVIORS.md` (инвентарь поведения форм).
## Принцип
Красим **один раз** в ui-kit через токены, утверждаем в Storybook. В проекте —
только композиция + узкодоменное. «Все цвета в ui-kit» = ui-kit это источник правды
по токенам; потребитель (ct-forms-portal) держит **зеркало** `src/styles/_design-tokens.scss`
(heft не резолвит scss из node_modules), формы красятся из него.
Компоненты не «для KPS», а «для семейства СТ-форм»: API проектируем сразу под 3 формы
(CT/OID используют те же Fluent-примитивы). Правило аддитивности: не хватает пропа для
CT/OID → добавляем **опциональный** (не ломает KPS, semver minor/patch). Escape hatch:
не ложится на 3 формы без уродства → оставляем локальным.
## Архитектура токенов
```
@tnsu/ui-kit src/tokens/_tokens.scss ← ИСТОЧНИК ПРАВДЫ (navy/gold, radius, shadow, bp, миксины)
│ (копия при изменении)
▼
ct-forms-portal src/styles/_design-tokens.scss ← зеркало для SPFx-сборки
▲ (импортят свои .scss: Dashboard, CtFormsPortal, …)
│
forms/kps/components/common/_theme.scss ← МОСТ: семантика формы ($accent,$ok,$supplier-bg…)
▲ → бренд-токены. Имена сохранены.
│ @import '../common/theme'
12 доменных *.module.scss шагов ← перекрашиваются БЕЗ своих правок
```
## Три корзины (по инвентарю KPS-формы)
### A. Уже есть в ui-kit → заменить импорт (мгновенная перекраска)
| Было (Fluent/локальное) | Стало (ui-kit) |
| --- | --- |
| `TextField` | `TextInput` / `TextArea` / `MoneyInput` (числа) |
| `DefaultButton` / `PrimaryButton` | `Button` (variant secondary/primary) |
| низ `StepShell` (кнопки Назад/Далее) | `FormActions` |
| `MessageBar` (+Type) | `Alert` (map severity) |
| `Icon` | `Icon` / `IconSprite` |
| локальный `Pill` | `Badge` |
| локальный `Breadcrumbs` | `Stepper` |
| локальный `common/FormHeader` | `FormHeader` |
### B. Создать в ui-kit (нужны и CT/OID)
| Новый компонент | Драйвер / где нужен |
| --- | --- |
| `Checkbox` (+ строчный вариант) | RequestRow, RequestLines, Step1ReadOnlyView (Шаг 1) |
| `Spinner` / `Loader` | KpFileList, CurrencyRatesForm, AiCommentBlock, Step4Approval |
| `IconButton` | UploadedKpList, AiCommentBlock, CurrencyRatesForm, Step4Approval |
| `Dialog` / `Modal` (generic, с телом+полями) | Step3BuildCt, Step4Approval, InlineApprovalChain (ConfirmDialog не тянет) |
| `Link` | UploadedKpList (скачать) |
### C. Доменное — остаётся в проекте (красим токенами + примитивами ui-kit)
- `Step3BuildCt/ComparisonMatrix`, `MatrixRowView`, `OfferCells` — матрица сравнения СТ.
- `Step3BuildCt/CriteriaTable` — таблица критериев.
- `common/ApprovalChain`, `Step4Approval/InlineApprovalChain` — цепочки (пилюли из `Badge`).
- `Step2LoadKp/KpFileList`, `UploadedKpList` — список файлов КП (статусы обработки Syntex/поток).
Загрузку можно частично на `FileDropzone` + `AttachmentList`.
## Семантические токены формы (мост `_theme.scss`)
| Переменная | Бренд-токен | Было (Fluent) |
| --- | --- | --- |
| `$accent` | `$navy-800` | #0078d4 |
| `$accent-bg` | `$navy-50` | #eff6fc |
| `$accent-border` | `$navy-100` | #c7e0f4 |
| `$accent-dark` | `$navy-950` | #004578 |
| `$accent-hover` | `$navy-700` | #106ebe |
| `$ok` / `$ok-bg` | `$green-600` / `$green-50` | #107c10 / #dff6dd |
| `$danger` / `$danger-bg` | `$red-500` / `$red-50` | #a4262c / #fde7e9 |
| `$warn-fg` / `$warn-bg` | `$amber-700` / `$amber-50` | #7d5900 / — |
| `$text` / `$muted` / `$muted-2` | `$gray-900` / `$gray-500` / `$gray-400` | #323130 / #605e5c / #a19f9d |
| `$border` / `$border-strong` | `$gray-200` / `$gray-300` | #edebe9 / #c8c6c4 |
| `$head-bg` / `$hover` / `$surface` | `$gray-50` / `$gray-100` / `$white` | #faf9f8 / #f3f2f1 / #fff |
| `$supplier-bg` / `$supplier-border` | `$gold-50` / `$gold-100` | #fffdf0 / #f2e9b8 |
## Роадмап и статус
- [x] **Фаза 0 — Токены.** `$amber-50` в ui-kit; зеркало `src/styles/_design-tokens.scss`;
мост `forms/kps/components/common/_theme.scss` → бренд. heft build ок.
_Осталось в рамках гигиены: перевести `Dashboard.module.scss`/`CtFormsPortal.module.scss`
(шелл) с хардкодов на зеркало — не блокер._
- [x] **Фаза 1 — Примитивы (корзина B) собраны в ui-kit** (build ок, 68 модулей):
`Checkbox` (checked/indeterminate/disabled/readOnly/label/ariaLabel),
`Spinner` (xSmall/small/medium/large, label, block),
`IconButton` (icon: IconName, title, danger, size),
`Dialog` (open/title/subText/onDismiss/footer/width, Esc+overlay close),
`Link` (href/onClick/target/download).
В спрайт добавлены иконки `arrow-up`, `arrow-down`, `swap`, `refresh`.
_Осталось: Storybook-утверждение дизайна → релиз ui-kit (skill publish-ui-kit) →
`npm i @tnsu/ui-kit@latest` в ct-forms-portal._
- [~] **Фаза 2 — Перекраска KPS** (по шагам; heft build после каждого):
- [x] **Шаг 1** (`Step1SelectPositions`): Checkbox/Icon → ui-kit (+ariaLabel), `Pill`→адаптер над
`Badge` (перекрасил все 6 мест сразу), `#ffffff`→`$surface`. build ок.
- [x] **Шаг 2** (`Step2LoadKp/*`): MessageBar→Alert, Spinner/IconButton/Link/Icon→ui-kit,
DefaultButton→Button, TextField→TextInput (в обёртке для className), литералы→токены. build ок.
_Косметика: TextInput без suffix/ariaLabel (KZT→placeholder); dropzone оставлен доменным._
_Долг: перед Шагом 4 добавить в Alert вариант `info` (MessageBarType.info) + переиздать ui-kit._
- [x] **Шаг 3** (`Step3BuildCt/*`): AiCommentBlock (IconButton/Spinner/Alert), AddExtraRow +
supplier toolbar (Button/TextInput), OfferCells/CriteriaTable (borderless TextInput/TextArea
в обёртке `.input`), Dialog AI-промпта (ui-kit Dialog + footer). Матрица/критерии — домен,
все литералы→токены ($accent-border/-bg/-dark/-hover, $supplier-bg, $warn-bg/-fg, $surface, $danger).
Микро-кнопки в ячейках (×/+/удалить поставщика) оставлены доменными. build ок.
Потребовало ui-kit **0.11.0**: TextInput/TextArea `borderless`+`ariaLabel`+`onKeyDown`+`autoFocus`,
Alert вариант `info`, иконка `info`.
- [x] **Шаг 4** (`Step4Approval.tsx` + `InlineApprovalChain.tsx`): MessageBar→Alert (ok/err/info/warn),
IconButton (arrow-up/down/trash/swap), Button (approve=primary, reject=danger, send/refresh с иконками),
Dialog×2 (решение/замена) → ui-kit Dialog+footer, TextField→TextInput/TextArea, литералы→токены ($surface,$sh-2).
Цепочки — домен, пилюли через Pill→Badge. build ок.
- [x] **common**: `StepShell` кнопки→`Button` (split-раскладка сохранена), `common/FormHeader`→адаптер
над ui-kit `FormHeader` (right=Pill→Badge), `Breadcrumbs`→адаптер над `Stepper` (адаптивный),
убран `@fluentui References.scss` из `KpsCtForm.module.scss`, #a4262c→$danger. Осиротевшие
`FormHeader.module.scss`/`Breadcrumbs.module.scss` удалены.
- ✅ **Fluent полностью удалён из KPS-формы (0 импортов @fluentui). Фаза 2 завершена, build ок.**
- _KPS = двойной эталон (структура + примитивы)._
- [ ] **Фаза 3 — CT, затем OID.** НЕ только перекраска: **декомпозиция god-компонентов**
(`Step3ComparisonTable.tsx` ~2200–2500 стр) в структуру KPS + сборка из ui-kit +
сохранить 100% поведения. Инвентарь поведения → резка → build/workbench на каждом шаге.
## Заметки / риски
- Декомпозиция CT/OID — самая рискованная часть; KPS = готовый чертёж (тот же скелет
Step1→Step2→Step3 матрица→Step4; отличия доменные: CT disciplineCode/SMR/agskCode, OID parentGroup/priceSmeta).
- При изменении токенов в ui-kit — синхронизировать зеркало `src/styles/_design-tokens.scss`.