refactor(web): streamline component structure by removing unused refs and logging
Build, Test, and Push CFDM Docker Image / test (push) Successful in 4m34s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 1m46s
Build, Test, and Push CFDM Docker Image / create-release (push) Skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 5s

- Removed unnecessary useRef and useEffect hooks from PageShell, OpsDashboard, ResourcePage, SettingsShell, and other components to simplify code and improve maintainability.
- Updated components to utilize direct className assignments for layout without relying on refs for width logging.
- Introduced CountedLineTabs in various components to enhance tab functionality and improve user experience.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-07-17 12:07:12 +07:00
co-authored by Cursor
parent 1f710bbe8b
commit a02f4f36a7
7 changed files with 87 additions and 136 deletions
@@ -0,0 +1,46 @@
import { Tabs, TabsList, TabsTrigger } from '@cfdm/ui/components/tabs'
import { cn } from '@cfdm/ui/lib/utils'
export interface CountedLineTab {
id: string
label: string
count?: number
}
interface CountedLineTabsProps {
tabs: CountedLineTab[]
value: string
onValueChange: (value: string) => void
className?: string
listClassName?: string
}
/** Line tabs with count pills (c-tabs-2 / data-grid-filtering-2). */
export function CountedLineTabs({
tabs,
value,
onValueChange,
className,
listClassName,
}: CountedLineTabsProps) {
return (
<Tabs value={value} onValueChange={onValueChange} className={className}>
<TabsList variant="line" className={cn('gap-5', listClassName)}>
{tabs.map((tab) => (
<TabsTrigger
key={tab.id}
value={tab.id}
className="text-muted-foreground hover:text-foreground h-auto gap-2 px-0 pb-3 after:bottom-0"
>
<span>{tab.label}</span>
{tab.count !== undefined ? (
<span className="bg-muted text-muted-foreground inline-flex min-w-5 items-center justify-center rounded-md px-1.5 py-0.5 text-xs tabular-nums">
{tab.count}
</span>
) : null}
</TabsTrigger>
))}
</TabsList>
</Tabs>
)
}
+2 -33
View File
@@ -1,7 +1,5 @@
import { useEffect, useRef, type ReactNode } from 'react'
import { useRouterState } from '@tanstack/react-router'
import type { ReactNode } from 'react'
import { cn } from '@cfdm/ui/lib/utils'
import { DEBUG_BUILD_STAMP, debugAgentLog } from '@/lib/debug-agent-log'
interface PageShellProps {
children: ReactNode
@@ -9,37 +7,8 @@ interface PageShellProps {
}
export function PageShell({ children, className }: PageShellProps) {
const ref = useRef<HTMLDivElement>(null)
const pathname = useRouterState({ select: (s) => s.location.pathname })
useEffect(() => {
const el = ref.current
if (!el) return
const main = el.closest('main')
const mainWidth = main?.clientWidth ?? 0
const shellWidth = el.clientWidth
const child = el.firstElementChild as HTMLElement | null
const childWidth = child?.clientWidth ?? 0
const childMaxWidth = child ? getComputedStyle(child).maxWidth : 'none'
debugAgentLog(
'page-shell.tsx:mount',
'page layout widths',
{
buildStamp: DEBUG_BUILD_STAMP,
pathname,
mainWidth,
shellWidth,
childWidth,
childMaxWidth,
shellClass: el.className,
childClass: child?.className ?? null,
},
'A',
)
}, [pathname])
return (
<div ref={ref} className={cn('flex flex-col gap-4 md:gap-6', className)}>
<div className={cn('flex flex-col gap-4 md:gap-6', className)}>
{children}
</div>
)
@@ -1,4 +1,4 @@
import { useEffect, useRef, type ReactNode } from 'react'
import type { ReactNode } from 'react'
import {
Frame,
FrameDescription,
@@ -7,7 +7,6 @@ import {
FrameTitle,
} from '@/components/reui/frame'
import { Skeleton } from '@cfdm/ui/components/skeleton'
import { debugAgentLog } from '@/lib/debug-agent-log'
import { KpiStatGrid, type KpiStatCard } from './kpi-stat-grid'
export type OpsKpiCard = KpiStatCard
@@ -21,9 +20,12 @@ interface OpsDashboardProps {
isLoading?: boolean
}
const rootClassName =
'text-foreground @container flex w-full flex-col gap-2 md:gap-3'
function OpsDashboardSkeleton() {
return (
<div className="text-foreground @container mx-auto flex w-full max-w-7xl flex-col gap-2 md:gap-3">
<div className={rootClassName}>
<header className="px-1">
<Skeleton className="h-7 w-56" />
<Skeleton className="mt-2 h-4 w-80 max-w-full" />
@@ -46,33 +48,12 @@ export function OpsDashboard({
queue,
isLoading = false,
}: OpsDashboardProps) {
const rootRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (isLoading) return
const el = rootRef.current
if (!el) return
debugAgentLog(
'ops-dashboard.tsx:mount',
'ops dashboard width',
{
clientWidth: el.clientWidth,
maxWidth: getComputedStyle(el).maxWidth,
className: el.className,
},
'A',
)
}, [isLoading])
if (isLoading) {
return <OpsDashboardSkeleton />
}
return (
<div
ref={rootRef}
className="text-foreground @container mx-auto flex w-full max-w-7xl flex-col gap-2 md:gap-3"
>
<div className={rootClassName}>
<header className="px-1">
<div className="flex flex-col gap-px">
<h1 className="text-xl font-semibold tracking-tight">{title}</h1>
@@ -1,4 +1,4 @@
import { useCallback, useMemo, useState, useEffect, useRef, type ReactNode } from 'react'
import { useCallback, useMemo, useState, type ReactNode } from 'react'
import {
getCoreRowModel,
getPaginationRowModel,
@@ -11,6 +11,7 @@ import {
} from '@tanstack/react-table'
import { FilterIcon, FunnelXIcon } from 'lucide-react'
import { CountedLineTabs } from '@/components/counted-line-tabs'
import { Badge } from '@/components/reui/badge'
import { DataGrid } from '@/components/reui/data-grid/data-grid'
import { DataGridPagination } from '@/components/reui/data-grid/data-grid-pagination'
@@ -32,11 +33,9 @@ import {
import { Button } from '@cfdm/ui/components/button'
import { Separator } from '@cfdm/ui/components/separator'
import { Skeleton } from '@cfdm/ui/components/skeleton'
import { Tabs, TabsList, TabsTrigger } from '@cfdm/ui/components/tabs'
import { Alert, AlertDescription, AlertTitle } from '@cfdm/ui/components/alert'
import { EmptyState } from '@/components/empty-state'
import { applyFiltersToData } from './filter-utils'
import { debugAgentLog } from '@/lib/debug-agent-log'
export interface ResourcePageTab {
id: string
@@ -122,7 +121,6 @@ export function ResourcePage<T extends object>({
toolbarExtra,
hideHeader = false,
}: ResourcePageProps<T>) {
const frameRef = useRef<HTMLDivElement>(null)
const [internalTab, setInternalTab] = useState(tabs?.[0]?.id ?? 'all')
const activeTab = controlledTab ?? internalTab
@@ -207,21 +205,15 @@ export function ResourcePage<T extends object>({
resetPagination()
}, [onClearFilters, resetPagination])
useEffect(() => {
if (isLoading || isError || (data.length === 0 && emptyState)) return
const el = frameRef.current
if (!el) return
debugAgentLog(
'resource-page.tsx:mount',
'resource page width',
{
title,
clientWidth: el.clientWidth,
maxWidth: getComputedStyle(el).maxWidth,
},
'A',
)
}, [title, isLoading, isError, data.length, emptyState])
const countedTabs = useMemo(
() =>
(tabs ?? []).map((tab) => ({
id: tab.id,
label: tab.label,
count: tabCounts[tab.id] ?? tab.count ?? 0,
})),
[tabs, tabCounts],
)
if (isLoading) {
return <ResourcePageSkeleton />
@@ -256,7 +248,7 @@ export function ResourcePage<T extends object>({
const emptyMessage = 'Нет записей по выбранным фильтрам.'
return (
<div ref={frameRef} className="w-full">
<div className="w-full">
{selectionToolbar && selectedCount > 0
? selectionToolbar({
selectedIds,
@@ -306,26 +298,14 @@ export function ResourcePage<T extends object>({
) : null}
<FramePanel className="p-0 shadow-none!">
{tabs && tabs.length > 0 ? (
{countedTabs.length > 0 ? (
<>
{/* Line tabs: c-tabs-2 underline; after:bottom-0 — FramePanel overflow-hidden клипает bottom-[-5px] */}
<div className="px-(--frame-panel-header-px) pt-(--frame-panel-header-py)">
<Tabs value={activeTab} onValueChange={handleTabChange}>
<TabsList variant="line" className="gap-5">
{tabs.map((tab) => (
<TabsTrigger
key={tab.id}
value={tab.id}
className="text-muted-foreground hover:text-foreground h-auto gap-2 px-0 pb-3 after:bottom-0"
>
<span>{tab.label}</span>
<span className="bg-muted text-muted-foreground inline-flex min-w-5 items-center justify-center rounded-md px-1.5 py-0.5 text-xs tabular-nums">
{tabCounts[tab.id] ?? tab.count ?? 0}
</span>
</TabsTrigger>
))}
</TabsList>
</Tabs>
<CountedLineTabs
tabs={countedTabs}
value={activeTab}
onValueChange={handleTabChange}
/>
</div>
<Separator />
</>
@@ -1,11 +1,10 @@
import { useEffect, useRef, type ReactNode } from 'react'
import type { ReactNode } from 'react'
import { Link, Outlet, useRouterState } from '@tanstack/react-router'
import { SettingsIcon } from 'lucide-react'
import { useIsMobile } from '@cfdm/ui/hooks/use-mobile'
import { cn } from '@cfdm/ui/lib/utils'
import { PageShell } from '@/components/page-shell'
import { debugAgentLog } from '@/lib/debug-agent-log'
export interface SettingsTabConfig {
id: string
@@ -35,28 +34,11 @@ export function SettingsShell({
tabs = DEFAULT_TABS,
}: SettingsShellProps) {
const isMobile = useIsMobile()
const rootRef = useRef<HTMLDivElement>(null)
const pathname = useRouterState({ select: (s) => s.location.pathname })
useEffect(() => {
const el = rootRef.current
if (!el) return
debugAgentLog(
'settings-shell.tsx:mount',
'settings shell width',
{
pathname,
clientWidth: el.clientWidth,
maxWidth: getComputedStyle(el).maxWidth,
className: el.className,
},
'A',
)
}, [pathname])
return (
<PageShell>
<div ref={rootRef} className="mx-auto flex w-full max-w-4xl flex-col gap-6">
<div className="mx-auto flex w-full max-w-4xl flex-col gap-6">
<header className="px-1">
<div className="flex flex-col gap-px">
<h1 className="text-xl font-semibold tracking-tight">{title}</h1>
@@ -78,7 +60,7 @@ export function SettingsShell({
className={cn(
'flex gap-1',
isMobile
? 'scrollbar-none -mx-1 overflow-x-auto pb-1'
? 'scrollbar-none -mx-1 overflow-x-auto overflow-y-hidden pb-1'
: 'w-44 shrink-0 flex-col',
)}
>
@@ -27,6 +27,7 @@ import {
FramePanel,
FrameTitle,
} from '@/components/reui/frame'
import { CountedLineTabs } from '@/components/counted-line-tabs'
import { EmptyState } from '@/components/empty-state'
import { applyFiltersToData } from '@/components/reui-kit/filter-utils'
import { createServicesGroupedColumns } from '@/components/services/services-grouped-columns'
@@ -53,7 +54,6 @@ import {
} from '@cfdm/ui/components/dropdown-menu'
import { Separator } from '@cfdm/ui/components/separator'
import { Skeleton } from '@cfdm/ui/components/skeleton'
import { Tabs, TabsList, TabsTrigger } from '@cfdm/ui/components/tabs'
const HEALTH_TABS = [
{ id: 'health-ok', label: 'OK' },
@@ -359,22 +359,15 @@ export function ServicesGroupedCatalog({
<FramePanel className="p-0 shadow-none!">
<div className="px-(--frame-panel-header-px) pt-(--frame-panel-header-py)">
<Tabs value={tab} onValueChange={setTab}>
<TabsList variant="line" className="gap-5 overflow-x-auto">
{ALL_TABS.map((t) => (
<TabsTrigger
key={t.id}
value={t.id}
className="text-muted-foreground hover:text-foreground h-auto gap-2 px-0 pb-3 after:bottom-0"
>
<span>{t.label}</span>
<span className="bg-muted text-muted-foreground inline-flex min-w-5 items-center justify-center rounded-md px-1.5 py-0.5 text-xs tabular-nums">
{tabCounts[t.id] ?? 0}
</span>
</TabsTrigger>
))}
</TabsList>
</Tabs>
<CountedLineTabs
tabs={ALL_TABS.map((t) => ({
id: t.id,
label: t.label,
count: tabCounts[t.id] ?? 0,
}))}
value={tab}
onValueChange={setTab}
/>
</div>
<Separator />
+2 -2
View File
@@ -67,8 +67,8 @@ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
"group-data-[variant=line]/tabs-list:flex-none group-data-[variant=line]/tabs-list:rounded-none group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:shadow-none group-data-[variant=line]/tabs-list:data-active:bg-transparent group-data-[variant=line]/tabs-list:data-active:shadow-none dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
"group-data-[variant=default]/tabs-list:data-active:bg-background group-data-[variant=default]/tabs-list:data-active:text-foreground dark:group-data-[variant=default]/tabs-list:data-active:border-input dark:group-data-[variant=default]/tabs-list:data-active:bg-input/30 dark:group-data-[variant=default]/tabs-list:data-active:text-foreground",
"data-active:text-foreground",
// Line underline (c-tabs-2): size/position need data-horizontal on root; active → opacity-100
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
// Line underline (c-tabs-2): bottom-0 so FramePanel overflow-hidden does not clip
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-0 group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
className
)}
{...props}