feat(ui): единый PageHeader h1 на всех разделах
Build, Test, and Push CFDM Docker Image / test (push) Successful in 3m4s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 1m45s
Build, Test, and Push CFDM Docker Image / create-release (push) Skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 6s
Build, Test, and Push CFDM Docker Image / test (push) Successful in 3m4s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 1m45s
Build, Test, and Push CFDM Docker Image / create-release (push) Skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 6s
Заголовок раздела над KPI/контентом; FrameTitle text-sm больше не заменяет page-level h1. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -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 (
|
||||||
|
<header
|
||||||
|
className={cn(
|
||||||
|
'flex flex-wrap items-start justify-between gap-3 px-1',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex min-w-0 flex-1 flex-col gap-px">
|
||||||
|
<h1 className="text-xl font-semibold tracking-tight md:text-2xl">
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
{description ? (
|
||||||
|
<p className="text-muted-foreground max-w-2xl text-sm leading-relaxed">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{actions ? (
|
||||||
|
<div className="flex shrink-0 flex-wrap items-center justify-end gap-2">
|
||||||
|
{actions}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
FrameTitle,
|
FrameTitle,
|
||||||
} from '@/components/reui/frame'
|
} from '@/components/reui/frame'
|
||||||
import { Skeleton } from '@cfdm/ui/components/skeleton'
|
import { Skeleton } from '@cfdm/ui/components/skeleton'
|
||||||
|
import { PageHeader } from '@/components/page-header'
|
||||||
import { KpiStatGrid, type KpiStatCard } from './kpi-stat-grid'
|
import { KpiStatGrid, type KpiStatCard } from './kpi-stat-grid'
|
||||||
|
|
||||||
export type OpsKpiCard = KpiStatCard
|
export type OpsKpiCard = KpiStatCard
|
||||||
@@ -54,14 +55,7 @@ export function OpsDashboard({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={rootClassName}>
|
<div className={rootClassName}>
|
||||||
<header className="px-1">
|
<PageHeader title={title} description={description} />
|
||||||
<div className="flex flex-col gap-px">
|
|
||||||
<h1 className="text-xl font-semibold tracking-tight">{title}</h1>
|
|
||||||
<p className="text-muted-foreground max-w-2xl text-sm leading-relaxed">
|
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<section aria-label="Ключевые метрики">
|
<section aria-label="Ключевые метрики">
|
||||||
<KpiStatGrid cards={kpiCards} skeletonCount={4} />
|
<KpiStatGrid cards={kpiCards} skeletonCount={4} />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { SettingsIcon } from 'lucide-react'
|
|||||||
import { useIsMobile } from '@cfdm/ui/hooks/use-mobile'
|
import { useIsMobile } from '@cfdm/ui/hooks/use-mobile'
|
||||||
import { cn } from '@cfdm/ui/lib/utils'
|
import { cn } from '@cfdm/ui/lib/utils'
|
||||||
import { PageShell } from '@/components/page-shell'
|
import { PageShell } from '@/components/page-shell'
|
||||||
|
import { PageHeader } from '@/components/page-header'
|
||||||
import { Separator } from '@cfdm/ui/components/separator'
|
import { Separator } from '@cfdm/ui/components/separator'
|
||||||
|
|
||||||
export interface SettingsTabConfig {
|
export interface SettingsTabConfig {
|
||||||
@@ -40,15 +41,10 @@ export function SettingsShell({
|
|||||||
return (
|
return (
|
||||||
<PageShell>
|
<PageShell>
|
||||||
<div className="mx-auto flex w-full max-w-4xl flex-col gap-5">
|
<div className="mx-auto flex w-full max-w-4xl flex-col gap-5">
|
||||||
<header className="flex flex-col gap-3 px-1">
|
<div className="flex flex-col gap-3">
|
||||||
<div className="flex flex-col gap-px">
|
<PageHeader title={title} description={description} />
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
|
|
||||||
<p className="text-muted-foreground max-w-2xl text-sm leading-relaxed">
|
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
<Separator />
|
||||||
</header>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ interface ServicesGroupedCatalogProps {
|
|||||||
domainLabel?: string
|
domainLabel?: string
|
||||||
isLoading?: boolean
|
isLoading?: boolean
|
||||||
primaryAction?: ReactNode
|
primaryAction?: ReactNode
|
||||||
|
hideHeader?: boolean
|
||||||
togglingId: number | null
|
togglingId: number | null
|
||||||
activeTab?: string
|
activeTab?: string
|
||||||
onTabChange?: (tabId: string) => void
|
onTabChange?: (tabId: string) => void
|
||||||
@@ -210,6 +211,7 @@ export function ServicesGroupedCatalog({
|
|||||||
domainLabel,
|
domainLabel,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
primaryAction,
|
primaryAction,
|
||||||
|
hideHeader = false,
|
||||||
togglingId,
|
togglingId,
|
||||||
activeTab: controlledTab,
|
activeTab: controlledTab,
|
||||||
onTabChange,
|
onTabChange,
|
||||||
@@ -347,21 +349,23 @@ export function ServicesGroupedCatalog({
|
|||||||
tableLayout={{ dense: true }}
|
tableLayout={{ dense: true }}
|
||||||
>
|
>
|
||||||
<Frame dense spacing="sm" className="w-full">
|
<Frame dense spacing="sm" className="w-full">
|
||||||
<FrameHeader className="flex-row items-start justify-between gap-3">
|
{!hideHeader ? (
|
||||||
<div className="flex min-w-0 flex-col gap-px">
|
<FrameHeader className="flex-row items-start justify-between gap-3">
|
||||||
<FrameTitle>Сервисы</FrameTitle>
|
<div className="flex min-w-0 flex-col gap-px">
|
||||||
<FrameDescription>
|
<FrameTitle>Сервисы</FrameTitle>
|
||||||
{domainLabel
|
<FrameDescription>
|
||||||
? `Каталог сервисов с привязками к ${domainLabel}`
|
{domainLabel
|
||||||
: 'Группы, FQDN и доступность сервисов'}
|
? `Каталог сервисов с привязками к ${domainLabel}`
|
||||||
</FrameDescription>
|
: 'Группы, FQDN и доступность сервисов'}
|
||||||
</div>
|
</FrameDescription>
|
||||||
{primaryAction ? (
|
|
||||||
<div className="flex shrink-0 flex-wrap items-center justify-end gap-2">
|
|
||||||
{primaryAction}
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
{primaryAction ? (
|
||||||
</FrameHeader>
|
<div className="flex shrink-0 flex-wrap items-center justify-end gap-2">
|
||||||
|
{primaryAction}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</FrameHeader>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<FramePanel className="p-0 shadow-none!">
|
<FramePanel className="p-0 shadow-none!">
|
||||||
<div className="px-(--frame-panel-header-px) pt-(--frame-panel-header-py)">
|
<div className="px-(--frame-panel-header-px) pt-(--frame-panel-header-py)">
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { Filter } from '@/components/reui/filters'
|
|||||||
import { certificatesQueryOptions, certKeys, certSummaryQueryOptions } from '@/queries'
|
import { certificatesQueryOptions, certKeys, certSummaryQueryOptions } from '@/queries'
|
||||||
import { api } from '@/lib/api-client'
|
import { api } from '@/lib/api-client'
|
||||||
import { PageShell } from '@/components/page-shell'
|
import { PageShell } from '@/components/page-shell'
|
||||||
|
import { PageHeader } from '@/components/page-header'
|
||||||
import { ResourcePage } from '@/components/reui-kit'
|
import { ResourcePage } from '@/components/reui-kit'
|
||||||
import { CertKpiCards } from '@/components/cert-kpi-cards'
|
import { CertKpiCards } from '@/components/cert-kpi-cards'
|
||||||
import {
|
import {
|
||||||
@@ -71,6 +72,11 @@ function CertificatesPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageShell>
|
<PageShell>
|
||||||
|
<PageHeader
|
||||||
|
title="Сертификаты"
|
||||||
|
description="Мониторинг SSL: хосты с активными сервисами или режимом «Обязательно»"
|
||||||
|
actions={primaryAction}
|
||||||
|
/>
|
||||||
<CertKpiCards
|
<CertKpiCards
|
||||||
summary={summary}
|
summary={summary}
|
||||||
total={certs?.length}
|
total={certs?.length}
|
||||||
@@ -81,6 +87,7 @@ function CertificatesPage() {
|
|||||||
<ResourcePage
|
<ResourcePage
|
||||||
title="Сертификаты"
|
title="Сертификаты"
|
||||||
description="Мониторинг SSL: хосты с активными сервисами или режимом «Обязательно»"
|
description="Мониторинг SSL: хосты с активными сервисами или режимом «Обязательно»"
|
||||||
|
hideHeader
|
||||||
tabs={CERT_TABS.map((tab) => ({ ...tab }))}
|
tabs={CERT_TABS.map((tab) => ({ ...tab }))}
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
onTabChange={setActiveTab}
|
onTabChange={setActiveTab}
|
||||||
@@ -97,7 +104,6 @@ function CertificatesPage() {
|
|||||||
isError={isError}
|
isError={isError}
|
||||||
error={error}
|
error={error}
|
||||||
onRetry={refetch}
|
onRetry={refetch}
|
||||||
primaryAction={primaryAction}
|
|
||||||
emptyState={{
|
emptyState={{
|
||||||
title: 'Сертификаты не найдены',
|
title: 'Сертификаты не найдены',
|
||||||
description: 'Сертификаты появятся после проверки доменов',
|
description: 'Сертификаты появятся после проверки доменов',
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
serviceBindingKeys,
|
serviceBindingKeys,
|
||||||
} from '@/queries'
|
} from '@/queries'
|
||||||
import { PageShell } from '@/components/page-shell'
|
import { PageShell } from '@/components/page-shell'
|
||||||
|
import { PageHeader } from '@/components/page-header'
|
||||||
import { DomainKpiCards } from '@/components/domain-kpi-cards'
|
import { DomainKpiCards } from '@/components/domain-kpi-cards'
|
||||||
import { ResourcePage } from '@/components/reui-kit'
|
import { ResourcePage } from '@/components/reui-kit'
|
||||||
import {
|
import {
|
||||||
@@ -149,6 +150,11 @@ function DomainsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageShell>
|
<PageShell>
|
||||||
|
<PageHeader
|
||||||
|
title="Домены"
|
||||||
|
description="Импортированные зоны Cloudflare"
|
||||||
|
actions={primaryAction}
|
||||||
|
/>
|
||||||
<DomainKpiCards
|
<DomainKpiCards
|
||||||
domains={domains}
|
domains={domains}
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
@@ -158,6 +164,7 @@ function DomainsPage() {
|
|||||||
<ResourcePage
|
<ResourcePage
|
||||||
title="Домены"
|
title="Домены"
|
||||||
description="Импортированные зоны Cloudflare"
|
description="Импортированные зоны Cloudflare"
|
||||||
|
hideHeader
|
||||||
tabs={DOMAIN_TABS.map((tab) => ({ ...tab }))}
|
tabs={DOMAIN_TABS.map((tab) => ({ ...tab }))}
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
onTabChange={setActiveTab}
|
onTabChange={setActiveTab}
|
||||||
@@ -174,7 +181,6 @@ function DomainsPage() {
|
|||||||
isError={isError}
|
isError={isError}
|
||||||
error={error}
|
error={error}
|
||||||
onRetry={refetch}
|
onRetry={refetch}
|
||||||
primaryAction={primaryAction}
|
|
||||||
enableRowSelection
|
enableRowSelection
|
||||||
selectionToolbar={({ selectedIds, clearSelection }) => (
|
selectionToolbar={({ selectedIds, clearSelection }) => (
|
||||||
<DomainsBulkToolbar
|
<DomainsBulkToolbar
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { api } from '@/lib/api-client'
|
import { api } from '@/lib/api-client'
|
||||||
import type { CreateGroupInput, Group } from '@/lib/schemas'
|
import type { CreateGroupInput, Group } from '@/lib/schemas'
|
||||||
import { PageShell } from '@/components/page-shell'
|
import { PageShell } from '@/components/page-shell'
|
||||||
|
import { PageHeader } from '@/components/page-header'
|
||||||
import { GroupKpiCards } from '@/components/group-kpi-cards'
|
import { GroupKpiCards } from '@/components/group-kpi-cards'
|
||||||
import { EmptyState } from '@/components/empty-state'
|
import { EmptyState } from '@/components/empty-state'
|
||||||
import { ConfirmDialog } from '@/components/confirm-dialog'
|
import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||||
@@ -202,15 +203,31 @@ function GroupsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const headerActions = (
|
||||||
|
<>
|
||||||
|
{createButton}
|
||||||
|
<CatalogBoardToggle
|
||||||
|
view={view}
|
||||||
|
onViewChange={setView}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<PageShell>
|
<PageShell>
|
||||||
|
<PageHeader
|
||||||
|
title="Группы"
|
||||||
|
description="Каталог групп доменов"
|
||||||
|
actions={headerActions}
|
||||||
|
/>
|
||||||
{view === 'board' ? (
|
{view === 'board' ? (
|
||||||
<KanbanBoardSkeleton columns={3} />
|
<KanbanBoardSkeleton columns={3} />
|
||||||
) : (
|
) : (
|
||||||
<ResourcePage
|
<ResourcePage
|
||||||
title="Группы"
|
title="Группы"
|
||||||
description="Каталог групп доменов"
|
description="Каталог групп доменов"
|
||||||
|
hideHeader
|
||||||
isLoading
|
isLoading
|
||||||
filterFields={[]}
|
filterFields={[]}
|
||||||
filters={[]}
|
filters={[]}
|
||||||
@@ -228,6 +245,7 @@ function GroupsPage() {
|
|||||||
if (isError) {
|
if (isError) {
|
||||||
return (
|
return (
|
||||||
<PageShell>
|
<PageShell>
|
||||||
|
<PageHeader title="Группы" description="Каталог групп доменов" />
|
||||||
<EmptyState
|
<EmptyState
|
||||||
title="Не удалось загрузить группы"
|
title="Не удалось загрузить группы"
|
||||||
description={error?.message}
|
description={error?.message}
|
||||||
@@ -243,6 +261,15 @@ function GroupsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageShell>
|
<PageShell>
|
||||||
|
<PageHeader
|
||||||
|
title="Группы"
|
||||||
|
description={
|
||||||
|
view === 'board'
|
||||||
|
? 'Перетащите домен в группу или создайте новую группу'
|
||||||
|
: 'Каталог групп доменов'
|
||||||
|
}
|
||||||
|
actions={headerActions}
|
||||||
|
/>
|
||||||
{view === 'board' ? (
|
{view === 'board' ? (
|
||||||
(groups?.length ?? 0) === 0 && (domains?.length ?? 0) === 0 ? (
|
(groups?.length ?? 0) === 0 && (domains?.length ?? 0) === 0 ? (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
@@ -263,12 +290,6 @@ function GroupsPage() {
|
|||||||
columns={columnConfigs}
|
columns={columnConfigs}
|
||||||
value={kanbanValue}
|
value={kanbanValue}
|
||||||
onValueChange={handleKanbanValueChange}
|
onValueChange={handleKanbanValueChange}
|
||||||
toolbarActions={
|
|
||||||
<>
|
|
||||||
{createButton}
|
|
||||||
<CatalogBoardToggle view="board" onViewChange={setView} />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
renderCard={(domain, ctx) => (
|
renderCard={(domain, ctx) => (
|
||||||
<DomainKanbanCard
|
<DomainKanbanCard
|
||||||
domain={domain}
|
domain={domain}
|
||||||
@@ -289,6 +310,7 @@ function GroupsPage() {
|
|||||||
<ResourcePage
|
<ResourcePage
|
||||||
title="Группы"
|
title="Группы"
|
||||||
description="Каталог групп доменов"
|
description="Каталог групп доменов"
|
||||||
|
hideHeader
|
||||||
tabs={GROUP_TABS.map((tab) => ({ ...tab }))}
|
tabs={GROUP_TABS.map((tab) => ({ ...tab }))}
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
onTabChange={setActiveTab}
|
onTabChange={setActiveTab}
|
||||||
@@ -301,12 +323,6 @@ function GroupsPage() {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
data={catalogRows}
|
data={catalogRows}
|
||||||
getRowId={(row) => String(row.id)}
|
getRowId={(row) => String(row.id)}
|
||||||
primaryAction={
|
|
||||||
<>
|
|
||||||
{createButton}
|
|
||||||
<CatalogBoardToggle view="catalog" onViewChange={setView} />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
emptyState={{
|
emptyState={{
|
||||||
title: 'Группы не найдены',
|
title: 'Группы не найдены',
|
||||||
description: 'Создайте группу для организации доменов.',
|
description: 'Создайте группу для организации доменов.',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import type {
|
|||||||
UpdateServiceConfigInput,
|
UpdateServiceConfigInput,
|
||||||
} from '@/lib/schemas'
|
} from '@/lib/schemas'
|
||||||
import { PageShell } from '@/components/page-shell'
|
import { PageShell } from '@/components/page-shell'
|
||||||
|
import { PageHeader } from '@/components/page-header'
|
||||||
import { ServiceKpiCards } from '@/components/service-kpi-cards'
|
import { ServiceKpiCards } from '@/components/service-kpi-cards'
|
||||||
import { EmptyState } from '@/components/empty-state'
|
import { EmptyState } from '@/components/empty-state'
|
||||||
import { ConfirmDialog } from '@/components/confirm-dialog'
|
import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||||
@@ -437,6 +438,17 @@ function ServicesPage() {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const headerActions = (
|
||||||
|
<>
|
||||||
|
{addMenu}
|
||||||
|
<CatalogBoardToggle view={view} onViewChange={setView} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const pageDescription = filteredDomain
|
||||||
|
? `Сервисы с привязками к домену ${filteredDomain.zone_name}`
|
||||||
|
: 'Группы, FQDN и доступность сервисов'
|
||||||
|
|
||||||
const sheets = (
|
const sheets = (
|
||||||
<>
|
<>
|
||||||
<ServiceEditSheet
|
<ServiceEditSheet
|
||||||
@@ -528,12 +540,18 @@ function ServicesPage() {
|
|||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<PageShell>
|
<PageShell>
|
||||||
|
<PageHeader
|
||||||
|
title="Сервисы"
|
||||||
|
description={pageDescription}
|
||||||
|
actions={headerActions}
|
||||||
|
/>
|
||||||
{view === 'board' ? (
|
{view === 'board' ? (
|
||||||
<KanbanBoardSkeleton columns={3} />
|
<KanbanBoardSkeleton columns={3} />
|
||||||
) : (
|
) : (
|
||||||
<ServicesGroupedCatalog
|
<ServicesGroupedCatalog
|
||||||
data={{ groups: [], ungrouped: [] }}
|
data={{ groups: [], ungrouped: [] }}
|
||||||
isLoading
|
isLoading
|
||||||
|
hideHeader
|
||||||
togglingId={null}
|
togglingId={null}
|
||||||
onEditService={() => {}}
|
onEditService={() => {}}
|
||||||
onDeleteService={() => {}}
|
onDeleteService={() => {}}
|
||||||
@@ -551,6 +569,7 @@ function ServicesPage() {
|
|||||||
if (isError) {
|
if (isError) {
|
||||||
return (
|
return (
|
||||||
<PageShell>
|
<PageShell>
|
||||||
|
<PageHeader title="Сервисы" description={pageDescription} />
|
||||||
<EmptyState
|
<EmptyState
|
||||||
title="Не удалось загрузить сервисы"
|
title="Не удалось загрузить сервисы"
|
||||||
description={error?.message}
|
description={error?.message}
|
||||||
@@ -567,6 +586,11 @@ function ServicesPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageShell>
|
<PageShell>
|
||||||
|
<PageHeader
|
||||||
|
title="Сервисы"
|
||||||
|
description={pageDescription}
|
||||||
|
actions={headerActions}
|
||||||
|
/>
|
||||||
{view === 'board' ? (
|
{view === 'board' ? (
|
||||||
<>
|
<>
|
||||||
<ServicesBulkToolbar
|
<ServicesBulkToolbar
|
||||||
@@ -588,7 +612,6 @@ function ServicesPage() {
|
|||||||
action={
|
action={
|
||||||
<div className="flex flex-wrap justify-center gap-2">
|
<div className="flex flex-wrap justify-center gap-2">
|
||||||
{addMenu}
|
{addMenu}
|
||||||
<CatalogBoardToggle view="board" onViewChange={setView} />
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -603,12 +626,6 @@ function ServicesPage() {
|
|||||||
columns={columnConfigs}
|
columns={columnConfigs}
|
||||||
value={kanbanValue}
|
value={kanbanValue}
|
||||||
onValueChange={dragDisabled ? () => {} : handleKanbanValueChange}
|
onValueChange={dragDisabled ? () => {} : handleKanbanValueChange}
|
||||||
toolbarActions={
|
|
||||||
<>
|
|
||||||
{addMenu}
|
|
||||||
<CatalogBoardToggle view="board" onViewChange={setView} />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
renderCard={(service, ctx) => (
|
renderCard={(service, ctx) => (
|
||||||
<ServiceKanbanCard
|
<ServiceKanbanCard
|
||||||
service={service}
|
service={service}
|
||||||
@@ -638,18 +655,13 @@ function ServicesPage() {
|
|||||||
togglingId={togglingServiceId}
|
togglingId={togglingServiceId}
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
onTabChange={setActiveTab}
|
onTabChange={setActiveTab}
|
||||||
|
hideHeader
|
||||||
onEditService={setEditingService}
|
onEditService={setEditingService}
|
||||||
onDeleteService={setDeletingService}
|
onDeleteService={setDeletingService}
|
||||||
onToggleService={handleServiceToggle}
|
onToggleService={handleServiceToggle}
|
||||||
onEditGroup={setEditingGroup}
|
onEditGroup={setEditingGroup}
|
||||||
onDeleteGroup={setDeletingGroup}
|
onDeleteGroup={setDeletingGroup}
|
||||||
onAddServiceToGroup={handleOpenCreateService}
|
onAddServiceToGroup={handleOpenCreateService}
|
||||||
primaryAction={
|
|
||||||
<>
|
|
||||||
{addMenu}
|
|
||||||
<CatalogBoardToggle view="catalog" onViewChange={setView} />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
emptyAction={
|
emptyAction={
|
||||||
<div className="flex flex-wrap justify-center gap-2">
|
<div className="flex flex-wrap justify-center gap-2">
|
||||||
{addMenu}
|
{addMenu}
|
||||||
|
|||||||
Reference in New Issue
Block a user