Compare commits

..
1 Commits
Author SHA1 Message Date
DenozordecandCursor 357ed4ce7b refactor(web): enhance layout and styling for dashboard components
CI / changes (push) Successful in 12s
CI / commitlint (push) Skipped
CI / go (push) Skipped
CI / bird2 (push) Skipped
CI / openapi (push) Successful in 1m18s
CI / web (push) Successful in 2m0s
CI / release (push) Successful in 6m51s
Updated various dashboard components to improve layout and responsiveness. Adjusted class names to include 'min-w-0' for better handling of overflow and added flex properties to ensure proper alignment. Refined grid structures and skeleton loading states for a more cohesive user experience.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 00:51:10 +07:00
9 changed files with 54 additions and 42 deletions
@@ -50,7 +50,7 @@ export function DashboardActivityTimeline({
<DashboardFramePanel
title="Недавняя активность"
description="Задачи, ревизии и сетевые события"
className="h-full"
className="h-full min-w-0"
>
{loading ? (
<p className="text-muted-foreground px-4 py-6 text-sm">Загрузка</p>
@@ -82,7 +82,7 @@ export function DashboardActivityTimeline({
</TimelineIndicator>
</TimelineHeader>
<TimelineContent className="min-w-0 pb-1 text-foreground">
<TimelineTitle className="text-sm leading-snug font-normal">
<TimelineTitle className="text-sm leading-snug font-normal break-words">
{item.message}
</TimelineTitle>
<div className="mt-2">
@@ -119,9 +119,10 @@ export function DashboardModulesGrid({
cell: ({ row }) => (
<div className="flex min-w-0 items-center gap-2">
<BoxesIcon className="text-muted-foreground size-4 shrink-0" aria-hidden />
<DataGridPrimaryCell title={row.original.name} accent="primary" className="max-w-[240px]" />
<DataGridPrimaryCell title={row.original.name} accent="primary" />
</div>
),
minSize: 180,
meta: { headerTitle: 'Модуль' },
},
{
@@ -164,6 +165,7 @@ export function DashboardModulesGrid({
<DashboardFramePanel
title="Модули"
description="Поиск, сортировка и быстрый переход к настройке"
className="min-w-0"
actions={
<Button variant="outline" size="sm" render={<Link to="/modules/new" />}>
<PlusIcon />
@@ -183,14 +185,14 @@ export function DashboardModulesGrid({
columnsVisibility: false,
columnsResizable: false,
columnsMovable: false,
width: 'fixed',
width: 'auto',
}}
tableClassNames={{ bodyRow: 'group/module-row cursor-pointer [&>td]:h-14' }}
onRowClick={(row) => void navigate({ to: '/modules/$moduleId', params: { moduleId: row.id } })}
>
<div className="flex flex-col">
<div className="flex flex-col gap-3 border-b px-4 py-3 lg:flex-row lg:items-center lg:justify-between">
<InputGroup className="w-full min-w-40 sm:max-w-xs">
<div className="flex flex-col gap-3 border-b px-4 py-3 @3xl:flex-row @3xl:items-center @3xl:justify-between">
<InputGroup className="w-full min-w-0 @3xl:max-w-xs">
<InputGroupAddon align="inline-start">
<SearchIcon className="text-muted-foreground size-4" aria-hidden />
</InputGroupAddon>
+1 -1
View File
@@ -54,7 +54,7 @@ export function PanelCard({
<Frame
dense
spacing={spacing}
className={cn(panelCardClassName, className)}
className={cn(panelCardClassName, 'min-w-0', className)}
>
<FramePanel className="flex flex-col gap-0 p-0 shadow-xs">
{hasHeader ? (
+7 -4
View File
@@ -1,10 +1,13 @@
/** Shared grid column classes for hybrid KPI / Quick Actions tiles. */
/**
* Shared grid column classes for hybrid KPI / Quick Actions tiles.
* Container queries only — never 56 columns on laptop width (tiles overlap).
* @see https://reui.io/preview/base/stats-12
*/
export function kpiCols(count: number): string {
if (count <= 1) return 'grid-cols-1'
if (count === 2) return 'grid-cols-1 @xl:grid-cols-2'
if (count === 3) return 'grid-cols-1 @3xl:grid-cols-3'
if (count === 4) return 'grid-cols-1 @3xl:grid-cols-2 @6xl:grid-cols-4'
if (count === 5) return 'grid-cols-2 @3xl:grid-cols-3 xl:grid-cols-5'
if (count === 6) return 'grid-cols-2 sm:grid-cols-3 xl:grid-cols-6'
return 'grid-cols-1 sm:grid-cols-2 xl:grid-cols-4'
if (count <= 6) return 'grid-cols-1 @xl:grid-cols-2 @4xl:grid-cols-3'
return 'grid-cols-1 @xl:grid-cols-2 @4xl:grid-cols-3 @6xl:grid-cols-4'
}
@@ -27,7 +27,7 @@ interface OpsDashboardProps {
}
const rootClassName =
'text-foreground @container flex w-full flex-col gap-2 md:gap-3'
'text-foreground @container flex w-full min-w-0 flex-col gap-4 md:gap-6'
function OpsDashboardSkeleton() {
return (
@@ -37,7 +37,7 @@ function OpsDashboardSkeleton() {
<Skeleton className="mt-2 h-4 w-80 max-w-full" />
</header>
<KpiStatGrid cards={[]} isLoading skeletonCount={4} />
<div className="grid gap-2 @3xl:grid-cols-2">
<div className="flex min-w-0 flex-col gap-4">
<Skeleton className="h-64 w-full rounded-xl" />
<Skeleton className="h-64 w-full rounded-xl" />
</div>
@@ -79,13 +79,13 @@ export function OpsDashboard({
<section
aria-label="Аналитика"
className="grid min-w-0 items-start gap-2 @3xl:grid-cols-2"
className="flex min-w-0 flex-col gap-4"
>
{charts}
</section>
<section aria-label={queueTitle}>
<Frame dense spacing="sm" className="w-full">
<Frame dense spacing="sm" className="w-full min-w-0">
<FrameHeader>
<FrameTitle>{queueTitle}</FrameTitle>
<FrameDescription>{queueDescription}</FrameDescription>
+3 -3
View File
@@ -39,11 +39,11 @@ export function AnalyticsDashboardSkeleton() {
</div>
<div className={dashboardMainSidebarClassName}>
<Skeleton className="h-96 w-full rounded-xl xl:col-span-8" />
<Skeleton className="h-96 w-full rounded-xl xl:col-span-4" />
<Skeleton className="h-96 w-full min-w-0 rounded-xl" />
<Skeleton className="h-96 w-full min-w-0 rounded-xl" />
</div>
<div className="grid gap-4 lg:grid-cols-2">
<div className={chartPanelGridClassName}>
<Skeleton className="h-64 w-full rounded-xl" />
<Skeleton className="h-64 w-full rounded-xl" />
</div>
+1
View File
@@ -44,6 +44,7 @@ export const DATA_GRID_MESSAGES_RU = {
export const DATA_GRID_DENSE_LAYOUT: NonNullable<DataGridProps<object>['tableLayout']> = {
...DATA_GRID_TABLE_LAYOUT,
dense: true,
width: 'auto',
}
export function createTextGlobalFilter<TData extends object>(
+9 -3
View File
@@ -30,7 +30,13 @@ export const kpiGridClassName =
export const kpiCardContentClassName = 'flex flex-col items-start gap-4 p-5'
/** Two-column chart panel row (monitoring / network overview). */
export const chartPanelGridClassName = 'grid grid-cols-1 gap-4 @5xl:grid-cols-2'
export const chartPanelGridClassName =
'grid min-w-0 grid-cols-1 items-start gap-4 @5xl:grid-cols-2'
/** Main + sidebar layout (8+4) used on dashboard. */
export const dashboardMainSidebarClassName = 'grid gap-4 xl:grid-cols-12 xl:items-start'
/**
* Main + sidebar: stack until the dashboard container is wide enough.
* Do not nest this inside another 2-col grid — that squeezes modules/timeline.
* @see https://reui.io/preview/base/dashboard-1
*/
export const dashboardMainSidebarClassName =
'grid min-w-0 grid-cols-1 items-start gap-4 @5xl:grid-cols-2'
+21 -21
View File
@@ -37,7 +37,9 @@ function parseShowQuickActions(value: unknown): boolean {
}
/**
* Dashboard — OpsDashboard kit (KPI → QuickActions → charts → queue).
* Dashboard — OpsDashboard kit (KPI → QuickActions → modules → activity/health → queue).
* Charts slot is a vertical stack: modules stay full-width; activity shares a row
* with BGP widgets only at @5xl (container), never nested 8+4 inside a 2-col parent.
* @see https://reui.io/preview/base/dashboard-1
* @see https://reui.io/preview/base/stats-12
*/
@@ -99,33 +101,31 @@ function DashboardComponent() {
afterKpi={showQuickActions ? <DashboardQuickLinks /> : null}
charts={
<>
<div className={dashboardMainSidebarClassName}>
<div className="xl:col-span-8">
<DashboardModulesGrid modules={modules} isLoading={refreshing} />
</div>
<div className="xl:col-span-4">
<DashboardActivityTimeline
jobs={jobs}
revisions={revisions}
peers={peers}
speakers={speakers}
/>
</div>
<div className="min-w-0">
<DashboardModulesGrid modules={modules} isLoading={refreshing} />
</div>
<div className={chartPanelGridClassName}>
<DashboardNetworkHealth peers={peers} speakers={speakers} jobs={jobs} />
<DashboardOperationsBreakdown jobs={jobs} modules={modules} />
<div className={dashboardMainSidebarClassName}>
<DashboardActivityTimeline
jobs={jobs}
revisions={revisions}
peers={peers}
speakers={speakers}
/>
<div className="grid min-w-0 gap-4">
<DashboardNetworkHealth peers={peers} speakers={speakers} jobs={jobs} />
<DashboardOperationsBreakdown jobs={jobs} modules={modules} />
</div>
</div>
</>
}
queueTitle="Недавняя активность"
queueDescription="Задачи и ревизии плоскости управления"
queueTitle="Задачи и ревизии"
queueDescription="Последние фоновые операции и история конфигураций"
queue={
<div className="grid gap-4 lg:grid-cols-2">
<div className={chartPanelGridClassName}>
<DashboardFramePanel
title="Недавние задачи"
description="Последние фоновые операции"
className="h-full"
className="h-full min-w-0"
>
{activityLoading ? (
<Skeleton className="m-4 h-24 w-auto" />
@@ -136,7 +136,7 @@ function DashboardComponent() {
<DashboardFramePanel
title="Последние ревизии"
description="История конфигураций"
className="h-full"
className="h-full min-w-0"
>
{activityLoading ? (
<Skeleton className="m-4 h-24 w-auto" />