refactor(web): streamline dashboard and tab components for improved layout
Refactored various dashboard components and tab structures to enhance layout and responsiveness. Adjusted class names to include 'min-w-0' for better overflow handling and improved alignment. Updated the ChartDonutMetric to include a tooltip for better data representation. Simplified the DashboardRecentJobsGrid and DashboardRecentRevisionsGrid by replacing DataGridSection with DataGridShell for consistency. Enhanced the OpsDashboard layout for better clarity and usability.
This commit is contained in:
@@ -2,6 +2,8 @@ import { Cell, Label, Pie, PieChart } from 'recharts'
|
||||
|
||||
import {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
type ChartConfig,
|
||||
} from '@evobgp/ui/components/chart'
|
||||
import { cn } from '@evobgp/ui/lib/utils'
|
||||
@@ -43,6 +45,7 @@ export function ChartDonutMetric({
|
||||
<div className={cn('flex flex-col items-center justify-start gap-4 sm:flex-row sm:gap-6', className)}>
|
||||
<ChartContainer config={chartConfig} className="mx-0 aspect-square h-44 w-44 shrink-0">
|
||||
<PieChart>
|
||||
<ChartTooltip content={<ChartTooltipContent nameKey="label" hideLabel />} />
|
||||
<Pie
|
||||
data={data}
|
||||
dataKey="count"
|
||||
|
||||
@@ -46,7 +46,7 @@ export function BadgeTabs({
|
||||
<TabsList
|
||||
variant="line"
|
||||
className={cn(
|
||||
'mb-3.5 w-full min-w-0 justify-start gap-4 overflow-x-auto',
|
||||
'mb-3.5 w-full min-w-0 justify-start gap-4',
|
||||
listClassName,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -3,13 +3,16 @@ import { useMemo, useState } from 'react'
|
||||
import { ChartDonutMetric } from '@/components/analytics/chart-donut-metric'
|
||||
import { AnalyticsSegmentControl } from '@/components/analytics/analytics-segment-control'
|
||||
import { PanelCard } from '@/components/panel-card'
|
||||
import { Separator } from '@evobgp/ui/components/separator'
|
||||
import { jobStatusBreakdown, moduleTypeBreakdown } from '@/lib/metrics'
|
||||
import type { JobRow, ModuleRow } from '@/types/api'
|
||||
|
||||
type FlowMode = 'jobs' | 'modules'
|
||||
|
||||
/** chart-13 inspired donut breakdown (Card surface). */
|
||||
/**
|
||||
* Donut distribution — chart-27 DNA: Frame + one legend (inside ChartDonutMetric).
|
||||
* @see https://reui.io/preview/base/chart-27
|
||||
* @see https://reui.io/docs/components/base/frame
|
||||
*/
|
||||
export function DashboardOperationsBreakdown({
|
||||
jobs,
|
||||
modules,
|
||||
@@ -32,7 +35,7 @@ export function DashboardOperationsBreakdown({
|
||||
return (
|
||||
<PanelCard
|
||||
title="Поток операций"
|
||||
description="Распределение задач и типов модулей"
|
||||
description="Доли статусов задач или типов модулей"
|
||||
actions={
|
||||
<AnalyticsSegmentControl
|
||||
value={mode}
|
||||
@@ -43,45 +46,16 @@ export function DashboardOperationsBreakdown({
|
||||
]}
|
||||
/>
|
||||
}
|
||||
className="h-full"
|
||||
className="min-w-0"
|
||||
contentClassName="px-5 py-4"
|
||||
>
|
||||
<div className="p-4">
|
||||
{loading ? (
|
||||
<div className="text-muted-foreground flex h-48 items-center justify-center text-sm">
|
||||
Загрузка…
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<ChartDonutMetric slices={slices} centerLabel={centerLabel} centerValue={total} />
|
||||
{slices.length > 0 ? (
|
||||
<ul className="mt-4 flex min-w-0 flex-col">
|
||||
{slices.map((slice, index) => {
|
||||
const pct = total > 0 ? ((slice.count / total) * 100).toFixed(1) : '0'
|
||||
return (
|
||||
<li key={slice.key}>
|
||||
<div className="grid grid-cols-[minmax(0,1fr)_auto_auto] items-center gap-3 py-2">
|
||||
<div className="flex min-w-0 items-center gap-2.5">
|
||||
<span
|
||||
aria-hidden
|
||||
className="border-background size-3 shrink-0 rounded-full border-2 shadow-sm"
|
||||
style={{ backgroundColor: slice.color }}
|
||||
/>
|
||||
<span className="truncate text-sm font-medium">{slice.label}</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium tabular-nums">{slice.count}</span>
|
||||
<span className="text-muted-foreground/70 w-10 text-right text-xs tabular-nums">
|
||||
{pct}%
|
||||
</span>
|
||||
</div>
|
||||
{index < slices.length - 1 ? <Separator className="w-auto" /> : null}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{loading ? (
|
||||
<div className="text-muted-foreground flex h-44 items-center justify-center text-sm">
|
||||
Загрузка…
|
||||
</div>
|
||||
) : (
|
||||
<ChartDonutMetric slices={slices} centerLabel={centerLabel} centerValue={total} />
|
||||
)}
|
||||
</PanelCard>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ColumnDef } from '@tanstack/react-table'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { DataGridPrimaryCell } from '@/components/data-grid-cell'
|
||||
import { DataGridSection } from '@/components/data-grid-shell'
|
||||
import { DataGridShell } from '@/components/data-grid-shell'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
|
||||
import { DATA_GRID_DENSE_LAYOUT } from '@/lib/data-grid-defaults'
|
||||
@@ -49,7 +49,7 @@ export function DashboardRecentJobsGrid({
|
||||
[nameById],
|
||||
)
|
||||
|
||||
const { table, globalFilter, setGlobalFilter, filteredCount } = useClientDataGrid({
|
||||
const { table, filteredCount } = useClientDataGrid({
|
||||
data,
|
||||
columns,
|
||||
getSearchText: (row) => {
|
||||
@@ -63,16 +63,13 @@ export function DashboardRecentJobsGrid({
|
||||
})
|
||||
|
||||
return (
|
||||
<DataGridSection
|
||||
<DataGridShell
|
||||
table={table}
|
||||
recordCount={filteredCount}
|
||||
isLoading={isLoading}
|
||||
emptyMessage="Нет задач"
|
||||
showPagination={false}
|
||||
tableLayout={DATA_GRID_DENSE_LAYOUT}
|
||||
searchValue={globalFilter}
|
||||
onSearchChange={setGlobalFilter}
|
||||
searchPlaceholder="Поиск задач…"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ColumnDef } from '@tanstack/react-table'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { DataGridMutedCell, DataGridPrimaryCell } from '@/components/data-grid-cell'
|
||||
import { DataGridSection } from '@/components/data-grid-shell'
|
||||
import { DataGridShell } from '@/components/data-grid-shell'
|
||||
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
|
||||
import { DATA_GRID_DENSE_LAYOUT } from '@/lib/data-grid-defaults'
|
||||
import { useClientDataGrid } from '@/hooks/use-client-data-grid'
|
||||
@@ -42,7 +42,7 @@ export function DashboardRecentRevisionsGrid({
|
||||
[],
|
||||
)
|
||||
|
||||
const { table, globalFilter, setGlobalFilter, filteredCount } = useClientDataGrid({
|
||||
const { table, filteredCount } = useClientDataGrid({
|
||||
data,
|
||||
columns,
|
||||
getSearchText: (row) => row.id,
|
||||
@@ -51,16 +51,13 @@ export function DashboardRecentRevisionsGrid({
|
||||
})
|
||||
|
||||
return (
|
||||
<DataGridSection
|
||||
<DataGridShell
|
||||
table={table}
|
||||
recordCount={filteredCount}
|
||||
isLoading={isLoading}
|
||||
emptyMessage="Нет ревизий"
|
||||
showPagination={false}
|
||||
tableLayout={DATA_GRID_DENSE_LAYOUT}
|
||||
searchValue={globalFilter}
|
||||
onSearchChange={setGlobalFilter}
|
||||
searchPlaceholder="Поиск ревизий…"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import {
|
||||
Frame,
|
||||
FrameDescription,
|
||||
FrameHeader,
|
||||
FramePanel,
|
||||
FrameTitle,
|
||||
} from '@/components/reui/frame'
|
||||
import { Skeleton } from '@evobgp/ui/components/skeleton'
|
||||
import { PageHeader } from '@/components/page-header'
|
||||
import { KpiStatGrid, type KpiStatCardData } from './kpi-stat-grid'
|
||||
@@ -47,9 +40,11 @@ function OpsDashboardSkeleton() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ops dashboard: KPI → optional Quick Actions → charts → attention queue.
|
||||
* Ops dashboard: KPI → optional Quick Actions → charts → queue.
|
||||
* Queue is a sibling Frame grid — never wrap FrameDataGrid inside another Frame.
|
||||
* @see https://reui.io/preview/base/dashboard-1
|
||||
* @see https://reui.io/preview/base/stats-12
|
||||
* @see https://reui.io/docs/components/base/frame
|
||||
*/
|
||||
export function OpsDashboard({
|
||||
title = 'Панель управления',
|
||||
@@ -77,21 +72,18 @@ export function OpsDashboard({
|
||||
|
||||
{afterKpi ? <section aria-label="Быстрые действия">{afterKpi}</section> : null}
|
||||
|
||||
<section
|
||||
aria-label="Аналитика"
|
||||
className="flex min-w-0 flex-col gap-4"
|
||||
>
|
||||
<section aria-label="Аналитика" className="flex min-w-0 flex-col gap-4">
|
||||
{charts}
|
||||
</section>
|
||||
|
||||
<section aria-label={queueTitle}>
|
||||
<Frame dense spacing="sm" className="w-full min-w-0">
|
||||
<FrameHeader>
|
||||
<FrameTitle>{queueTitle}</FrameTitle>
|
||||
<FrameDescription>{queueDescription}</FrameDescription>
|
||||
</FrameHeader>
|
||||
<FramePanel>{queue}</FramePanel>
|
||||
</Frame>
|
||||
<section aria-label={queueTitle} className="flex min-w-0 flex-col gap-4">
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<h2 className="text-sm font-semibold tracking-tight">{queueTitle}</h2>
|
||||
{queueDescription ? (
|
||||
<p className="text-muted-foreground max-w-prose text-sm">{queueDescription}</p>
|
||||
) : null}
|
||||
</div>
|
||||
{queue}
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user