Compare commits

...
2 Commits
Author SHA1 Message Date
Denozordec 1a142e68a9 refactor: update Dashboard components for improved layout and integration
CI / changes (push) Successful in 9s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 52s
CI / go (push) Successful in 59s
CI / bird2 (push) Successful in 15s
CI / release (push) Successful in 4m20s
Refactored the DashboardQuickLinkCard to enhance its structure and presentation by integrating FramePanel. Updated the DashboardQuickLinks component to utilize Frame for better organization, including a header and description. Removed the previous DashboardFramePanel integration in the DashboardComponent to streamline the layout, ensuring a more cohesive user experience.
2026-07-09 21:15:04 +07:00
Denozordec 46e7b2aa6b chore: update BadgeTabs component to log additional layout metrics
CI / changes (push) Successful in 10s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 49s
CI / go (push) Successful in 1m0s
CI / bird2 (push) Successful in 17s
CI / release (push) Successful in 22s
Added new layout metrics logging to the BadgeTabs component, enhancing debugging capabilities. This update includes additional timestamped entries for layout metrics, providing better insights into component behavior during runtime.
2026-07-09 21:06:33 +07:00
5 changed files with 84 additions and 111 deletions
+5
View File
@@ -9,3 +9,8 @@
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605152671} {"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605152671}
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605222064} {"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605222064}
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605222067} {"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605222067}
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605819320}
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605827145}
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783605864391}
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783606471768}
{"sessionId":"c10bcb","runId":"post-fix","hypothesisId":"H1","location":"badge-tabs.tsx:useEffect","message":"BadgeTabs layout metrics","data":{"flexDirection":"column","orientation":"horizontal","listLeft":280,"panelLeft":280,"stacked":true},"timestamp":1783606472923}
+26 -60
View File
@@ -1,5 +1,4 @@
import type { ComponentProps, ReactNode } from 'react' import type { ComponentProps, ReactNode } from 'react'
import { useEffect, useRef } from 'react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@evobgp/ui/components/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@evobgp/ui/components/tabs'
import { cn } from '@evobgp/ui/lib/utils' import { cn } from '@evobgp/ui/lib/utils'
@@ -36,67 +35,34 @@ export function BadgeTabs({
listClassName, listClassName,
contentClassName, contentClassName,
}: BadgeTabsProps) { }: BadgeTabsProps) {
const tabsRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const el = tabsRef.current?.querySelector('[data-slot=tabs]')
if (!el || !(el instanceof HTMLElement)) return
const flexDirection = getComputedStyle(el).flexDirection
const listRect = el.querySelector('[data-slot=tabs-list]')?.getBoundingClientRect()
const panelRect = el.querySelector('[data-slot=tabs-content]')?.getBoundingClientRect()
// #region agent log
fetch('http://127.0.0.1:7311/ingest/6b35c3ae-1bcd-4c9c-81eb-f157c9347393', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': 'c10bcb' },
body: JSON.stringify({
sessionId: 'c10bcb',
runId: 'post-fix',
hypothesisId: 'H1',
location: 'badge-tabs.tsx:useEffect',
message: 'BadgeTabs layout metrics',
data: {
flexDirection,
orientation: el.getAttribute('data-orientation'),
listLeft: listRect?.left,
panelLeft: panelRect?.left,
stacked: flexDirection === 'column',
},
timestamp: Date.now(),
}),
}).catch(() => {})
// #endregion
}, [value, defaultValue])
return ( return (
<div ref={tabsRef} className={cn('w-full', className)}> <Tabs
<Tabs value={value}
value={value} defaultValue={defaultValue}
defaultValue={defaultValue} onValueChange={onValueChange}
onValueChange={onValueChange} className={cn('w-full', className)}
className="w-full" >
<TabsList
variant="line"
className={cn(
'mb-4 w-full justify-start gap-6',
listClassName,
)}
> >
<TabsList {items.map((item) => (
variant="line" <TabsTrigger key={item.value} value={item.value} className="gap-2">
className={cn( {item.icon}
'mb-4 w-full justify-start gap-6', {item.label}
listClassName, {item.count !== undefined ? (
)} <Badge variant={item.badgeVariant ?? 'primary-light'} size="sm">
> {item.count}
{items.map((item) => ( </Badge>
<TabsTrigger key={item.value} value={item.value} className="gap-2"> ) : null}
{item.icon} </TabsTrigger>
{item.label} ))}
{item.count !== undefined ? ( </TabsList>
<Badge variant={item.badgeVariant ?? 'primary-light'} size="sm"> <div className={cn('w-full min-w-0', contentClassName)}>{children}</div>
{item.count} </Tabs>
</Badge>
) : null}
</TabsTrigger>
))}
</TabsList>
<div className={cn('w-full min-w-0', contentClassName)}>{children}</div>
</Tabs>
</div>
) )
} }
@@ -2,8 +2,8 @@ import { Link } from '@tanstack/react-router'
import { ChevronRight } from 'lucide-react' import { ChevronRight } from 'lucide-react'
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import { Frame, FramePanel } from '@/components/reui/frame'
import { CardDotField } from '@/components/dashboard/card-dot-field' import { CardDotField } from '@/components/dashboard/card-dot-field'
import { FramePanel } from '@/components/reui/frame'
import { cn } from '@evobgp/ui/lib/utils' import { cn } from '@evobgp/ui/lib/utils'
import { Item, ItemMedia } from '@evobgp/ui/components/item' import { Item, ItemMedia } from '@evobgp/ui/components/item'
@@ -25,46 +25,44 @@ export function DashboardQuickLinkCard({
iconClass, iconClass,
}: QuickLinkCardProps) { }: QuickLinkCardProps) {
return ( return (
<Frame className="h-full"> <Link
<Link to={to}
to={to} search={search}
search={search} className="group block h-full rounded-[inherit] focus-visible:outline-none"
className="group block h-full rounded-[inherit] focus-visible:outline-none" aria-label={`${label}: ${description}`}
aria-label={`${label}: ${description}`} >
<FramePanel
className={cn(
'relative isolate h-full overflow-hidden transition-colors',
'hover:border-foreground/20',
'group-focus-visible:ring-2 group-focus-visible:ring-ring group-focus-visible:ring-offset-2 group-focus-visible:ring-offset-background',
)}
> >
<FramePanel <CardDotField className="text-muted-foreground [mask-image:linear-gradient(to_bottom_left,black,transparent_60%)]" />
className={cn( <div className="relative z-10 flex h-full flex-col gap-7.5">
'relative isolate h-full overflow-hidden transition-colors', <Item
'hover:border-foreground/20', className={cn(
'group-focus-visible:ring-2 group-focus-visible:ring-ring group-focus-visible:ring-offset-2 group-focus-visible:ring-offset-background', 'border-background flex size-11 shrink-0 items-center justify-center border-2 p-0 shadow-[0_1px_3px_0_rgba(0,0,0,0.14)] dark:border [&_svg]:size-5',
)} iconClass,
> )}
<CardDotField className="text-muted-foreground [mask-image:linear-gradient(to_bottom_left,black,transparent_60%)]" /> >
<div className="relative z-10 flex h-full flex-col gap-7.5"> <ItemMedia variant="icon" className="size-auto">
<Item {icon}
className={cn( </ItemMedia>
'border-background flex size-11 shrink-0 items-center justify-center border-2 p-0 shadow-[0_1px_3px_0_rgba(0,0,0,0.14)] dark:border [&_svg]:size-5', </Item>
iconClass, <div className="mt-auto flex flex-col gap-3">
)} <span className="text-foreground block text-sm leading-tight font-medium">{label}</span>
> <p className="text-muted-foreground text-xs leading-relaxed">{description}</p>
<ItemMedia variant="icon" className="size-auto"> <span className="text-primary inline-flex items-center gap-1 text-xs font-medium underline-offset-2 group-hover:underline">
{icon} Перейти
</ItemMedia> <ChevronRight
</Item> aria-hidden
<div className="mt-auto flex flex-col gap-3"> className="size-2.5 shrink-0 transition-transform group-hover:translate-x-0.5"
<span className="text-foreground block text-sm leading-tight font-medium">{label}</span> />
<p className="text-muted-foreground text-xs leading-relaxed">{description}</p> </span>
<span className="text-primary inline-flex items-center gap-1 text-xs font-medium underline-offset-2 group-hover:underline">
Перейти
<ChevronRight
aria-hidden
className="size-2.5 shrink-0 transition-transform group-hover:translate-x-0.5"
/>
</span>
</div>
</div> </div>
</FramePanel> </div>
</Link> </FramePanel>
</Frame> </Link>
) )
} }
@@ -2,6 +2,12 @@ import { Gauge, Network, Play, Plus, Share2, Tags } from 'lucide-react'
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import { DashboardQuickLinkCard } from '@/components/dashboard/dashboard-quick-link-card' import { DashboardQuickLinkCard } from '@/components/dashboard/dashboard-quick-link-card'
import {
Frame,
FrameDescription,
FrameHeader,
FrameTitle,
} from '@/components/reui/frame'
type QuickLink = { type QuickLink = {
icon: ReactNode icon: ReactNode
@@ -63,12 +69,16 @@ const LINKS: QuickLink[] = [
export function DashboardQuickLinks() { export function DashboardQuickLinks() {
return ( return (
<div className="@container w-full"> <Frame className="@container w-full">
<div className="grid gap-3 sm:grid-cols-2 @2xl:grid-cols-3"> <FrameHeader>
<FrameTitle>Быстрые действия</FrameTitle>
<FrameDescription>Частые переходы к настройке и деплою</FrameDescription>
</FrameHeader>
<div className="grid gap-1 sm:grid-cols-2 xl:grid-cols-3">
{LINKS.map((link) => ( {LINKS.map((link) => (
<DashboardQuickLinkCard key={link.label} {...link} /> <DashboardQuickLinkCard key={link.label} {...link} />
))} ))}
</div> </div>
</div> </Frame>
) )
} }
+2 -8
View File
@@ -85,6 +85,8 @@ function DashboardComponent() {
} }
/> />
<DashboardQuickLinks />
{initialLoading ? ( {initialLoading ? (
<AnalyticsDashboardSkeleton /> <AnalyticsDashboardSkeleton />
) : ( ) : (
@@ -142,14 +144,6 @@ function DashboardComponent() {
)} )}
</DashboardFramePanel> </DashboardFramePanel>
</div> </div>
<DashboardFramePanel
title="Быстрые действия"
description="Частые переходы к настройке и деплою"
contentClassName="p-4"
>
<DashboardQuickLinks />
</DashboardFramePanel>
</div> </div>
) )
} }