diff --git a/apps/web/src/components/page-header.tsx b/apps/web/src/components/page-header.tsx new file mode 100644 index 0000000..ce11dc8 --- /dev/null +++ b/apps/web/src/components/page-header.tsx @@ -0,0 +1,45 @@ +import type { ReactNode } from 'react' +import { cn } from '@cfdm/ui/lib/utils' + +interface PageHeaderProps { + title: string + description?: ReactNode + actions?: ReactNode + className?: string +} + +/** + * Page-level section header (h1) — shared by dashboard, lists, settings. + * Distinct from FrameTitle (text-sm inside Frame panels). + */ +export function PageHeader({ + title, + description, + actions, + className, +}: PageHeaderProps) { + return ( +
+
+

+ {title} +

+ {description ? ( +

+ {description} +

+ ) : null} +
+ {actions ? ( +
+ {actions} +
+ ) : null} +
+ ) +} diff --git a/apps/web/src/components/reui-kit/ops-dashboard.tsx b/apps/web/src/components/reui-kit/ops-dashboard.tsx index 6a86c10..c53fcc1 100644 --- a/apps/web/src/components/reui-kit/ops-dashboard.tsx +++ b/apps/web/src/components/reui-kit/ops-dashboard.tsx @@ -7,6 +7,7 @@ import { FrameTitle, } from '@/components/reui/frame' import { Skeleton } from '@cfdm/ui/components/skeleton' +import { PageHeader } from '@/components/page-header' import { KpiStatGrid, type KpiStatCard } from './kpi-stat-grid' export type OpsKpiCard = KpiStatCard @@ -54,14 +55,7 @@ export function OpsDashboard({ return (
-
-
-

{title}

-

- {description} -

-
-
+
diff --git a/apps/web/src/components/reui-kit/settings-shell.tsx b/apps/web/src/components/reui-kit/settings-shell.tsx index 2be7145..519280a 100644 --- a/apps/web/src/components/reui-kit/settings-shell.tsx +++ b/apps/web/src/components/reui-kit/settings-shell.tsx @@ -5,6 +5,7 @@ import { SettingsIcon } from 'lucide-react' import { useIsMobile } from '@cfdm/ui/hooks/use-mobile' import { cn } from '@cfdm/ui/lib/utils' import { PageShell } from '@/components/page-shell' +import { PageHeader } from '@/components/page-header' import { Separator } from '@cfdm/ui/components/separator' export interface SettingsTabConfig { @@ -40,15 +41,10 @@ export function SettingsShell({ return (
-
-
-

{title}

-

- {description} -

-
+
+ -
+
void @@ -210,6 +211,7 @@ export function ServicesGroupedCatalog({ domainLabel, isLoading = false, primaryAction, + hideHeader = false, togglingId, activeTab: controlledTab, onTabChange, @@ -347,21 +349,23 @@ export function ServicesGroupedCatalog({ tableLayout={{ dense: true }} > - -
- Сервисы - - {domainLabel - ? `Каталог сервисов с привязками к ${domainLabel}` - : 'Группы, FQDN и доступность сервисов'} - -
- {primaryAction ? ( -
- {primaryAction} + {!hideHeader ? ( + +
+ Сервисы + + {domainLabel + ? `Каталог сервисов с привязками к ${domainLabel}` + : 'Группы, FQDN и доступность сервисов'} +
- ) : null} -
+ {primaryAction ? ( +
+ {primaryAction} +
+ ) : null} + + ) : null}
diff --git a/apps/web/src/routes/_auth/certificates.tsx b/apps/web/src/routes/_auth/certificates.tsx index a57c84a..acb1c1b 100644 --- a/apps/web/src/routes/_auth/certificates.tsx +++ b/apps/web/src/routes/_auth/certificates.tsx @@ -6,6 +6,7 @@ import type { Filter } from '@/components/reui/filters' import { certificatesQueryOptions, certKeys, certSummaryQueryOptions } from '@/queries' import { api } from '@/lib/api-client' import { PageShell } from '@/components/page-shell' +import { PageHeader } from '@/components/page-header' import { ResourcePage } from '@/components/reui-kit' import { CertKpiCards } from '@/components/cert-kpi-cards' import { @@ -71,6 +72,11 @@ function CertificatesPage() { return ( + ({ ...tab }))} activeTab={activeTab} onTabChange={setActiveTab} @@ -97,7 +104,6 @@ function CertificatesPage() { isError={isError} error={error} onRetry={refetch} - primaryAction={primaryAction} emptyState={{ title: 'Сертификаты не найдены', description: 'Сертификаты появятся после проверки доменов', diff --git a/apps/web/src/routes/_auth/domains/index.tsx b/apps/web/src/routes/_auth/domains/index.tsx index 3266b9f..216740a 100644 --- a/apps/web/src/routes/_auth/domains/index.tsx +++ b/apps/web/src/routes/_auth/domains/index.tsx @@ -15,6 +15,7 @@ import { serviceBindingKeys, } from '@/queries' import { PageShell } from '@/components/page-shell' +import { PageHeader } from '@/components/page-header' import { DomainKpiCards } from '@/components/domain-kpi-cards' import { ResourcePage } from '@/components/reui-kit' import { @@ -149,6 +150,11 @@ function DomainsPage() { return ( + ({ ...tab }))} activeTab={activeTab} onTabChange={setActiveTab} @@ -174,7 +181,6 @@ function DomainsPage() { isError={isError} error={error} onRetry={refetch} - primaryAction={primaryAction} enableRowSelection selectionToolbar={({ selectedIds, clearSelection }) => ( ) + const headerActions = ( + <> + {createButton} + + + ) + if (isLoading) { return ( + {view === 'board' ? ( ) : ( + + {view === 'board' ? ( (groups?.length ?? 0) === 0 && (domains?.length ?? 0) === 0 ? ( - {createButton} - - - } renderCard={(domain, ctx) => ( ({ ...tab }))} activeTab={activeTab} onTabChange={setActiveTab} @@ -301,12 +323,6 @@ function GroupsPage() { columns={columns} data={catalogRows} getRowId={(row) => String(row.id)} - primaryAction={ - <> - {createButton} - - - } emptyState={{ title: 'Группы не найдены', description: 'Создайте группу для организации доменов.', diff --git a/apps/web/src/routes/_auth/services.tsx b/apps/web/src/routes/_auth/services.tsx index c0e63af..f56a7c5 100644 --- a/apps/web/src/routes/_auth/services.tsx +++ b/apps/web/src/routes/_auth/services.tsx @@ -21,6 +21,7 @@ import type { UpdateServiceConfigInput, } from '@/lib/schemas' import { PageShell } from '@/components/page-shell' +import { PageHeader } from '@/components/page-header' import { ServiceKpiCards } from '@/components/service-kpi-cards' import { EmptyState } from '@/components/empty-state' import { ConfirmDialog } from '@/components/confirm-dialog' @@ -437,6 +438,17 @@ function ServicesPage() { /> ) + const headerActions = ( + <> + {addMenu} + + + ) + + const pageDescription = filteredDomain + ? `Сервисы с привязками к домену ${filteredDomain.zone_name}` + : 'Группы, FQDN и доступность сервисов' + const sheets = ( <> + {view === 'board' ? ( ) : ( {}} onDeleteService={() => {}} @@ -551,6 +569,7 @@ function ServicesPage() { if (isError) { return ( + + {view === 'board' ? ( <> {addMenu} -
} /> @@ -603,12 +626,6 @@ function ServicesPage() { columns={columnConfigs} value={kanbanValue} onValueChange={dragDisabled ? () => {} : handleKanbanValueChange} - toolbarActions={ - <> - {addMenu} - - - } renderCard={(service, ctx) => ( - {addMenu} - - - } emptyAction={
{addMenu}