"use client" import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" import { useIsMobile } from "@/hooks/use-mobile" import { useEffect, useMemo, useState } from "react" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" import { DataGridContainer, DataGridScrollArea, DataGridTable } from "@/components/reui/data-grid" import { type ColumnDef, useTable, } from "@tanstack/react-table" import type { HomeRouter, JumpHost, WanJhLeg } from "@/lib/route-optimizer-data" import { Flag } from "@/components/flag" import { cn } from "@/lib/utils" 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 { WifiIcon } from "lucide-react" function LossChip({ loss }: { loss: number }) { if (loss === 0) return 0% return ( 1 ? "text-destructive" : "text-warning")}> {loss}% ) } interface RouteOptimizerWanMatrixDataGridProps { home: HomeRouter legs: WanJhLeg[] jumpHosts: JumpHost[] } function RouteOptimizerWanMatrixDataGrid({ home, legs, jumpHosts, }: RouteOptimizerWanMatrixDataGridProps) { const isMobile = useIsMobile() const [wanQuery, setWanQuery] = useState("") const [jhQuery, setJhQuery] = useState("") const [jhPage, setJhPage] = useState(0) const filteredWans = useMemo(() => home.wans.filter(wan => [wan.name, wan.iface, wan.isp, wan.ip, wan.id].join(" ").toLocaleLowerCase().includes(wanQuery.trim().toLocaleLowerCase())), [home.wans, wanQuery]) const filteredHosts = useMemo(() => jumpHosts.filter(jh => [jh.label, jh.site, jh.ip, jh.country, jh.id].join(" ").toLocaleLowerCase().includes(jhQuery.trim().toLocaleLowerCase())), [jumpHosts, jhQuery]) const lastHostPage = Math.max(0, Math.ceil(filteredHosts.length / 5) - 1) const hostPage = Math.min(jhPage, lastHostPage) if (jhPage !== hostPage) setJhPage(hostPage) const visibleHosts = useMemo(() => filteredHosts.slice(hostPage * 5, hostPage * 5 + 5), [filteredHosts, hostPage]) const { bestScore, legMap } = useMemo(() => { let bestScore = legs.length ? -Infinity : 0 const legMap = new Map>() for (const leg of legs) { bestScore = Math.max(bestScore, leg.score) let byHost = legMap.get(leg.wanId) if (!byHost) { byHost = new Map(); legMap.set(leg.wanId, byHost) } // Preserve the first matching leg, as the former Array.find did. if (!byHost.has(leg.jhId)) byHost.set(leg.jhId, leg) } return { bestScore, legMap } }, [legs]) const columns = useMemo[]>(() => { const base: ColumnDef[] = [ { id: "wan", size: isMobile ? 112 : 180, accessorKey: "name", header: () => WAN-аплинк, cell: ({ row }) => { const wan = row.original return (
) }, meta: { headerClassName: DATA_GRID_CELL_PAD_FIRST, cellClassName: DATA_GRID_CELL_PAD_FIRST }, }, { id: "isp", size: 160, header: () => ISP / IP, cell: ({ row }) => { const wan = row.original return (

{wan.isp}

{wan.ip}

) }, meta: { headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD }, }, { id: "bandwidth", size: 112, header: () => ( Лимит модели ), cell: ({ row }) => { const wan = row.original return (

↓{wan.maxDl}

↑{wan.maxUl} Мбит/с

) }, meta: { headerClassName: cn(DATA_GRID_CELL_PAD, "text-right"), cellClassName: cn(DATA_GRID_CELL_PAD, "text-right"), }, }, ] const jhCols: ColumnDef[] = visibleHosts.map((jh) => ({ id: `jh-${jh.id}`, size: isMobile ? 160 : 200, header: () => (
{jh.label}
{jh.site} · {jh.ip}
), cell: ({ row }) => { const wan = row.original const leg = legMap.get(wan.id)?.get(jh.id) if (!leg) return — const isBest = leg.score === bestScore return (
{isBest && ( ★ ЛУЧШИЙ )} {leg.observed ? (leg.observed.pingMs ?? "—") : leg.pingMs} мс ↓{leg.observed ? (leg.observed.dlMbps ?? "—") : leg.dlMbps} ↑{leg.observed ? (leg.observed.ulMbps ?? "—") : leg.ulMbps} Мбит/с {leg.observed && {leg.observed.probeId ? <>Снимок пробы · Ping: {leg.observed.pingAt ? : "дата неизвестна"}. Скорость: дата неизвестна. : "Измерения отсутствуют"} }
модель {leg.score} {(leg.observed ? (leg.observed.lossPct ?? 0) : leg.loss) > 0 && }
) }, meta: { headerClassName: cn(DATA_GRID_CELL_PAD, "min-w-[130px] text-center"), cellClassName: cn(DATA_GRID_CELL_PAD, "min-w-[130px]"), }, })) const last = jhCols[jhCols.length - 1] if (last?.meta) { last.meta.headerClassName = cn(last.meta.headerClassName, DATA_GRID_CELL_PAD_LAST.replace("py-3", "")) last.meta.cellClassName = DATA_GRID_CELL_PAD_LAST } return [...base, ...jhCols] }, [bestScore, visibleHosts, legMap, isMobile]) const table = useTable({ features: dataGridFeatures, manualPagination: false, autoResetPageIndex: false, initialState: { pagination: { pageIndex: 0, pageSize: 50 }, columnPinning: { start: ["wan"], end: [] } }, manualSorting: true, data: filteredWans, columns, getRowId: (row) => row.id, }) const { pageIndex, pageSize } = table.state.pagination useEffect(() => { const lastPage = Math.max(0, Math.ceil(filteredWans.length / pageSize) - 1) if (pageIndex > lastPage) table.setPageIndex(lastPage) }, [filteredWans.length, pageIndex, pageSize, table]) return (
WAN: {filteredWans.length} из {home.wans.length}. Jump Host: {filteredHosts.length} из {jumpHosts.length}. {filteredHosts.length > 5 && <> Столбцы {hostPage * 5 + 1}–{Math.min((hostPage + 1) * 5, filteredHosts.length)} из {filteredHosts.length} }
{legs.some(leg=>leg.observed?.probeId) &&

Скорость — снимок пары устройств. Сопоставление с WAN расчётное. ↓ к Home, ↑ от Home.

} 50 || pageSize !== 50} tableClassNames={{ headerRow: "border-b bg-muted/20", bodyRow: "group/row hover:bg-muted/30" }} tableLayout={{ dense: true, width: "fixed", columnsPinnable: true }} >
) } export { RouteOptimizerWanMatrixDataGrid, type RouteOptimizerWanMatrixDataGridProps }