From a02f4f36a762c212e83221e49bdd1d2f7c8893e2 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Fri, 17 Jul 2026 12:07:12 +0700 Subject: [PATCH] refactor(web): streamline component structure by removing unused refs and logging - 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 --- apps/web/src/components/counted-line-tabs.tsx | 46 +++++++++++++++ apps/web/src/components/page-shell.tsx | 35 +----------- .../src/components/reui-kit/ops-dashboard.tsx | 31 ++-------- .../src/components/reui-kit/resource-page.tsx | 56 ++++++------------- .../components/reui-kit/settings-shell.tsx | 24 +------- .../services/services-grouped-catalog.tsx | 27 ++++----- packages/ui/src/components/tabs.tsx | 4 +- 7 files changed, 87 insertions(+), 136 deletions(-) create mode 100644 apps/web/src/components/counted-line-tabs.tsx diff --git a/apps/web/src/components/counted-line-tabs.tsx b/apps/web/src/components/counted-line-tabs.tsx new file mode 100644 index 0000000..4d4788e --- /dev/null +++ b/apps/web/src/components/counted-line-tabs.tsx @@ -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.map((tab) => ( + + {tab.label} + {tab.count !== undefined ? ( + + {tab.count} + + ) : null} + + ))} + + + ) +} diff --git a/apps/web/src/components/page-shell.tsx b/apps/web/src/components/page-shell.tsx index 0824993..10fbfdb 100644 --- a/apps/web/src/components/page-shell.tsx +++ b/apps/web/src/components/page-shell.tsx @@ -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(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 ( -
+
{children}
) diff --git a/apps/web/src/components/reui-kit/ops-dashboard.tsx b/apps/web/src/components/reui-kit/ops-dashboard.tsx index ebbc3ac..6a86c10 100644 --- a/apps/web/src/components/reui-kit/ops-dashboard.tsx +++ b/apps/web/src/components/reui-kit/ops-dashboard.tsx @@ -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 ( -
+
@@ -46,33 +48,12 @@ export function OpsDashboard({ queue, isLoading = false, }: OpsDashboardProps) { - const rootRef = useRef(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 } return ( -
+

{title}

diff --git a/apps/web/src/components/reui-kit/resource-page.tsx b/apps/web/src/components/reui-kit/resource-page.tsx index f9d9599..90c4084 100644 --- a/apps/web/src/components/reui-kit/resource-page.tsx +++ b/apps/web/src/components/reui-kit/resource-page.tsx @@ -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({ toolbarExtra, hideHeader = false, }: ResourcePageProps) { - const frameRef = useRef(null) const [internalTab, setInternalTab] = useState(tabs?.[0]?.id ?? 'all') const activeTab = controlledTab ?? internalTab @@ -207,21 +205,15 @@ export function ResourcePage({ 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 @@ -256,7 +248,7 @@ export function ResourcePage({ const emptyMessage = 'Нет записей по выбранным фильтрам.' return ( -
+
{selectionToolbar && selectedCount > 0 ? selectionToolbar({ selectedIds, @@ -306,26 +298,14 @@ export function ResourcePage({ ) : null} - {tabs && tabs.length > 0 ? ( + {countedTabs.length > 0 ? ( <> - {/* Line tabs: c-tabs-2 underline; after:bottom-0 — FramePanel overflow-hidden клипает bottom-[-5px] */}
- - - {tabs.map((tab) => ( - - {tab.label} - - {tabCounts[tab.id] ?? tab.count ?? 0} - - - ))} - - +
diff --git a/apps/web/src/components/reui-kit/settings-shell.tsx b/apps/web/src/components/reui-kit/settings-shell.tsx index 506f8ad..7ec36c7 100644 --- a/apps/web/src/components/reui-kit/settings-shell.tsx +++ b/apps/web/src/components/reui-kit/settings-shell.tsx @@ -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(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 ( -
+

{title}

@@ -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', )} > diff --git a/apps/web/src/components/services/services-grouped-catalog.tsx b/apps/web/src/components/services/services-grouped-catalog.tsx index eea4ba0..4caa9dc 100644 --- a/apps/web/src/components/services/services-grouped-catalog.tsx +++ b/apps/web/src/components/services/services-grouped-catalog.tsx @@ -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({
- - - {ALL_TABS.map((t) => ( - - {t.label} - - {tabCounts[t.id] ?? 0} - - - ))} - - + ({ + id: t.id, + label: t.label, + count: tabCounts[t.id] ?? 0, + }))} + value={tab} + onValueChange={setTab} + />
diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx index 8c914b0..c07b824 100644 --- a/packages/ui/src/components/tabs.tsx +++ b/packages/ui/src/components/tabs.tsx @@ -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}