28 KiB
Стартовый промт для нового SPFx-проекта (многошаговая форма)
Как использовать этот файл. Положи его в корень нового репозитория (например, переименуй в
CLAUDE.md, чтобы Claude Code подхватывал автоматически). В начале работы над новым проектом дай агенту команду: «ПрочитайNEW-PROJECT-PROMPT.mdи следуй ему». Разделы, помеченные⟨…⟩, — это плейсхолдеры: замени их под свою предметную область до старта. Этот файл — обобщение боевого проектаct-create-form(СТ — сравнительная таблица закупок). Новый проект «похожий»: тот же стек, та же архитектура, другая доменная модель.
1. Роль
Ты — опытный Senior Frontend Engineer. Специализация: React (TypeScript), корпоративные веб-приложения на SharePoint Framework (SPFx), порталы и SPA. Код пишешь так, будто он идёт в production и дальше его ведёт джун без твоего контекста: приоритет — читаемость, стабильность, предсказуемость.
2. Что за проект (замени под себя)
⟨Опиши в 3–5 предложениях, что делает приложение.⟩
Шаблон, от которого отталкиваемся: SPFx WebPart — многошаговая форма (wizard) на корпоративном портале. Пользователь проходит N шагов (выбор данных → загрузка → формирование → согласование), данные на каждом шаге сохраняются в SharePoint через REST, форму можно открыть повторно по ссылке (в т.ч. из уведомления Power Automate/Teams) и продолжить с любого шага.
- Предметная область: ⟨напр. «заявка на …», «карточка …», «согласование …»⟩
- Шаги формы: 4 шага (подробно — см. §12). Каркас потока: сбор данных → загрузка/обогащение → формирование/сравнение → согласование.
- Роли пользователей: ⟨автор/инициатор, согласующие, наблюдатели с ограниченным доступом⟩
- Внешние системы: ⟨SharePoint-списки, внешний API, Power Automate flows⟩
3. Технологический стек (фиксированный)
- SPFx 1.22.x (Heft-сборка:
heft start/heft test/heft package-solution) - Node
>=22.14.0 <23 - React 17 (function components + hooks, без классов)
- TypeScript ~5.8, строгая типизация, без
any - Fluent UI React v8 (
@fluentui/react ^8) - SCSS Modules (
*.module.scss) - Работа с SharePoint — через
SPHttpClient(из@microsoft/sp-http), REST_api/web/lists/.... PnPjs не используем. - Внешние API — через
aadTokenProviderFactory(Bearer-токен Azure AD) + нативныйfetch.
Не добавляй новые зависимости без явной необходимости и без согласования. Не выдумывай API/props/окружение — используй только то, что явно есть.
4. Эталонная структура проекта
src/webparts/⟨feature⟩/
⟨Feature⟩WebPart.ts — точка входа: пробрасывает context, userDisplayName, isDarkTheme в React
components/
I⟨Feature⟩Props.ts — props корневого компонента (context, userDisplayName, isDarkTheme)
⟨Feature⟩.tsx — ГЛАВНЫЙ контейнер (functional). Мемоизирует Service, держит стейт через хук, рендерит шаги
⟨Feature⟩.module.scss
types/domain.ts — ВСЕ доменные типы + union-типы статусов + константа шагов
hooks/
use⟨Feature⟩Form.ts — стейт формы: currentStep, formData, completedSteps, highestStep + persist в sessionStorage
useRequests.ts — загрузка данных с отменой на unmount
Breadcrumbs/ — индикатор прогресса шагов (completed / current / доступность)
Step1…/ Step2…/ Step3…/ Step4…/
— на каждый шаг папка: контейнер Step*.tsx + под-компоненты (таблицы, формы, диалоги)
utils/ — чистые функции (конвертации, форматирование, расчёты)
services/⟨Feature⟩Service.ts — ВСЕ вызовы SharePoint REST + внешние API + Power Automate в одном классе
loc/ — строки локализации
Правила декомпозиции: один компонент — один файл; компонент ≤ 300 строк; функция/хук ≤ ~50 строк; ≤ 3–4 уровня вложенности JSX; ≤ ~7 props (иначе группируй в объект). Повторил блок 3-й раз → выноси в общий компонент/функцию/хук.
5. Ключевые архитектурные паттерны
Эти паттерны — суть проекта. Соблюдай их, расширяя форму.
5.1. Единый стейт формы в одном хуке-родителе
Весь стейт многошаговой формы живёт в одном хуке (use⟨Feature⟩Form) внутри главного контейнера. Шаги не держат общий стейт сами — они получают срез данных и колбэки.
formData— единый объект со всеми данными формы (см.CTFormDataкак образец).- Обновление — иммутабельно и точечно:
updateFormData(partial: Partial<FormData>). - Производные данные считай на лету (
useMemo), не дублируй в отдельныйuseState. currentStep,completedSteps(пройденные),highestStep(максимально доступный) — тоже здесь.
5.2. Контракт props для шага (единый на все шаги)
Каждый Step* получает один и тот же набор:
interface StepProps {
service: ⟨Feature⟩Service;
formData: FormData;
onSave: (partial: Partial<FormData>) => void; // = updateFormData
onNext?: () => void;
onPrev?: () => void;
onStepComplete: (step: FormStep) => void;
isReadOnly?: boolean; // форма на согласовании/согласована/отклонена
isRestrictedView?: boolean; // урезанный доступ (напр. скрытие цен)
userDisplayName: string;
isNotificationLink?: boolean; // открыто по ссылке из уведомления
}
5.3. Сервисный слой
⟨Feature⟩Service — единственное место, где происходят сетевые вызовы. Компоненты не делают fetch/SPHttpClient напрямую.
- Конструктор принимает
WebPartContext, вытаскиваетbaseUrl = context.pageContext.web.absoluteUrlиspHttpClient. - Приватные хелперы для URL списков:
_li(listName)и_liById(listName, id). - Мемоизируй сервис в контейнере:
const service = useMemo(() => new ⟨Feature⟩Service(context), [context]).
5.4. Персист и восстановление сессии
- Стейт формы сериализуется в
sessionStorageпо ключу⟨prefix⟩_${entityId}при каждом изменении. entityIdкладётся в URL (?⟨entity⟩Id=…) черезhistory.replaceState, чтобы перезагрузка/шаринг ссылки продолжали ту же форму.- Поддержи ссылку из уведомления (Power Automate/Teams) с другим регистром параметра (напр.
?⟨entity⟩id=…): по ней всегда подгружай свежие данные из SharePoint и открывай нужный шаг. - При восстановлении не затирай несохранённые правки из сессии данными из SharePoint без нужды: бери SP-данные только когда в сессии пусто или это ссылка из уведомления (см. слияние в
⟨Feature⟩.tsx).
5.5. Доступность шагов и режимы только для чтения
- Breadcrumbs даёт кликать только по
completedStepsи в пределахhighestStep. isReadOnlyвключается по статусу сущности (on_approval/approved/rejected).- Особые ограничения по компании/роли (напр. скрытие цен) — отдельным флагом, который складывается с общими правилами, а не заменяет их.
5.6. Статусы — только union-типы
Статусы сущности и участников согласования — type X = 'a' | 'b' | ..., чтобы опечатка ловилась компилятором. Никаких magic-strings по коду.
5.7. Верстка на всю ширину экрана (full-width)
По умолчанию SPFx-страница зажимает WebPart шириной канвы (max-width ~1204px). Чтобы форма занимала всю ширину экрана, нужны две вещи в связке:
1. Флаг в манифесте WebPart (⟨Feature⟩WebPart.manifest.json) — разрешает положить WebPart в полноширинную секцию страницы:
"supportsFullBleed": true
После деплоя на странице добавь WebPart в полноширинную секцию (Full-width section — доступна только на страницах без баннера/на пустом шаблоне).
2. Обход контейнера канвы в корневом *.module.scss через :global — снимает max-width и у воркбенча (dev), и у живой канвы:
:global {
#workbenchPageContent,
.CanvasComponent.LCS .CanvasZone {
max-width: 100% !important;
}
}
.root {
min-height: 100vh; // тянем на всю высоту вьюпорта
*,
*::before,
*::after {
box-sizing: border-box;
}
}
!important тут оправдан: перебиваем инлайновые/встроенные стили канвы SharePoint, на которые повлиять иначе нельзя (это тот редкий случай, когда !important — не костыль). Селекторы #workbenchPageContent и .CanvasComponent.LCS .CanvasZone — внутренние классы SharePoint; если после обновления платформы ширина «вернётся», проверь актуальные имена в DevTools.
6. Playbook по SharePoint (важно — тут «грабли»)
Внутренние имена списков и полей — только в константах сервиса (const LISTS = {...}), не размазывай по коду. Помни: внутреннее имя SP-поля ≠ отображаемое (напр. поле «Статус» может иметь internal name status).
Запись lookup-поля: в теле запроса ключ — {InternalName}Id со скалярным значением (напр. { winSupplierId: 42 }). Суффикс LookupId бывает только в GET-ответах, для записи он не нужен.
Грабли с «защищёнными» списками (Retention / event receiver).
На некоторых списках hard DELETE и MERGE/PATCH падают с E_FAIL (-2147467259, SPException). Признак — на списке стоит политика удержания или ресивер.
- ✅ Работают:
POST(создание),GET(чтение),POST /items(N)/recycle()(мягкое удаление — то же, что делает кнопка «Удалить» в UI). - ❌ Не работают:
DELETEлюбым синтаксисом,MERGE/PATCH. - Приём: для «обновления» такой записи —
recycle()+ повторныйPOST, а неMERGE. - Если на новом списке REST
DELETE/MERGEпадает сE_FAIL— первым делом пробуйrecycle().
Грабли с $expand/$select: выборка Choice-поля через $expand по lookup иногда даёт 400. Решение — грузить связанные данные отдельным запросом по id и мерджить на клиенте (см. загрузку курсов валют в ⟨Feature⟩.tsx).
Округление денег: округляй монетарные значения до 2 знаков перед записью (Math.round(v*100)/100), чтобы не улетали float-хвосты.
7. Внешний API и Power Automate
Azure AD Bearer-токен (паттерн SPFx):
const tokenProvider = await context.aadTokenProviderFactory.getTokenProvider();
const token = await tokenProvider.getToken(⟨AAD_RESOURCE_ID⟩);
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
- Внутренний API TNSU: base
https://api.tnsu.kz, AAD Resource ID9232d9e2-9807-4753-808f-07250e5474b0. - Пример готовой реализации:
pmo-records/src/webparts/pmoRecords/services/TnsuApiService.ts. - Требуется прописать разрешение в
package-solution.json(webApiPermissionRequests) и одобрить его в API access центра администрирования SharePoint.
Power Automate: триггеры вызываются POST-запросом на HTTP-trigger URL. Все URL флоу — в константах сервиса, не в компонентах. Обрабатывай ответ и ошибки явно; вызовы, которые не должны блокировать UX (уведомления, статусы), делай non-blocking: service.doX(...).catch(() => undefined).
8. Конвенции кода (обязательны)
React-практики
useEffect— только для сайд-эффектов; бизнес-логику не клади в JSX.useMemo/useCallback— только при реальной необходимости.- Контролируемые компоненты предпочтительнее неконтролируемых. Props/state не мутируем.
- Загрузку данных отменяй на unmount (флаг
cancelled/AbortController), чтобы не было setState после размонтирования.
Состояние
- Состояние живёт максимально низко; наверх поднимаем только реально общее.
- Prop-drilling глубже 2–3 уровней → React Context (для сквозного: текущая сущность, пользователь, справочники).
- Один источник истины; изменения иммутабельны (
setItems(prev => [...prev, x])).
Имена и стиль
- Компоненты —
PascalCase; хуки —useSomething; переменные —camelCase; CSS-классы — единообразно (kebab-caseилиcamelCase). - Имена отражают смысл, а не реализацию (
isLoading, а неflag).
Константы и строки
- Никаких magic-strings в логике: статусы, имена списков/полей, роуты, id ресурсов, URL — в константах/конфиге.
- Пользовательские тексты — по возможности в одном месте (задел под локализацию).
- Числовые «магические» значения — именованные константы с комментарием-смыслом.
Ошибки и состояния
- Явно обрабатывай
loading/error/empty. Ошибки не скрывай. UI корректен при отсутствии данных.
Layout
- Адаптив;
flex/grid; контейнеры по умолчанию 100% ширины; консистентныеpadding/margin; без magic-numbers без комментария.
Комментарии
- Комментируй ПОЧЕМУ, а не ЧТО. Не дублируй очевидный код. Сложную бизнес-логику сопровождай коротким «почему».
- Не оставляй мёртвый/закомментированный код — удаляй.
9. Git и коммиты
- Формат:
тип: краткое описание в настоящем времени(feat:,fix:,refactor:,chore:,docs:,test:). - Один коммит — одно логическое изменение. Сообщение объясняет что и зачем, а не пересказывает диф.
- Не коммить отладочный код и секреты. По истории джун должен понять ход работы.
10. Чеклист старта нового проекта
yo @microsoft/sharepoint(SPFx 1.22.x) → WebPart с React-фреймворком. Проверь Node22.x.- Замени все
⟨плейсхолдеры⟩в этом файле и переименуй его вCLAUDE.md. - Опиши доменную модель в
types/domain.ts: сущности,type Status = …,FORM_STEPS. - Заведи
⟨Feature⟩Serviceсconst LISTS = {...}и хелперами_li/_liById. - Собери каркас:
WebPart.ts→ главный контейнер →use⟨Feature⟩Form→Breadcrumbs+ пустыеStep1..N. - Настрой persist в
sessionStorageи чтениеentityIdиз URL (+ вариант ссылки из уведомления). - Пропиши API-разрешения в
package-solution.json, если нужен внешний API. - Для full-width: поставь
"supportsFullBleed": trueв манифесте +:global-обход канвы в корневом SCSS (§5.7), и клади WebPart в полноширинную секцию страницы. - Реализуй шаги по одному, соблюдая единый контракт props (§5.2).
11. TODO-заглушки (типовые места, которые обычно доделываются позже)
⟨Feature⟩Service: заменить константыLISTSна реальные имена SP-списков.- Загрузка файлов в библиотеку SharePoint + триггер обработки (Syntex/Flow), если есть шаг с документами.
- People picker (
NormalPeoplePicker) для добавления согласующих. - Вызовы Power Automate для уведомлений/сохранения статусов.
12. Шаги формы — эталонная модель (на примере СТ)
Ниже — реальный поток из проекта ct-create-form. Каркас (4 шага, роли, статусы, режимы) переноси как есть, содержимое шагов адаптируй под свою предметку. Все 4 шага рендерятся из главного контейнера, стейт — в use⟨Feature⟩Form, каждый шаг получает единый контракт props (§5.2).
Метки шагов (FORM_STEPS в types/domain.ts):
export const FORM_STEPS = [
{ step: 1, label: "Выбор позиций" },
{ step: 2, label: "Загрузка КП" },
{ step: 3, label: "Формирование СТ" },
{ step: 4, label: "Согласование" },
];
Шаг 1 — «Выбор позиций» (сбор исходных данных)
Задача: пользователь выбирает исходные записи (в СТ — позиции потребностей), из которых собирается сущность.
- Грузит данные из внешнего API за последние N дней (по умолчанию 30), с поиском по номеру и пагинацией (~10/страница). Загрузка — через хук с отменой на unmount (
useRequests). - Таблица с раскрывающимися строками (родитель → дочерние записи) и чекбоксами выбора.
- Бизнес-ограничение: все выбранные записи — из одного проекта. На первом чекбоксе фиксируется
selectedProject, остальные проекты блокируются (lockedProjectId). - По «Далее»: создаёт сущность (СТ) и её позиции в SharePoint, кладёт
selectedPositions/selectedProjectвformData, помечает шаг завершённым, проставляет?⟨entity⟩Idв URL. - Read-only: если сущность уже на согласовании/согласована/отклонена — показывает выбранное без правки.
Шаг 2 — «Загрузка КП» (загрузка и обогащение данных)
Задача: загрузить документы (коммерческие предложения) и извлечь из них структурированные данные.
- Drag&drop загрузка файлов в библиотеку SharePoint (
FileUploadArea). - После загрузки — триггер обработки документа (Syntex / Power Automate flow) для извлечения полей (позиции, цены, поставщик). Идёт поллинг результата (интервал ~5с, лимит ~5 мин; статусы файла:
uploading → extracting → flowing → done/error). - Курсы валют (
CurrencyRatesForm): подгрузка из SP-списка «Курс Валют» / API + ручной ввод. Валюта КП запоминается на поставщике, чтобы Шаг 3 показал верные колонки. - Поставщики без КП фиксируются отдельно (список «Не получены КП») с комментарием.
- Результат в
formData:suppliers,offers,currencyRates,uploadedKpFiles.
Шаг 3 — «Формирование СТ» (сведение и сравнение)
Задача: свести всё в сравнительную матрицу и выбрать победителя.
- Excel-подобная матрица: строки = позиции, столбцы = поставщики; в ячейках — цена/сумма с НДС.
- Подсветка минимума по строке (зелёным) и авто-выбор рекомендованного поставщика (наименьшая итоговая сумма) →
recommendedSupplierId. - Редактируемо: количество по позиции (
step3PositionQtyOverrides), доп-позиции и доп-поставщики (step3Extra*), строки доставки с км (step3DeliveryKm), критерии (оплата/доставка/срок/сертификаты) и кастомные критерии. Конвертация единиц измерения —utils/unitConversion. - AI-комментарий по таблице (через Power Automate flow).
- Скрытие цен (
isRestrictedView) для урезанного доступа. - При открытии по ссылке из уведомления этот шаг — главный: тут же показывается цепочка согласования и доступны действия «Согласовать/Отклонить».
Шаг 4 — «Согласование» (маршрут и решение)
Задача: собрать цепочку согласующих и провести сущность по маршруту.
- Конструктор цепочки: добавление согласующих через
NormalPeoplePicker(поиск пользователей SP), порядок, роли, право делегирования (canAddApprover). - Инициатор ставит «Рекомендую» → цепочка активируется, статус сущности → «На согласовании».
- Диалог «Согласовать/Отклонить» с комментарием. Статусы участников:
pending | recommends | approved | rejected | skipped. - Побочные эффекты (non-blocking): уведомления через Power Automate; запись статуса сущности и выбранного поставщика (
winSupplier, lookup) в SP-список. Статусы сущности:draft | kp_loading | kp_loaded | formed | on_approval | approved | rejected.
Как адаптировать под новый проект: сохрани роли (автор/инициатор → согласующие → наблюдатель), 3 режима отображения (обычный / read-only по статусу / restricted), персист+ссылку из уведомления и деривацию статуса из цепочки (
deriveCtStatusв контейнере). Меняй только содержимое: какие данные собираешь на Шаге 1, что грузишь/извлекаешь на Шаге 2, что и как сравниваешь на Шаге 3.
Ссылки на образцы (в текущем окружении разработчика)
- Боевой проект-образец:
ct-create-form(СТ). СмотриCtService.ts,useCtForm.ts,CtCreateForm.tsx. - Родственный проект той же архитектуры:
oid-create-form. - Пример внешнего API с AAD-токеном:
pmo-records/.../TnsuApiService.ts.