Files
EvoBGP/apps/web/src/components/dashboard/dashboard-prefix-trend.tsx
T
DenozordecandClaude Sonnet 5.5 3b79d834cd
quality / changes (push) Successful in 8s
quality / go (push) Skipped
quality / bird2 (push) Skipped
quality / commitlint (push) Skipped
quality / docker-check (push) Skipped
quality / openapi (push) Successful in 26s
quality / web (push) Successful in 1m24s
CD / quality (push) Successful in 2m0s
CD / publish (push) Successful in 3m7s
feat(web): dashboard as on the shared mockup board
Панель переписана по доске BgpDashboard: действия в шапке, KPI-полоса, вкладка «Обзор», быстрые действия карточками, график префиксов по последним ревизиям, таблица модулей и лента активности. Графики загрузки BGP и потока операций убраны с панели — они живут в «Сети» и «Мониторинге».

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-23 22:38:11 +07:00

56 lines
2.9 KiB
TypeScript

import { useMemo } from 'react'
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from '@evobgp/ui/components/chart'
import { Frame, FrameDescription, FrameHeader, FramePanel, FrameTitle } from '@/components/reui/frame'
import { fmtWhen } from '@/components/portal/format'
import type { RevisionRow } from '@/types/api'
const chartConfig = {
prefixes: { label: 'Префиксов', color: 'var(--chart-1)' },
} satisfies ChartConfig
/** Префиксов в опубликованной конфигурации по последним ревизиям (доска «Панель»). */
export function DashboardPrefixTrend({ revisions, loading }: { revisions: RevisionRow[]; loading?: boolean }) {
const data = useMemo(
() =>
[...revisions]
.sort((a, b) => a.created_at.localeCompare(b.created_at))
.slice(-12)
.map((r) => ({ id: r.id, when: fmtWhen(r.created_at), prefixes: r.materialized_prefix_count })),
[revisions],
)
return (
<Frame className="[--frame-panel-header-py-adjust:4px] w-full min-w-0">
<FrameHeader className="flex-col items-start gap-0.5">
<FrameTitle>Префиксов в опубликованной конфигурации</FrameTitle>
<FrameDescription className="text-pretty">Последние {data.length || 12} ревизий</FrameDescription>
</FrameHeader>
<FramePanel className="p-3">
{data.length < 2 ? (
<div className="text-muted-foreground flex h-48 items-center justify-center text-sm">
{loading ? 'Загрузка…' : 'Недостаточно ревизий для графика'}
</div>
) : (
<ChartContainer config={chartConfig} className="aspect-auto h-64 w-full">
<AreaChart data={data} margin={{ left: 4, right: 12, top: 8 }}>
<defs>
<linearGradient id="prefix-fill" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="var(--color-prefixes)" stopOpacity={0.35} />
<stop offset="95%" stopColor="var(--color-prefixes)" stopOpacity={0.02} />
</linearGradient>
</defs>
<CartesianGrid vertical={false} strokeDasharray="3 3" />
<XAxis dataKey="id" tickLine={false} axisLine={false} tickMargin={8} minTickGap={24} />
<YAxis domain={([min, max]) => [Math.floor(min * 0.98), Math.ceil(max * 1.01)]} tickLine={false} axisLine={false} width={48} tickFormatter={(v: number) => v.toLocaleString('ru-RU')} />
<ChartTooltip content={<ChartTooltipContent labelFormatter={(_, p) => p?.[0]?.payload?.when ?? ''} />} />
<Area dataKey="prefixes" type="monotone" stroke="var(--color-prefixes)" strokeWidth={2} fill="url(#prefix-fill)" />
</AreaChart>
</ChartContainer>
)}
</FramePanel>
</Frame>
)
}