diff --git a/docs/MIGRATION-CHECKLIST.md b/docs/MIGRATION-CHECKLIST.md index 1f4ad02..74779e9 100644 --- a/docs/MIGRATION-CHECKLIST.md +++ b/docs/MIGRATION-CHECKLIST.md @@ -89,13 +89,15 @@ - [ ] Модалка: «Название *» (обязательно, Enter — сохранить), «Родительский объект» (select только при правке Раздела: узлы уровня 0, сортировка ru, без себя и потомков), «Проект» (read-only) - [ ] Заголовки модалки: «Добавить Объект-пятно / Раздел / Вид работ», «Редактировать …», «— (корневой)» - [ ] Удаление: подтверждение с числом дочерних, «Это действие нельзя отменить.» -- [ ] Drag & drop (только `canEdit`): до / внутрь / после, запрет на себя и потомков, ошибка «Не удалось переместить: …» +- [-] ~~Drag & drop~~ — **убрано по решению 2026-10-16** (неудобно, зона «внутрь» не работала, лишние случайные переносы). Родителя Раздела меняют через «Редактировать». - [ ] «Работы» → раздел Работы с `objectId` - [ ] После любой операции дерево перезагружается - [ ] Список «Объекты»: `Title`, `project` (текст = название проекта), `parent` (self-lookup) - [ ] Права: «Доступы» `projectId`/`userId` → `edit0` -🐞 Перетаскивание меняет только родителя, порядок не сохраняется. 🐞 Удаление узла не удаляет детей. 🐞 Зона сброса «внутрь» нулевой высоты — на практике узел можно бросить только «до» / «после» (перенесено как есть). +🐞 Удаление узла не удаляет детей. + +ℹ️ Экспорт падает с 500 на стороне `api.tnsu.kz`: «Failed to acquire Graph token … SPO_TENANT_ID, SPO_CLIENT_ID, SPO_CERT_PATH, SPO_CERT_PASS» — у API не настроен доступ к Graph (проверка 2026-10-16). Наш запрос доходит до API корректно; чинится на сервере. **Сознательные отличия от objects:** - `alert()` → сообщение `Alert` на странице (ошибки переноса, удаления, экспорта) или в модалке (ошибка сохранения). Тексты те же. @@ -119,7 +121,7 @@ - [ ] Навигационное свойство родителя определяется через `EntityDefinitions` (fallback `cree9_parentid`) - [ ] Проект находится подъёмом по родителям объекта до непустого `project` -🐞 Путь объекта (`#headerTitle`) не выводится. 🐞 Нет проверки «начало ≤ окончание». 🐞 Отступы в списке «Родительская работа» — обычные пробелы, браузер их схлопывает (как было). +🐞 Путь объекта (`#headerTitle`) не выводится. 🐞 Отступы в списке «Родительская работа» — обычные пробелы, браузер их схлопывает (как было). **Сознательные отличия от works:** - `alert()` → сообщение `Alert`: ошибка сохранения — в модалке, ошибки удаления — над таблицей. Тексты те же. @@ -127,6 +129,8 @@ - PATCH и DELETE идут через тот же AAD-клиент Dataverse, а не через `fetch` с токеном вручную; заголовки те же. - Адрес сайта — настройка «URL сайта SharePoint» (раньше `pageContext.web.absoluteUrl`), Dataverse — настройка «URL Dataverse»; имя таблицы — константа (раньше настройка `worksTable`). - Таблица — `DataTable` из ui-kit; меню ⋮ — общее меню из `pages/_tree`. +- 🔧 **Исправлено при проверке T-WRK (2026-10-19):** дату окончания нельзя поставить раньше даты начала — календарь не даёт, а при сохранении ошибка «Дата окончания не может быть раньше даты начала» (в старом коде проверки не было). +- ✨ **Улучшено по просьбе (2026-10-19):** «Ед. изм.» — список с поиском: можно начать печатать, Enter выбирает первое найденное. Выбрать можно только значение из справочника, как и раньше. --- @@ -268,4 +272,4 @@ - ✅ Схема списка **«Доступы»** проверена на сайте (2026-10-16): `project` — Lookup на «Проекты», `user` — Person, права `read` / `edit0` / `aproove`. SharePoint отдаёт к ним `projectId` / `userId` — поэтому оба способа из старых репозиториев (`project/Id` + `$expand` и `projectId` / `userId`) обращаются к одним и тем же полям. Расхождения нет. - ✅ Схема списка **«Объекты»** проверена на сайте (2026-10-16): `parent` — Lookup («Объект»), SharePoint отдаёт к нему `parentId` — чтение через `parent/Id` и запись через `parentId` обращаются к одному полю. `project` — Text (название проекта), как и ожидает код. -- ℹ️ В «Объектах» есть поля `level` (Number) и `skipLevelUpdate` (Boolean), которые не писал ни один старый репозиторий — вероятно, уровень проставляет поток Power Automate. Новое приложение их тоже не трогает. При проверке T-OBJ посмотреть, что у созданных объектов `level` проставился. +- ✅ В «Объектах» есть поля `level` (Number) и `skipLevelUpdate` (Boolean), которые не писал ни один старый репозиторий. Проверено 2026-10-16: `level` пустой и у старых настоящих объектов — поле никто не заполняет и ни на что не влияет. Новое приложение его тоже не трогает. diff --git a/docs/TEST-PLAN.md b/docs/TEST-PLAN.md index b651d3d..68260fc 100644 --- a/docs/TEST-PLAN.md +++ b/docs/TEST-PLAN.md @@ -103,7 +103,7 @@ - [ ] ✍️ **T-OBJ-05** Меню ⋮ у «Корпус 1» → «Добавить объект» → «Раздел» «Фундамент». У «Фундамент» → «Вид работ» «Бетонирование». У вида работ нет пункта «Добавить объект». - [ ] ✍️ **T-OBJ-06** Пустое название → сообщение «Поле обязательно…»; Enter сохраняет. - [ ] ✍️ **T-OBJ-07** Редактировать «Фундамент» → сменить родителя (нужен второй объект-пятно «Корпус 2»). -- [ ] ✍️ **T-OBJ-08** Перетащить объект на другой → родитель сменился; на своего потомка — запрещено. +- [-] **T-OBJ-08** Перетаскивание — **убрано** (решение 2026-10-16). Проверить, что строку дерева мышкой больше не перетащить. - [ ] ✍️ **T-OBJ-09** Удалить объект с детьми → подтверждение с числом детей; дети остаются (🐞 так было). - [ ] **T-OBJ-10** «Экспорт» → скачался `objects-{id}.xlsx`, содержимое как у старой страницы. - [ ] **T-OBJ-11** Меню ⋮ → «Работы» → открылся раздел «Работы» этого объекта. diff --git a/src/webparts/stroyBot/pages/works/WorkModal.tsx b/src/webparts/stroyBot/pages/works/WorkModal.tsx index 5c47dd9..fdff3ba 100644 --- a/src/webparts/stroyBot/pages/works/WorkModal.tsx +++ b/src/webparts/stroyBot/pages/works/WorkModal.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { Alert, Button, Dialog, FormField, Select, TextInput } from '@tnsu/ui-kit'; import { ITree } from '../_tree/treeRules'; import { FormInput } from '../_tree/FormInput'; +import { SearchableSelect } from '../../shared/components/SearchableSelect'; import { IWorkEditor } from './useWorkEditor'; import { parentOptions, unitOptions } from './worksRules'; import { IWork } from './worksTypes'; @@ -60,12 +61,15 @@ export const WorkModal: React.FC = ({ editor, tree, units, objectTitle }) /> - setIsOpen(true)} + onChange={(event) => { + setQuery(event.target.value); + setIsOpen(true); + }} + onKeyDown={onKeyDown} + /> + {value && !isOpen && ( + + )} + + + {isOpen && ( +
+ {filtered.length === 0 &&
{notFoundText}
} + {filtered.map((option) => ( +
{ + event.preventDefault(); + pick(option); + }} + > + {option.label} +
+ ))} +
+ )} + + ); +}; diff --git a/src/webparts/stroyBot/shared/utils/filterOptions.test.ts b/src/webparts/stroyBot/shared/utils/filterOptions.test.ts new file mode 100644 index 0000000..5a98d82 --- /dev/null +++ b/src/webparts/stroyBot/shared/utils/filterOptions.test.ts @@ -0,0 +1,18 @@ +import { filterOptions } from './filterOptions'; + +const units = ['м', 'м2', 'м3', 'кг', 'Комплект'].map((label) => ({ value: label, label })); + +describe('filterOptions', () => { + it('пустой запрос — весь список', () => { + expect(filterOptions(units, ' ')).toHaveLength(5); + }); + + it('подстрока без учёта регистра', () => { + expect(filterOptions(units, 'м3').map((o) => o.value)).toEqual(['м3']); + expect(filterOptions(units, 'КОМ').map((o) => o.value)).toEqual(['Комплект']); + }); + + it('ничего не найдено — пустой список', () => { + expect(filterOptions(units, 'литр')).toEqual([]); + }); +}); diff --git a/src/webparts/stroyBot/shared/utils/filterOptions.ts b/src/webparts/stroyBot/shared/utils/filterOptions.ts new file mode 100644 index 0000000..54555f4 --- /dev/null +++ b/src/webparts/stroyBot/shared/utils/filterOptions.ts @@ -0,0 +1,8 @@ +import { ISelectOption } from '@tnsu/ui-kit'; + +/** Поиск по подписи: подстрока без учёта регистра. Пустой запрос — весь список. */ +export function filterOptions(options: ISelectOption[], query: string): ISelectOption[] { + const normalized = query.trim().toLowerCase(); + if (!normalized) return options; + return options.filter((option) => option.label.toLowerCase().indexOf(normalized) !== -1); +}