diff --git a/apps/web/src/components/reui-kit/detail-panel.tsx b/apps/web/src/components/reui-kit/detail-panel.tsx index 5079a9f..fb23a6d 100644 --- a/apps/web/src/components/reui-kit/detail-panel.tsx +++ b/apps/web/src/components/reui-kit/detail-panel.tsx @@ -6,13 +6,23 @@ import { FramePanel, FrameTitle, } from '@/components/reui/frame' +import { + KpiStatGrid, + type KpiStatCard, + type KpiStatVariant, +} from '@/components/reui-kit/kpi-stat-grid' import { cn } from '@evofw/ui/lib/utils' export interface DetailMetricCard { id: string icon: ReactNode label: string + /** Primary numeric / text value (shown large). */ description: string + /** Outline badge on the right (hybrid KPI). */ + hint?: string + iconClassName?: string + variant?: KpiStatVariant footer?: ReactNode } @@ -22,7 +32,11 @@ interface DetailPanelProps { } function DetailPanelRoot({ children, className }: DetailPanelProps) { - return
{children}
+ return ( +
+ {children} +
+ ) } interface DetailPanelHeaderProps { @@ -53,34 +67,29 @@ function DetailPanelHeader({ ) : null} - {children ? {children} : null} + {children ? ( + {children} + ) : null} ) } /** - * Compact KPI strip — stats-7 pattern (single Frame, divided cells). - * Preview: https://reui.io/preview/base/stats-7 + * Hybrid KPI cards — KpiStatGrid / stats-12. + * Preview: https://reui.io/preview/base/stats-12 */ function DetailPanelMetrics({ cards }: { cards: DetailMetricCard[] }) { - return ( - - - {cards.map((card) => ( -
-
- {card.icon} - {card.label} -
-

- {card.description} -

- {card.footer} -
- ))} -
- - ) + const kpiCards: KpiStatCard[] = cards.map((card) => ({ + id: card.id, + label: card.label, + value: card.description, + icon: card.icon, + iconClassName: card.iconClassName, + hint: card.hint, + variant: card.variant, + footer: card.footer, + })) + return } function DetailPanelSection({ diff --git a/apps/web/src/routes/_auth/agents/$id.tsx b/apps/web/src/routes/_auth/agents/$id.tsx index cdea734..af6a57b 100644 --- a/apps/web/src/routes/_auth/agents/$id.tsx +++ b/apps/web/src/routes/_auth/agents/$id.tsx @@ -226,24 +226,31 @@ function AgentDetailPage() { { id: 'dropped', icon: , + iconClassName: 'text-warning', label: 'Dropped', description: String(a.last_apply_packets_dropped ?? 0), + hint: 'сумма counters', + variant: 'warning', }, { id: 'accepted', icon: , + iconClassName: 'text-success', label: 'Accepted', description: String(a.last_apply_packets_accepted ?? 0), + hint: 'сумма counters', }, { id: 'kernel', icon: , + iconClassName: 'text-info', label: 'Kernel', description: a.last_apply_kernel_method ?? '—', }, { id: 'apply', icon: , + iconClassName: 'text-primary', label: 'Last apply', description: a.last_apply_at ?? '—', }, diff --git a/apps/web/src/routes/_auth/lists/index.tsx b/apps/web/src/routes/_auth/lists/index.tsx index e1f336b..f07c892 100644 --- a/apps/web/src/routes/_auth/lists/index.tsx +++ b/apps/web/src/routes/_auth/lists/index.tsx @@ -82,7 +82,7 @@ type ListItem = { * Lists — master-detail (ReUI PRO composition). * Catalog: https://reui.io/preview/base/list-9 * Entries: https://reui.io/preview/base/data-grid-filtering-2 - * KPI: https://reui.io/preview/base/stats-7 + * KPI: https://reui.io/preview/base/stats-12 (KpiStatGrid hybrid) * Sheet: https://reui.io/preview/base/sheet-1 · sheet-8 * Empty: https://reui.io/preview/base/empty-state-12 */ @@ -461,27 +461,38 @@ function ListsPage() { { id: 'count', icon: , + iconClassName: 'text-info', label: 'Записей', description: String(entryItems.length), + hint: manual ? 'ручной список' : 'внешний источник', }, { id: 'type', icon: , + iconClassName: 'text-primary', label: 'Источник', description: + detail.type === 'json_url' + ? 'JSON' + : detail.type === 'evobgp_community' + ? 'EvoBGP' + : 'Ручной', + hint: detail.type === 'json_url' ? 'JSON по URL' : detail.type === 'evobgp_community' - ? 'EvoBGP community' - : 'Ручной', + ? 'community prefixes' + : 'IP / CIDR / домен / список', }, { id: 'cidrs', icon: , + iconClassName: 'text-success', label: 'CIDR в политике', description: String( detail.entry_count ?? detail.entries.length, ), + hint: 'materialized', }, ]} /> diff --git a/apps/web/src/routes/_auth/rules/$setId.tsx b/apps/web/src/routes/_auth/rules/$setId.tsx index ba7dfed..c4d00b8 100644 --- a/apps/web/src/routes/_auth/rules/$setId.tsx +++ b/apps/web/src/routes/_auth/rules/$setId.tsx @@ -410,20 +410,25 @@ function PolicySetDetailPage() { { id: 'rules', icon: , + iconClassName: 'text-info', label: 'Правила', description: String(set.rules_count ?? rules.length), }, { id: 'agents', icon: , + iconClassName: 'text-primary', label: 'Агенты', description: String(set.agents_count ?? assignedIds.length), + hint: 'назначено', }, { id: 'status', icon: , + iconClassName: set.enabled ? 'text-success' : 'text-muted-foreground', label: 'Статус', description: set.enabled ? 'Включён' : 'Выключен', + hint: set.enabled ? 'active' : 'disabled', }, ]} />