feat: wire report form page, remove section placeholder

All eight sections of the old stroy-bot-* web parts are now reachable
from the app. Unknown routes fall back to the hub.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Тимур Городецкий
2026-10-16 10:08:07 +05:00
parent 072d246eed
commit 92b578d806
5 changed files with 27 additions and 22 deletions
+6
View File
@@ -190,6 +190,12 @@
🐞 Create без проверки NoAccess. 🐞 Номер может совпасть после мягкого удаления. 🐞 Дата «сегодня» — UTC.
**Сознательные отличия от report:**
- `confirm('Утвердить отчет? …')` → `confirmModal` из ui-kit, текст тот же.
- Адрес сайта для списков и ссылки на папку фото — настройка «URL сайта SharePoint» (раньше `pageContext.web.absoluteUrl`); путь папки при создании — по-прежнему от сайта, где стоит страница (`web.serverRelativeUrl`). По умолчанию это один и тот же сайт `/sites/stroy`.
- Если Dataverse ответит на создание записи `204` без тела, старый код брал id из заголовка `OData-EntityId`, а новый клиент заголовков не видит. С `Prefer: return=representation` Dataverse отвечает `201` с телом, поэтому на практике не встречается; при пустом id сохранение останавливается той же ошибкой `reportId is empty`.
- Логика режимов, валидации, сборки строк и порядка записи / удаления сверена со старым кодом построчно и покрыта тестами (45 тестов).
---
## 6. Импорт работ (stroy-bot-import-works) — `?page=import&projectId=`
+18 -17
View File
@@ -1,34 +1,35 @@
import * as React from 'react';
import { useRouter } from './routing/RouterContext';
import { SectionPlaceholder } from './SectionPlaceholder';
import { HubPage } from '../pages/hub/HubPage';
import { ResourcesPage } from '../pages/resources/ResourcesPage';
import { BinderPage } from '../pages/binder/BinderPage';
import { ReportsPage } from '../pages/reports/ReportsPage';
import { ImportWorksPage } from '../pages/import/ImportWorksPage';
import { ObjectsPage } from '../pages/objects/ObjectsPage';
import { WorksPage } from '../pages/works/WorksPage';
import { ReportsPage } from '../pages/reports/ReportsPage';
import { ReportPage } from '../pages/report/ReportPage';
import { ImportWorksPage } from '../pages/import/ImportWorksPage';
import { ResourcesPage } from '../pages/resources/ResourcesPage';
import { BinderPage } from '../pages/binder/BinderPage';
/** Показывает страницу текущего раздела. Разделы подключаются сюда по мере переноса. */
/** Показывает страницу текущего раздела (раздел берётся из ?page=). */
export const PageOutlet: React.FC = () => {
const { route } = useRouter();
switch (route.page) {
case 'hub':
return <HubPage />;
case 'resources':
return <ResourcesPage />;
case 'binder':
return <BinderPage />;
case 'reports':
return <ReportsPage />;
case 'import':
return <ImportWorksPage />;
case 'objects':
return <ObjectsPage />;
case 'works':
return <WorksPage />;
case 'reports':
return <ReportsPage />;
case 'report':
return <ReportPage />;
case 'import':
return <ImportWorksPage />;
case 'resources':
return <ResourcesPage />;
case 'binder':
return <BinderPage />;
case 'hub':
default:
return <SectionPlaceholder />;
return <HubPage />;
}
};
+1 -3
View File
@@ -1,9 +1,7 @@
// Тексты оболочки приложения. Тексты разделов лежат рядом с разделами (pages/*/texts.ts).
export const APP_TEXTS = {
brand: 'StroyBot',
sectionInProgress: 'Раздел переносится из старого web part',
sectionInProgressHint: 'Пока пользуйтесь старой страницей раздела.'
brand: 'StroyBot'
} as const;
export const PROPERTY_PANE_TEXTS = {
@@ -53,7 +53,7 @@ export const ReportForm: React.FC<Props> = ({ params }) => {
}
const isSaved = !!report.successMessage;
const runAsync = (action: () => Promise<void>) => (): void => {
const runAsync = (action: () => Promise<void>): (() => void) => (): void => {
action().catch(() => undefined);
};
@@ -23,7 +23,7 @@ const stopPropagation = (event: React.KeyboardEvent): void => event.stopPropagat
export const PickerCreateForm: React.FC<Props> = ({ create, requiresUnit, nestedInTitle, parentCandidates }) => {
const { draft, isCreating, errorMessage } = create;
const change =
(field: keyof IPickerCreateDraft) =>
(field: keyof IPickerCreateDraft): ((value: string) => void) =>
(value: string): void =>
create.changeDraft({ [field]: value });