-
-
{item.label}
- {footer ?
{footer}
: null}
-
-
- {item.value}
+ {/* Design «VPS-Tracker» KPI: label, then value + hint Badge on one baseline. */}
+
+
{item.label}
+
+
+ {item.value}
+
+ {footer ? {footer} : null}
diff --git a/apps/web/src/components/reui-kit/ops-dashboard.tsx b/apps/web/src/components/reui-kit/ops-dashboard.tsx
index 26e2362..ae02659 100644
--- a/apps/web/src/components/reui-kit/ops-dashboard.tsx
+++ b/apps/web/src/components/reui-kit/ops-dashboard.tsx
@@ -1,11 +1,4 @@
import type { ReactNode } from 'react'
-import {
- Frame,
- FrameDescription,
- FrameHeader,
- FramePanel,
- 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'
@@ -16,13 +9,15 @@ interface OpsDashboardProps {
title?: string
description?: string
kpiCards: OpsKpiCard[]
- charts: ReactNode
- queue: ReactNode
+ /** Alert between the page header and KPI (design order: title → banner → KPI → tabs). */
+ banner?: ReactNode
+ /** Page body after KPI — design «VPS-Tracker»: line tabs (Проблемы / Последние VPS / Аккаунты). */
+ children: ReactNode
isLoading?: boolean
}
const rootClassName =
- 'text-foreground @container flex w-full flex-col gap-2 md:gap-3'
+ 'text-foreground @container flex w-full flex-col gap-4 md:gap-6'
function OpsDashboardSkeleton() {
return (
@@ -45,8 +40,8 @@ export function OpsDashboard({
title = 'Панель управления',
description = 'Обзор VPS, платежей, тарифов и синхронизации',
kpiCards,
- charts,
- queue,
+ banner,
+ children,
isLoading = false,
}: OpsDashboardProps) {
if (isLoading) {
@@ -57,25 +52,13 @@ export function OpsDashboard({
+ {banner}
+
-
-
-
-
-
- Требуют внимания
-
- Проблемы оплаты, истекающие VPS и ошибки синхронизации
-
-
- {queue}
-
-
+ {children}
)
}
diff --git a/apps/web/src/routes/_auth/dashboard.tsx b/apps/web/src/routes/_auth/dashboard.tsx
index 44bbdec..a18dc97 100644
--- a/apps/web/src/routes/_auth/dashboard.tsx
+++ b/apps/web/src/routes/_auth/dashboard.tsx
@@ -29,9 +29,9 @@ import { dataGridCellStack } from '@/components/data-grid-cells'
import { KpiStatGridSkeleton, TableSkeleton } from '@/components/skeletons'
import { Button } from '@cfdm/ui/components/button'
import { Badge } from '@/components/reui/badge'
-import { Tabs, TabsContent, TabsList, TabsTrigger } from '@cfdm/ui/components/tabs'
+import { TabsContent } from '@cfdm/ui/components/tabs'
+import { CountedLineTabs } from '@/components/counted-line-tabs'
import { StatusBadge } from '@/components/status-badge'
-import { cn } from '@cfdm/ui/lib/utils'
import { computeInventoryHealth } from '@/lib/inventory-health'
import { buildAtRiskAccounts, type AtRiskAccount } from '@/lib/account-health'
@@ -47,9 +47,6 @@ import { DashboardInventoryAlert } from '@/components/domain/dashboard-inventory
import type { Vps } from '@/types/entities'
-const DASHBOARD_TAB_TRIGGER_CLASS =
- 'flex-none rounded-none border-0 border-b-2 border-transparent px-3 pb-2.5 pt-2 shadow-none after:hidden data-active:border-foreground data-active:bg-transparent data-active:shadow-none dark:data-active:border-foreground dark:data-active:bg-transparent'
-
const DASHBOARD_QUICK_ACTIONS: QuickActionItem[] = [
{
id: 'vps',
@@ -371,111 +368,92 @@ function DashboardPage() {
return (
-
-
+ }
title="Дашборд"
description="Сводка по VPS, балансам и здоровью инвентаря"
kpiCards={statsLoading ? [] : kpiCards}
isLoading={statsLoading}
- charts={
-
-
-
-
- }
- queue={
-
-
-
-
- Проблемы
- {issues.length > 0 ? (
-
- {issues.length}
-
- ) : null}
-
-
- Последние VPS
-
-
- Аккаунты
- {atRisk.length > 0 ? (
-
- {atRisk.length}
-
- ) : null}
-
-
-
- i.key}
- emptyTitle="Проблем не найдено"
- emptyDescription="Критичных проблем в инвентаре не обнаружено"
- pagination={false}
+ >
+
+
+
+ {snap.settings?.[0]?.showQuickActions !== false ? (
+
-
-
- }>
- Все VPS
-
- }
- columns={columnDefFromDataGrid(vpsColumns)}
- data={activeVps.slice(0, 8)}
- getRowId={(v) => v.id}
- pagination={false}
- onRowClick={(v) => navigate({ to: '/vps', search: { edit: v.id } })}
+ ) : null}
+
+
-
-
- r.id}
- emptyTitle="Рисков нет"
- emptyDescription="Балансы и синхронизация в норме"
- pagination={false}
+
-
-
-
- }
- />
-
- {snap.settings?.[0]?.showQuickActions !== false ? (
-
- ) : null}
+
+ i.key}
+ emptyTitle="Проблем не найдено"
+ emptyDescription="Критичных проблем в инвентаре не обнаружено"
+ pagination={false}
+ />
+
+
+ }>
+ Все VPS
+
+ }
+ columns={columnDefFromDataGrid(vpsColumns)}
+ data={activeVps.slice(0, 8)}
+ getRowId={(v) => v.id}
+ pagination={false}
+ onRowClick={(v) => navigate({ to: '/vps', search: { edit: v.id } })}
+ />
+
+
+ r.id}
+ emptyTitle="Рисков нет"
+ emptyDescription="Балансы и синхронизация в норме"
+ pagination={false}
+ />
+
+
+
+