"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 }