From 5a95202327ba9643ffaa0bb9f097e929e401e21e Mon Sep 17 00:00:00 2001 From: Denozordec Date: Fri, 17 Jul 2026 01:36:54 +0700 Subject: [PATCH] refactor(web): enhance OpsDashboard and ChartContainer for improved metrics logging and responsiveness - Refactored OpsDashboard to encapsulate layout metrics logging in a dedicated function, improving code clarity and maintainability. - Updated metrics logging to include SVG dimensions for better analytics. - Enhanced ChartContainer to utilize ResizeObserver for dynamic sizing, ensuring charts render correctly within their containers. Co-authored-by: Cursor --- .../src/components/reui-kit/ops-dashboard.tsx | 109 ++++++++++-------- packages/ui/src/components/chart.tsx | 49 +++++--- 2 files changed, 96 insertions(+), 62 deletions(-) diff --git a/apps/web/src/components/reui-kit/ops-dashboard.tsx b/apps/web/src/components/reui-kit/ops-dashboard.tsx index 5789baa..a219df1 100644 --- a/apps/web/src/components/reui-kit/ops-dashboard.tsx +++ b/apps/web/src/components/reui-kit/ops-dashboard.tsx @@ -63,57 +63,68 @@ 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, + const logLayout = (runId: string) => { + 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 + const surface = f.querySelector( + '.recharts-surface', + ) as SVGElement | 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(surface), + svgW: surface?.clientWidth ?? 0, + svgH: surface?.clientHeight ?? 0, + } + }) + : [] + 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', }, - timestamp: Date.now(), - }), - }).catch(() => {}) + body: JSON.stringify({ + sessionId: '0f5088', + runId, + location: 'ops-dashboard.tsx:layout', + message: 'dashboard layout metrics', + hypothesisId: '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(() => {}) + } + logLayout('post-fix-2') + requestAnimationFrame(() => + requestAnimationFrame(() => logLayout('post-fix-2-raf')), + ) // #endregion - }, [isLoading]) + }, [isLoading, kpiCards]) if (isLoading) { return diff --git a/packages/ui/src/components/chart.tsx b/packages/ui/src/components/chart.tsx index 850bafa..1d9b7aa 100644 --- a/packages/ui/src/components/chart.tsx +++ b/packages/ui/src/components/chart.tsx @@ -48,9 +48,7 @@ function ChartContainer({ ...props }: React.ComponentProps<"div"> & { config: ChartConfig - children: React.ComponentProps< - typeof RechartsPrimitive.ResponsiveContainer - >["children"] + children: React.ReactNode initialDimension?: { width: number height: number @@ -58,28 +56,53 @@ function ChartContainer({ }) { const uniqueId = React.useId() const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}` + const containerRef = React.useRef(null) + const [size, setSize] = React.useState(initialDimension) + + React.useEffect(() => { + const el = containerRef.current + if (!el || typeof ResizeObserver === "undefined") return + + const update = () => { + const width = Math.max(1, Math.floor(el.clientWidth)) + const height = Math.max(1, Math.floor(el.clientHeight)) + setSize((prev) => + prev.width === width && prev.height === height + ? prev + : { width, height }, + ) + } + + update() + const ro = new ResizeObserver(update) + ro.observe(el) + return () => ro.disconnect() + }, []) + + // Recharts 3 ResponsiveContainer часто оставляет inner 0×0 (см. debug dashboard). + // Рендерим chart с явными width/height по размеру контейнера. + const sizedChildren = React.Children.map(children, (child) => { + if (!React.isValidElement(child)) return child + return React.cloneElement( + child as React.ReactElement<{ width?: number; height?: number }>, + { width: size.width, height: size.height }, + ) + }) return (
- - {children} - + {sizedChildren}
)