diff --git a/apps/web/src/components/reui-kit/dashboard-analytics.tsx b/apps/web/src/components/reui-kit/dashboard-analytics.tsx index 4fb0d12..1181c58 100644 --- a/apps/web/src/components/reui-kit/dashboard-analytics.tsx +++ b/apps/web/src/components/reui-kit/dashboard-analytics.tsx @@ -80,7 +80,7 @@ export function CertStatusChart({ data }: CertStatusChartProps) { ) return ( - + Статусы сертификатов Распределение по последней проверке @@ -162,7 +162,7 @@ export function GroupDomainsChart({ data }: GroupDomainsChartProps) { const chartRef = useChartSizeLog('group-domains', data.length) return ( - + Домены по группам Топ-6 групп по количеству зон diff --git a/apps/web/src/components/reui-kit/kpi-stat-grid.tsx b/apps/web/src/components/reui-kit/kpi-stat-grid.tsx index 48be41b..b91aa27 100644 --- a/apps/web/src/components/reui-kit/kpi-stat-grid.tsx +++ b/apps/web/src/components/reui-kit/kpi-stat-grid.tsx @@ -42,36 +42,40 @@ function KpiStatCardBody({ card }: { card: KpiStatCard }) { } function KpiStatCardItem({ card }: { card: KpiStatCard }) { + const interactive = Boolean(card.to || card.onSelect) const panelClass = cn( - 'relative isolate flex h-full flex-col gap-3', + 'relative isolate flex flex-col gap-3', card.selected && 'ring-primary/30 bg-muted/30 ring-1', + interactive && + 'hover:bg-muted/40 focus-within:ring-ring transition-colors focus-within:ring-2', ) + // card-35: FramePanel — прямой child грида; ссылка/кнопка внутри панели if (card.to) { return ( - - + + - - + + ) } if (card.onSelect) { return ( - + + ) } diff --git a/apps/web/src/components/reui-kit/ops-dashboard.tsx b/apps/web/src/components/reui-kit/ops-dashboard.tsx index 5e798ad..5789baa 100644 --- a/apps/web/src/components/reui-kit/ops-dashboard.tsx +++ b/apps/web/src/components/reui-kit/ops-dashboard.tsx @@ -62,6 +62,57 @@ export function OpsDashboard({ }, 'A', ) + // #region agent log + const kpi = el.querySelector('[aria-label="Ключевые метрики"]') + const analytics = el.querySelector('[aria-label="Аналитика"]') + const kpiFrame = kpi?.querySelector('[data-slot=frame]') + const kpiGrid = kpiFrame?.firstElementChild + const charts = analytics + ? [...analytics.querySelectorAll('[data-slot=frame]')].map((f) => { + const panel = f.querySelector('[data-slot=frame-panel]') + const chart = f.querySelector('[data-slot=chart]') + const inner = chart?.querySelector( + '.recharts-responsive-container > div', + ) as HTMLElement | null + return { + frameH: (f as HTMLElement).clientHeight, + panelH: (panel as HTMLElement | null)?.clientHeight ?? 0, + chartW: (chart as HTMLElement | null)?.clientWidth ?? 0, + chartH: (chart as HTMLElement | null)?.clientHeight ?? 0, + innerW: inner?.clientWidth ?? 0, + innerH: inner?.clientHeight ?? 0, + hasSvg: Boolean(f.querySelector('.recharts-surface')), + } + }) + : [] + fetch('http://127.0.0.1:7580/ingest/5c1b60ca-3f59-41ce-8435-d25bcc12c3cf', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Debug-Session-Id': '0f5088', + }, + body: JSON.stringify({ + sessionId: '0f5088', + runId: 'post-fix', + location: 'ops-dashboard.tsx:layout', + message: 'dashboard layout metrics', + hypothesisId: 'A-C', + data: { + kpiPanelDirect: + kpiGrid != null && + [...kpiGrid.children].every( + (c) => c.getAttribute('data-slot') === 'frame-panel', + ), + kpiFirstChildTag: kpiGrid?.firstElementChild?.tagName ?? null, + analyticsAutoRows: analytics + ? getComputedStyle(analytics).gridAutoRows + : null, + charts, + }, + timestamp: Date.now(), + }), + }).catch(() => {}) + // #endregion }, [isLoading]) if (isLoading) { @@ -88,7 +139,7 @@ export function OpsDashboard({
{charts}
diff --git a/packages/ui/src/components/chart.tsx b/packages/ui/src/components/chart.tsx index 3c50b1e..850bafa 100644 --- a/packages/ui/src/components/chart.tsx +++ b/packages/ui/src/components/chart.tsx @@ -65,13 +65,17 @@ function ChartContainer({ data-slot="chart" data-chart={chartId} className={cn( - "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden", + // relative + block: flex на контейнере даёт Recharts ResponsiveContainer 0×0 + "relative block w-full text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden", className )} {...props} > {children}