"use client" import { DataGridContainer, DataGridScrollArea, DataGridTable, dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" import { useEffect, useMemo, useRef, useState } from "react" import { type ColumnDef, useTable, } from "@tanstack/react-table" import { Flag } from "@/components/flag" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { DataGridShell } from "@/components/data-grids/shared/data-grid-shell" import { DATA_GRID_CELL_PAD, DATA_GRID_CELL_PAD_FIRST, DATA_GRID_CELL_PAD_LAST, } from "@/components/data-grids/shared/data-grid-layout" import { DataGridSortHeader } from "@/components/data-grids/shared/data-grid-sort-header" import { EmptyState } from "@/components/empty-state" import { AlertCircleIcon, ChevronDownIcon, ChevronRightIcon, PencilIcon, RouteIcon, TrashIcon, } from "lucide-react" export interface RecursiveRouteEndpoint { id: string gateway: string distance: number scope: number | null targetScope: number | null checkGateway: string country: string disabled: boolean } export interface RecursiveRouteGroup { id?: string key: string dstAddress: string routingTable: string comment: string endpoints: RecursiveRouteEndpoint[] } const INFER_COUNTRIES = [ { code: "RU", keys: ["MSK", "SPB", "RTK", "MTS", "VPSVILLE", "IHOR"] }, { code: "SE", keys: ["SWE", "STO"] }, { code: "FI", keys: ["HEL", "FIN"] }, { code: "DE", keys: ["FRA", "GER", "DE"] }, { code: "NL", keys: ["AMS", "NLD", "NL"] }, { code: "SG", keys: ["SGP", "SIN", "SG"] }, { code: "TR", keys: ["TUR", "TR"] }, { code: "US", keys: ["USA", "US", "NYC", "LAX"] }, ] function inferCountry(name: string): string | null { const upper = name.toUpperCase() for (const c of INFER_COUNTRIES) { if (c.keys.some((k) => upper.includes(k))) return c.code } return null } function RouteGroupExpandedDetail({ group }: { group: RecursiveRouteGroup }) { 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 (
Route: {group.dstAddress} Table:{" "} {group.routingTable || "main"} {group.comment && {group.comment}}
{sorted.slice(offset, offset + 50).map((ep, idx) => (
{ep.country && ( )} Endpoint {offset + idx + 1}
distance: {ep.distance}

{ep.gateway}

scope: {ep.scope ?? "—"} t.scope: {ep.targetScope ?? "—"} check: {ep.checkGateway || "—"} {ep.disabled && Отключён}
))}
{sorted.length > 50 &&
Конечные точки {offset + 1}–{Math.min(offset + 50, sorted.length)} из {sorted.length}
}
) } interface RecursiveRoutesDataGridProps { groups: RecursiveRouteGroup[] expandedKey?: string | null onExpandedChange?: (key: string | null) => void onEdit: (group: RecursiveRouteGroup) => void onDelete: (group: RecursiveRouteGroup) => void mutationsDisabled?: boolean emptyTitle?: string } function RecursiveRoutesDataGrid({ groups, expandedKey, onExpandedChange, onEdit, onDelete, mutationsDisabled = false, emptyTitle = "Нет маршрутов", }: RecursiveRoutesDataGridProps) { const [confirmDeleteKey, setConfirmDeleteKey] = useState(null) const expanded = useMemo(() => { if (!expandedKey) return {} const g = groups.find((x) => x.key === expandedKey) return g ? { [(g.id ?? g.key)]: true } : {} }, [expandedKey, groups]) const columns = useMemo[]>( () => [ { id: "route", size: 176, accessorKey: "dstAddress", header: ({ column }) => ( ), cell: ({ row }) => { const g = row.original const isExpanded = expandedKey === g.key return ( ) }, meta: { headerTitle: "Route / Comment", headerClassName: DATA_GRID_CELL_PAD_FIRST, cellClassName: DATA_GRID_CELL_PAD_FIRST, expandedContent: (group: RecursiveRouteGroup) => ( ), }, }, { id: "gateways", size: 208, header: () => Gateways, enableSorting: false, cell: ({ row }) => { const sorted = [...row.original.endpoints].sort((a, b) => a.distance - b.distance) return (
{sorted.slice(0, 3).map(ep => { const code = ep.country return (
) })} {sorted.length > 3 && Ещё {sorted.length - 3} · раскройте маршрут}
) }, meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "epCount", size: 64, header: () => EP, enableSorting: false, cell: ({ row }) => ( {row.original.endpoints.length} ), meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "priority", size: 96, header: () => Priority, enableSorting: false, cell: ({ row }) => { const bestDistance = Math.min(...row.original.endpoints.map((ep) => ep.distance)) return d{bestDistance} }, meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "table", size: 128, accessorKey: "routingTable", header: ({ column }) => , cell: ({ row }) => ( {row.original.routingTable || "main"} ), meta: { headerTitle: "Table", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD, }, }, { id: "actions", header: () => Действия, enableSorting: false, cell: ({ row }) => { const g = row.original const confirmDel = confirmDeleteKey === g.key return (
e.stopPropagation()} >
) }, size: 112, meta: { headerClassName: DATA_GRID_CELL_PAD_LAST, cellClassName: DATA_GRID_CELL_PAD_LAST }, }, ], [confirmDeleteKey, expandedKey, onDelete, onEdit, onExpandedChange, mutationsDisabled], ) const table = useTable({ features: dataGridFeatures, manualPagination: false, autoResetPageIndex: false, initialState: { pagination: { pageIndex: 0, pageSize: 50 }, columnPinning: { start: ["route"], end: [] } }, data: groups, columns, getRowId: (row) => row.id ?? row.key, getRowCanExpand: () => true, state: { expanded }, }) const requestedExpansion = useRef(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 (