"use client" import { useMemo, useState } from "react" import { type ColumnDef, getCoreRowModel, getExpandedRowModel, getSortedRowModel, useReactTable, } 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 sorted = [...group.endpoints].sort((a, b) => a.distance - b.distance) return (
Route: {group.dstAddress} Table:{" "} {group.routingTable || "main"} {group.comment && {group.comment}}
{sorted.map((ep, idx) => (
{(ep.country || inferCountry(ep.gateway)) && ( )} Endpoint {idx + 1}
distance: {ep.distance}

{ep.gateway}

scope: {ep.scope ?? "—"} t.scope: {ep.targetScope ?? "—"} check: {ep.checkGateway || "—"}
))}
) } interface RecursiveRoutesDataGridProps { groups: RecursiveRouteGroup[] expandedKey?: string | null onExpandedChange?: (key: string | null) => void onEdit: (group: RecursiveRouteGroup) => void onDelete: (group: RecursiveRouteGroup) => void } function RecursiveRoutesDataGrid({ groups, expandedKey, onExpandedChange, onEdit, onDelete, }: 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", accessorKey: "dstAddress", header: ({ column }) => ( ), cell: ({ row }) => { const g = row.original const isExpanded = expandedKey === g.key return (
{isExpanded ? ( ) : ( )}

{g.dstAddress}

{g.comment || "—"}

) }, meta: { headerTitle: "Route / Comment", headerClassName: DATA_GRID_CELL_PAD_FIRST, cellClassName: DATA_GRID_CELL_PAD_FIRST, expandedContent: (group: RecursiveRouteGroup) => ( ), }, }, { id: "gateways", header: () => Gateways, enableSorting: false, cell: ({ row }) => { const sorted = [...row.original.endpoints].sort((a, b) => a.distance - b.distance) return (
{sorted.map((ep, idx) => { const code = ep.country || inferCountry(ep.gateway) return (
{code ? ( ) : ( ? )} {ep.gateway}
) })}
) }, meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "epCount", header: () => EP, enableSorting: false, cell: ({ row }) => ( {row.original.endpoints.length} ), meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "priority", 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", 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: 80, meta: { headerClassName: DATA_GRID_CELL_PAD_LAST, cellClassName: DATA_GRID_CELL_PAD_LAST }, }, ], [confirmDeleteKey, expandedKey, onDelete, onEdit], ) const table = useReactTable({ data: groups, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getExpandedRowModel: getExpandedRowModel(), getRowId: (row) => row.id ?? row.key, getRowCanExpand: () => true, state: { expanded }, }) if (groups.length === 0) { return ( } title="Нет маршрутов" className="border-0 py-10" /> ) } return ( { onExpandedChange?.(expandedKey === row.key ? null : row.key) }} tableClassNames={{ headerRow: "border-b border-border", bodyRow: cn("group/row cursor-pointer", expandedKey && "data-[state=selected]:bg-muted/30"), }} /> ) } export { RecursiveRoutesDataGrid, type RecursiveRoutesDataGridProps, inferCountry }