refactor(web): enhance OpsDashboard and ChartContainer for improved metrics logging and responsiveness
Build, Test, and Push CFDM Docker Image / test (push) Successful in 3m46s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 1m56s
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 6s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped

- 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 <[email protected]>
This commit is contained in:
Denozordec
2026-07-17 01:36:54 +07:00
co-authored by Cursor
parent fa66f06067
commit 5a95202327
2 changed files with 96 additions and 62 deletions
@@ -63,6 +63,7 @@ export function OpsDashboard({
'A', 'A',
) )
// #region agent log // #region agent log
const logLayout = (runId: string) => {
const kpi = el.querySelector('[aria-label="Ключевые метрики"]') const kpi = el.querySelector('[aria-label="Ключевые метрики"]')
const analytics = el.querySelector('[aria-label="Аналитика"]') const analytics = el.querySelector('[aria-label="Аналитика"]')
const kpiFrame = kpi?.querySelector('[data-slot=frame]') const kpiFrame = kpi?.querySelector('[data-slot=frame]')
@@ -74,6 +75,9 @@ export function OpsDashboard({
const inner = chart?.querySelector( const inner = chart?.querySelector(
'.recharts-responsive-container > div', '.recharts-responsive-container > div',
) as HTMLElement | null ) as HTMLElement | null
const surface = f.querySelector(
'.recharts-surface',
) as SVGElement | null
return { return {
frameH: (f as HTMLElement).clientHeight, frameH: (f as HTMLElement).clientHeight,
panelH: (panel as HTMLElement | null)?.clientHeight ?? 0, panelH: (panel as HTMLElement | null)?.clientHeight ?? 0,
@@ -81,7 +85,9 @@ export function OpsDashboard({
chartH: (chart as HTMLElement | null)?.clientHeight ?? 0, chartH: (chart as HTMLElement | null)?.clientHeight ?? 0,
innerW: inner?.clientWidth ?? 0, innerW: inner?.clientWidth ?? 0,
innerH: inner?.clientHeight ?? 0, innerH: inner?.clientHeight ?? 0,
hasSvg: Boolean(f.querySelector('.recharts-surface')), hasSvg: Boolean(surface),
svgW: surface?.clientWidth ?? 0,
svgH: surface?.clientHeight ?? 0,
} }
}) })
: [] : []
@@ -93,10 +99,10 @@ export function OpsDashboard({
}, },
body: JSON.stringify({ body: JSON.stringify({
sessionId: '0f5088', sessionId: '0f5088',
runId: 'post-fix', runId,
location: 'ops-dashboard.tsx:layout', location: 'ops-dashboard.tsx:layout',
message: 'dashboard layout metrics', message: 'dashboard layout metrics',
hypothesisId: 'A-C', hypothesisId: 'C',
data: { data: {
kpiPanelDirect: kpiPanelDirect:
kpiGrid != null && kpiGrid != null &&
@@ -112,8 +118,13 @@ export function OpsDashboard({
timestamp: Date.now(), timestamp: Date.now(),
}), }),
}).catch(() => {}) }).catch(() => {})
}
logLayout('post-fix-2')
requestAnimationFrame(() =>
requestAnimationFrame(() => logLayout('post-fix-2-raf')),
)
// #endregion // #endregion
}, [isLoading]) }, [isLoading, kpiCards])
if (isLoading) { if (isLoading) {
return <OpsDashboardSkeleton /> return <OpsDashboardSkeleton />
+36 -13
View File
@@ -48,9 +48,7 @@ function ChartContainer({
...props ...props
}: React.ComponentProps<"div"> & { }: React.ComponentProps<"div"> & {
config: ChartConfig config: ChartConfig
children: React.ComponentProps< children: React.ReactNode
typeof RechartsPrimitive.ResponsiveContainer
>["children"]
initialDimension?: { initialDimension?: {
width: number width: number
height: number height: number
@@ -58,28 +56,53 @@ function ChartContainer({
}) { }) {
const uniqueId = React.useId() const uniqueId = React.useId()
const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}` const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`
const containerRef = React.useRef<HTMLDivElement>(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 ( return (
<ChartContext.Provider value={{ config }}> <ChartContext.Provider value={{ config }}>
<div <div
ref={containerRef}
data-slot="chart" data-slot="chart"
data-chart={chartId} data-chart={chartId}
className={cn( className={cn(
// relative + block: flex на контейнере даёт Recharts ResponsiveContainer 0×0 "relative block w-full min-h-0 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 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 className
)} )}
{...props} {...props}
> >
<ChartStyle id={chartId} config={config} /> <ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer {sizedChildren}
width="100%"
height="100%"
minWidth={0}
initialDimension={initialDimension}
>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div> </div>
</ChartContext.Provider> </ChartContext.Provider>
) )