fix(ui): заменить таблицы на карточки данных и улучшить функциональность поиска
Docker images / prepare-release (push) Successful in 6s
Docker images / backend-image (push) Successful in 1m37s
Docker images / frontend-image (push) Successful in 1m50s
Docker images / updater-image (push) Successful in 44s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 7s

This commit is contained in:
Denozordec
2026-06-30 22:22:51 +07:00
parent a1a9124f3d
commit d3a2d38b37
63 changed files with 8509 additions and 3652 deletions
+27 -89
View File
@@ -2,7 +2,6 @@
import { useMemo } from "react"
import {
type Column,
type ColumnDef,
getCoreRowModel,
getExpandedRowModel,
@@ -21,11 +20,13 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { DataGridShell } from "@/components/data-grids/shared/data-grid-shell"
import {
DataGrid,
DataGridContainer,
DataGridTable,
} from "@/components/reui/data-grid"
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 { ServerExpandedDetail } from "@/components/data-grids/server-expanded-detail"
import {
@@ -42,9 +43,6 @@ import {
RefreshCwIcon,
PowerIcon,
Trash2Icon,
ArrowUpIcon,
ArrowDownIcon,
ArrowUpDownIcon,
} from "lucide-react"
const TYPE_LABELS: Record<ServerType, string> = {
@@ -59,10 +57,6 @@ const TYPE_STYLES: Record<ServerType, string> = {
"home-router": "bg-emerald-500/10 text-emerald-400 border-emerald-500/20",
}
const CELL_PAD = "py-3"
const CELL_PAD_FIRST = "pl-5 py-3"
const CELL_PAD_LAST = "pr-4 py-3"
function rosVer(os: string): number {
const m = os.match(/(\d+)\.(\d+)/)
if (!m) return 0
@@ -91,48 +85,6 @@ function RosBadge({ os }: { os: string }) {
return <span className={cn("text-xs font-mono border rounded px-2 py-0.5", cls)}>{os}</span>
}
function ServersTableHeader<TData>({
column,
title,
className,
}: {
column: Column<TData, unknown>
title: string
className?: string
}) {
const sorted = column.getIsSorted()
const canSort = column.getCanSort()
if (!canSort) {
return (
<span className={cn("text-xs font-medium text-muted-foreground", className)}>
{title}
</span>
)
}
return (
<button
type="button"
className={cn(
"inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground",
"hover:text-foreground transition-colors rounded-md -ml-1 px-1 py-0.5",
className,
)}
onClick={column.getToggleSortingHandler()}
>
{title}
{sorted === "asc" ? (
<ArrowUpIcon className="size-3 text-foreground" />
) : sorted === "desc" ? (
<ArrowDownIcon className="size-3 text-foreground" />
) : (
<ArrowUpDownIcon className="size-3 opacity-35" />
)}
</button>
)
}
interface ServerRowActionsProps {
server: Server
isLive: boolean
@@ -234,7 +186,7 @@ function ServersDataGrid({
id: "name",
accessorKey: "name",
header: ({ column }) => (
<ServersTableHeader column={column} title="Имя / Хост" className="ml-1" />
<DataGridSortHeader column={column} title="Имя / Хост" className="ml-1" />
),
cell: ({ row }) => {
const s = row.original
@@ -258,8 +210,8 @@ function ServersDataGrid({
},
meta: {
headerTitle: "Имя / Хост",
headerClassName: CELL_PAD_FIRST,
cellClassName: CELL_PAD_FIRST,
headerClassName: DATA_GRID_CELL_PAD_FIRST,
cellClassName: DATA_GRID_CELL_PAD_FIRST,
expandedContent: (row: Server) => (
<ServerExpandedDetail
server={row}
@@ -273,35 +225,35 @@ function ServersDataGrid({
{
id: "type",
accessorKey: "type",
header: ({ column }) => <ServersTableHeader column={column} title="Тип" />,
header: ({ column }) => <DataGridSortHeader column={column} title="Тип" />,
cell: ({ row }) => <TypeBadge type={row.original.type} />,
meta: { headerTitle: "Тип", headerClassName: CELL_PAD, cellClassName: CELL_PAD },
meta: { headerTitle: "Тип", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "model",
accessorKey: "model",
header: ({ column }) => <ServersTableHeader column={column} title="Модель" />,
header: ({ column }) => <DataGridSortHeader column={column} title="Модель" />,
cell: ({ row }) => <span className="text-muted-foreground text-xs">{row.original.model}</span>,
meta: { headerTitle: "Модель", headerClassName: CELL_PAD, cellClassName: CELL_PAD },
meta: { headerTitle: "Модель", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "os",
accessorKey: "os",
header: ({ column }) => <ServersTableHeader column={column} title="RouterOS" />,
header: ({ column }) => <DataGridSortHeader column={column} title="RouterOS" />,
cell: ({ row }) => <RosBadge os={row.original.os} />,
meta: { headerTitle: "RouterOS", headerClassName: CELL_PAD, cellClassName: CELL_PAD },
meta: { headerTitle: "RouterOS", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "site",
accessorKey: "site",
header: ({ column }) => <ServersTableHeader column={column} title="Площадка" />,
header: ({ column }) => <DataGridSortHeader column={column} title="Площадка" />,
cell: ({ row }) => (
<div className="flex items-center gap-1.5">
<Flag code={row.original.country} />
<span className="font-medium text-sm">{row.original.site}</span>
</div>
),
meta: { headerTitle: "Площадка", headerClassName: CELL_PAD, cellClassName: CELL_PAD },
meta: { headerTitle: "Площадка", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "wan",
@@ -345,13 +297,13 @@ function ServersDataGrid({
</div>
)
},
meta: { headerTitle: "WAN / LAN", headerClassName: CELL_PAD, cellClassName: CELL_PAD },
meta: { headerTitle: "WAN / LAN", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "latency",
accessorKey: "latency",
header: ({ column }) => (
<ServersTableHeader column={column} title="Задержка" className="w-full justify-end" />
<DataGridSortHeader column={column} title="Задержка" className="w-full justify-end" />
),
cell: ({ row }) => {
const s = row.original
@@ -366,16 +318,16 @@ function ServersDataGrid({
},
meta: {
headerTitle: "Задержка",
headerClassName: cn(CELL_PAD, "text-right"),
cellClassName: cn(CELL_PAD, "text-right"),
headerClassName: cn(DATA_GRID_CELL_PAD, "text-right"),
cellClassName: cn(DATA_GRID_CELL_PAD, "text-right"),
},
},
{
id: "status",
accessorKey: "status",
header: ({ column }) => <ServersTableHeader column={column} title="Статус" />,
header: ({ column }) => <DataGridSortHeader column={column} title="Статус" />,
cell: ({ row }) => <StatusBadge status={row.original.status} />,
meta: { headerTitle: "Статус", headerClassName: CELL_PAD, cellClassName: CELL_PAD },
meta: { headerTitle: "Статус", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD },
},
{
id: "actions",
@@ -397,8 +349,8 @@ function ServersDataGrid({
enableSorting: false,
size: 56,
meta: {
headerClassName: CELL_PAD_LAST,
cellClassName: CELL_PAD_LAST,
headerClassName: DATA_GRID_CELL_PAD_LAST,
cellClassName: DATA_GRID_CELL_PAD_LAST,
},
},
],
@@ -427,25 +379,11 @@ function ServersDataGrid({
}
return (
<DataGrid
<DataGridShell
table={table}
recordCount={servers.length}
onRowClick={(row) => table.getRow(row.id).toggleExpanded()}
tableLayout={{
rowBorder: true,
headerBackground: true,
headerBorder: true,
columnsResizable: false,
}}
tableClassNames={{
headerRow: "border-b border-border",
bodyRow: "group/row",
}}
>
<DataGridContainer border={false} className="rounded-none border-0">
<DataGridTable />
</DataGridContainer>
</DataGrid>
/>
)
}