Custom pages: флажок «скрыть шапку страницы» (миграция 0089)

- custom_pages.hide_header: платформа не рендерит заголовок (название + описание)
  над виджетом — для полноэкранных отчётов/дашбордов, без CSS-хаков из кода страницы
- UI: переключатель «Скрыть шапку страницы» в диалоге создания/редактирования
- REST create/patch принимают hideHeader; MCP create_custom_page/update_custom_page —
  параметр hide_header (виден ИИ сразу, default false)
This commit is contained in:
2026-09-30 15:42:52 +03:00
parent 06a2794090
commit 49b7732a24
7 changed files with 38 additions and 15 deletions

View File

@@ -173,6 +173,16 @@ export function JsPagesSection({
onCheckedChange={(v) => setPageForm(f => ({ ...f, showInSidebar: v }))}
/>
</div>
<div className="flex items-center justify-between rounded-md border px-3 py-2">
<div>
<p className="text-sm font-medium">Скрыть шапку страницы</p>
<p className="text-xs text-muted-foreground">Не показывать платформенный заголовок с названием и описанием (для полноэкранных отчётов и дашбордов)</p>
</div>
<Switch
checked={pageForm.hideHeader}
onCheckedChange={(v) => setPageForm(f => ({ ...f, hideHeader: v }))}
/>
</div>
<div>
<label className="text-sm font-medium">Формы (источники данных)</label>
<p className="text-xs text-muted-foreground mb-1">Задачи из выбранных форм будут доступны через ctx.forms[formId]</p>

View File

@@ -80,7 +80,7 @@ export function useBotsController() {
// JS Pages state
const [pageDialogOpen, setPageDialogOpen] = useState(false);
const [editingPage, setEditingPage] = useState<any>(null);
const [pageForm, setPageForm] = useState({ name: '', slug: '', description: '', code: JS_PAGE_DEFAULT_CODE, formIds: [] as number[], icon: 'FileCode', showInSidebar: false });
const [pageForm, setPageForm] = useState({ name: '', slug: '', description: '', code: JS_PAGE_DEFAULT_CODE, formIds: [] as number[], icon: 'FileCode', showInSidebar: false, hideHeader: false });
const [pageShowTemplates, setPageShowTemplates] = useState(false);
const [pagePreviewOpen, setPagePreviewOpen] = useState(false);
const [pagePreviewDialogOpen, setPagePreviewDialogOpen] = useState(false);
@@ -160,7 +160,7 @@ export function useBotsController() {
queryClient.invalidateQueries({ queryKey: ['/api/custom-pages'] });
setPageDialogOpen(false);
setEditingPage(null);
setPageForm({ name: '', slug: '', description: '', code: JS_PAGE_DEFAULT_CODE, formIds: [], icon: 'FileCode', showInSidebar: false });
setPageForm({ name: '', slug: '', description: '', code: JS_PAGE_DEFAULT_CODE, formIds: [], icon: 'FileCode', showInSidebar: false, hideHeader: false });
toast({ title: 'Страница создана', description: pageForm.showInSidebar ? 'Ссылка появится в боковой панели' : 'Страница скрыта из бокового меню' });
},
onError: (e: any) => toast({ title: 'Ошибка', description: e?.message ?? 'Ошибка создания', variant: 'destructive' }),
@@ -188,14 +188,14 @@ export function useBotsController() {
const openCreatePage = () => {
setEditingPage(null);
setPageForm({ name: '', slug: '', description: '', code: JS_PAGE_DEFAULT_CODE, formIds: [], icon: 'FileCode', showInSidebar: false });
setPageForm({ name: '', slug: '', description: '', code: JS_PAGE_DEFAULT_CODE, formIds: [], icon: 'FileCode', showInSidebar: false, hideHeader: false });
setSlugCheckResult('idle');
setPageDialogOpen(true);
};
const openEditPage = (page: any) => {
setEditingPage(page);
setPageForm({ name: page.name, slug: page.slug, description: page.description ?? '', code: page.code ?? JS_PAGE_DEFAULT_CODE, formIds: page.formIds ?? [], icon: page.icon ?? 'FileCode', showInSidebar: page.showInSidebar ?? false });
setPageForm({ name: page.name, slug: page.slug, description: page.description ?? '', code: page.code ?? JS_PAGE_DEFAULT_CODE, formIds: page.formIds ?? [], icon: page.icon ?? 'FileCode', showInSidebar: page.showInSidebar ?? false, hideHeader: page.hideHeader ?? false });
setSlugCheckResult('idle');
setPageDialogOpen(true);
};

View File

@@ -113,13 +113,15 @@ export default function CustomPageView() {
return (
<Layout>
<div className="flex flex-col h-full overflow-hidden">
<div className="border-b px-4 py-2 flex items-center gap-2 shrink-0 bg-background">
<FileCode className="w-4 h-4 text-muted-foreground" />
<h1 className="text-sm font-medium">{page.name}</h1>
{page.description && (
<span className="text-xs text-muted-foreground ml-2">{page.description}</span>
)}
</div>
{!page.hideHeader && (
<div className="border-b px-4 py-2 flex items-center gap-2 shrink-0 bg-background">
<FileCode className="w-4 h-4 text-muted-foreground" />
<h1 className="text-sm font-medium">{page.name}</h1>
{page.description && (
<span className="text-xs text-muted-foreground ml-2">{page.description}</span>
)}
</div>
)}
<div className="flex-1 overflow-auto p-4">
<JsComponentRenderer
code={page.code}