fix(ui): ограничить группы и детали рекурсивных маршрутов

This commit is contained in:
Denozordec committed 2026-10-04 15:52:07 +07:00
1 parent f515818254
commit 9124d451a5
8 files changed
+127 -19

No files matched your search

@@ -1,8 +1,8 @@
"use client"
import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid"
import { DataGridContainer, DataGridScrollArea, DataGridTable, dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid"
import { useMemo, useState } from "react"
import { useEffect, useMemo, useRef, useState } from "react"
import {
type ColumnDef,
useTable,
@@ -67,9 +67,14 @@ function inferCountry(name: string): string | null {
}
function RouteGroupExpandedDetail({ group }: { group: RecursiveRouteGroup }) {
const sorted = [...group.endpoints].sort((a, b) => a.distance - b.distance)
const [page, setPage] = useState(0)
const sorted = useMemo(() => [...group.endpoints].sort((a, b) => a.distance - b.distance), [group.endpoints])
const lastPage = Math.max(0, Math.ceil(sorted.length / 50) - 1)
if (page > lastPage) setPage(lastPage)
const currentPage = Math.min(page, lastPage)
const offset = currentPage * 50
return (
<div className="flex flex-col gap-4 px-5 py-4 bg-muted/20">
<div role="region" aria-label={`Конечные точки: ${group.dstAddress} · ${group.routingTable}`} className="sticky left-0 w-full max-w-[calc(100vw-3rem)] md:max-w-none flex flex-col gap-4 px-5 py-4 bg-muted/20">
<div className="flex flex-wrap gap-x-6 gap-y-2 text-xs">
<span className="text-muted-foreground">
Route: <span className="font-mono text-foreground">{group.dstAddress}</span>
@@ -81,15 +86,15 @@ function RouteGroupExpandedDetail({ group }: { group: RecursiveRouteGroup }) {
{group.comment && <span className="text-muted-foreground italic">{group.comment}</span>}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2.5">
{sorted.map((ep, idx) => (
<div key={ep.id} className="rounded-lg border border-border bg-background px-4 py-3">
{sorted.slice(offset, offset + 50).map((ep, idx) => (
<div key={ep.id} data-recursive-endpoint={ep.id} className="rounded-lg border border-border bg-background px-4 py-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
{ep.country && (
<Flag code={ep.country} size={16} />
)}
<span className="text-[11px] font-semibold text-muted-foreground uppercase tracking-wide">
Endpoint {idx + 1}
Endpoint {offset + idx + 1}
</span>
</div>
<span className="text-[11px] font-mono">distance: {ep.distance}</span>
@@ -104,6 +109,11 @@ function RouteGroupExpandedDetail({ group }: { group: RecursiveRouteGroup }) {
</div>
))}
</div>
{sorted.length > 50 && <div className="flex flex-wrap items-center gap-2">
<span role="status" className="text-xs text-muted-foreground">Конечные точки {offset + 1}–{Math.min(offset + 50, sorted.length)} из {sorted.length}</span>
<Button type="button" variant="outline" className="min-h-11" aria-label="Предыдущие конечные точки" disabled={currentPage === 0} onClick={() => setPage(currentPage - 1)}>Назад</Button>
<Button type="button" variant="outline" className="min-h-11" aria-label="Следующие конечные точки" disabled={currentPage === lastPage} onClick={() => setPage(currentPage + 1)}>Далее</Button>
</div>}
</div>
)
}
@@ -139,6 +149,7 @@ function RecursiveRoutesDataGrid({
() => [
{
id: "route",
size: 176,
accessorKey: "dstAddress",
header: ({ column }) => (
<DataGridSortHeader column={column} title="Route / Comment" className="ml-1" />
@@ -147,7 +158,7 @@ function RecursiveRoutesDataGrid({
const g = row.original
const isExpanded = expandedKey === g.key
return (
<button type="button" aria-label={`Раскрыть маршрут ${g.dstAddress} · ${g.routingTable}`} aria-expanded={isExpanded} onClick={event => { event.stopPropagation(); onExpandedChange?.(isExpanded ? null : g.key) }} className="flex items-start gap-2 min-w-0 text-left rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring">
<button type="button" aria-label={`Раскрыть маршрут ${g.dstAddress} · ${g.routingTable}`} aria-expanded={isExpanded} onClick={event => { event.stopPropagation(); onExpandedChange?.(isExpanded ? null : g.key) }} className="flex items-start gap-2 min-h-11 min-w-0 text-left rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring">
{isExpanded ? (
<ChevronDownIcon aria-hidden="true" className="size-3.5 mt-0.5 shrink-0 text-muted-foreground" />
) : (
@@ -165,19 +176,20 @@ function RecursiveRoutesDataGrid({
headerClassName: DATA_GRID_CELL_PAD_FIRST,
cellClassName: DATA_GRID_CELL_PAD_FIRST,
expandedContent: (group: RecursiveRouteGroup) => (
<RouteGroupExpandedDetail group={group} />
<RouteGroupExpandedDetail key={group.key} group={group} />
),
},
},
{
id: "gateways",
size: 208,
header: () => <span className="text-xs font-medium text-muted-foreground">Gateways</span>,
enableSorting: false,
cell: ({ row }) => {
const sorted = [...row.original.endpoints].sort((a, b) => a.distance - b.distance)
return (
<div className="flex flex-col gap-0.5">
{sorted.map(ep => {
{sorted.slice(0, 3).map(ep => {
const code = ep.country
return (
<div key={ep.id} className="flex items-center gap-1.5 text-[11px] font-mono">
@@ -200,6 +212,7 @@ function RecursiveRoutesDataGrid({
</div>
)
})}
{sorted.length > 3 && <span className="text-xs text-muted-foreground">Ещё {sorted.length - 3} · раскройте маршрут</span>}
</div>
)
},
@@ -207,6 +220,7 @@ function RecursiveRoutesDataGrid({
},
{
id: "epCount",
size: 64,
header: () => <span className="text-xs font-medium text-muted-foreground">EP</span>,
enableSorting: false,
cell: ({ row }) => (
@@ -216,6 +230,7 @@ function RecursiveRoutesDataGrid({
},
{
id: "priority",
size: 96,
header: () => <span className="text-xs font-medium text-muted-foreground">Priority</span>,
enableSorting: false,
cell: ({ row }) => {
@@ -226,6 +241,7 @@ function RecursiveRoutesDataGrid({
},
{
id: "table",
size: 128,
accessorKey: "routingTable",
header: ({ column }) => <DataGridSortHeader column={column} title="Table" />,
cell: ({ row }) => (
@@ -253,7 +269,7 @@ function RecursiveRoutesDataGrid({
>
<Button type="button" size="sm"
variant="ghost"
className="size-7 p-0 text-muted-foreground hover:text-foreground"
className="size-11 p-0 text-muted-foreground hover:text-foreground"
aria-label={`Редактировать маршрут ${g.dstAddress} · ${g.routingTable}`}
disabled={mutationsDisabled}
onClick={() => onEdit(g)}
@@ -263,7 +279,7 @@ function RecursiveRoutesDataGrid({
<Button type="button" size="sm"
variant="ghost"
className={cn(
"size-7 p-0 transition-colors",
"size-11 p-0 transition-colors",
confirmDel
? "text-destructive bg-destructive/10 hover:bg-destructive/20"
: "text-muted-foreground hover:text-destructive",
@@ -289,7 +305,7 @@ function RecursiveRoutesDataGrid({
</div>
)
},
size: 80,
size: 112,
meta: { headerClassName: DATA_GRID_CELL_PAD_LAST, cellClassName: DATA_GRID_CELL_PAD_LAST },
},
],
@@ -298,7 +314,9 @@ function RecursiveRoutesDataGrid({
const table = useTable({
features: dataGridFeatures,
manualPagination: true,
manualPagination: false,
autoResetPageIndex: false,
initialState: { pagination: { pageIndex: 0, pageSize: 50 }, columnPinning: { start: ["route"], end: [] } },
data: groups,
columns,
getRowId: (row) => row.id ?? row.key,
@@ -306,6 +324,20 @@ function RecursiveRoutesDataGrid({
state: { expanded },
})
const requestedExpansion = useRef<string | null>(null)
const { pageIndex, pageSize } = table.state.pagination
useEffect(() => {
const lastPage = Math.max(0, Math.ceil(groups.length / pageSize) - 1)
if (pageIndex > lastPage) table.setPageIndex(lastPage)
if (!expandedKey) { requestedExpansion.current = null; return }
const index = table.getSortedRowModel().rows.findIndex(row => row.original.key === expandedKey)
if (index < 0) { requestedExpansion.current = null; return }
if (requestedExpansion.current !== expandedKey) {
requestedExpansion.current = expandedKey
table.setPageIndex(Math.floor(index / pageSize))
}
}, [groups.length, expandedKey, pageIndex, pageSize, table])
if (groups.length === 0) {
return (
<EmptyState
@@ -320,6 +352,8 @@ function RecursiveRoutesDataGrid({
<DataGridShell
table={table}
recordCount={groups.length}
pagination={groups.length > 50 || pageSize !== 50}
tableLayout={{ dense: true, columnsPinnable: true }}
onRowClick={(row) => {
onExpandedChange?.(expandedKey === row.key ? null : row.key)
}}
@@ -327,7 +361,11 @@ function RecursiveRoutesDataGrid({
headerRow: "border-b border-border",
bodyRow: cn("group/row cursor-pointer", expandedKey && "data-[state=selected]:bg-muted/30"),
}}
/>
>
<DataGridContainer border={false} className="w-full rounded-none border-0">
<DataGridScrollArea orientation="horizontal"><DataGridTable /></DataGridScrollArea>
</DataGridContainer>
</DataGridShell>
)
}