fix(frontend): починить сортировку DataGrid и стиль колонки actions
Build and Push Telemt Panel Docker Image / build-and-push (push) Successful in 1m57s
Build and Push Telemt Panel Docker Image / create-release (push) Skipped

Клик по заголовку снова сортирует напрямую; убран pin последней колонки, из-за которого actions выглядел отрезанным от строки.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Denozordec
2026-08-04 22:22:29 +07:00
co-authored by Cursor
parent ee3e96647e
commit 14911cd067
6 changed files with 75 additions and 19 deletions
@@ -238,7 +238,12 @@ export function createErrorsColumns(opts: {
enableSorting: false,
enableHiding: false,
enableResizing: false,
meta: { skeleton: <Skeleton className="size-7 rounded-md" /> },
enablePinning: false,
meta: {
skeleton: <Skeleton className="size-7 rounded-md" />,
cellClassName: 'bg-transparent!',
headerClassName: 'bg-transparent!',
},
},
]
}
@@ -157,7 +157,6 @@ export function ErrorsGridView() {
}
getFilterFieldValue={getFilterFieldValue}
onRowClick={handleOpen}
pinLastColumn
pageSize={10}
initialSorting={[{ id: 'total', desc: true }]}
enableColumnVisibility
@@ -177,8 +176,10 @@ export function ErrorsGridView() {
columnsResizable: true,
columnsMovable: true,
columnsVisibility: true,
columnsPinnable: false,
rowsPinnable: false,
}}
pinLastColumn={false}
/>
<ResourcePage
@@ -6,6 +6,7 @@ import {
useReactTable,
type ColumnDef,
type ColumnOrderState,
type ColumnPinningState,
type OnChangeFn,
type PaginationState,
type RowPinningState,
@@ -172,7 +173,7 @@ const DEFAULT_FILTERED_LAYOUT: TableLayoutConfig = {
dense: true,
columnsVisibility: true,
columnsResizable: true,
columnsPinnable: true,
columnsPinnable: false,
columnsMovable: true,
rowsPinnable: false,
}
@@ -404,6 +405,20 @@ function ResourcePageFiltered<T extends object>({
? (columns[columns.length - 1]?.id ?? '')
: ''
const [columnPinning, setColumnPinning] = useState<ColumnPinningState>(() =>
pinLastColumn && lastColId
? { left: [], right: [lastColId] }
: { left: [], right: [] },
)
const handleSortingChange = useCallback<OnChangeFn<SortingState>>(
(updater) => {
setSorting(updater)
resetPagination()
},
[resetPagination],
)
const table = useReactTable({
data: filteredData,
columns,
@@ -414,6 +429,7 @@ function ResourcePageFiltered<T extends object>({
pagination,
columnVisibility,
columnOrder,
columnPinning,
...(enableRowPinning ? { rowPinning: resolvedRowPinning } : {}),
},
enableRowSelection,
@@ -421,20 +437,18 @@ function ResourcePageFiltered<T extends object>({
keepPinnedRows: enableRowPinning,
enableColumnPinning: Boolean(pinLastColumn || layout.columnsPinnable),
enableHiding: enableColumnVisibility,
onSortingChange: setSorting,
enableSorting: true,
manualSorting: false,
onSortingChange: handleSortingChange,
onRowSelectionChange: setRowSelection,
onPaginationChange: setPagination,
onColumnVisibilityChange: setColumnVisibility,
onColumnOrderChange: setColumnOrder,
onColumnPinningChange: setColumnPinning,
onRowPinningChange: enableRowPinning ? setRowPinning : undefined,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
initialState: {
...(pinLastColumn && lastColId
? { columnPinning: { right: [lastColId] } }
: {}),
},
})
const handleTabChange = useCallback(
@@ -289,26 +289,44 @@ function DataGridColumnHeaderInner<TData, TValue>({
if (hasControls) {
return (
<div className="-ms-2 flex h-full items-center justify-between gap-1.5">
<DropdownMenu>
<DropdownMenuTrigger
render={
<div className="-ms-2 flex h-full items-center gap-0.5">
{canSort ? (
<Button
variant="ghost"
className={headerButtonClassName}
disabled={isLoading}
onClick={handleSort}
aria-label={`Сортировать: ${resolvedTitle}`}
>
{icon && icon}
{resolvedTitle}
{sortIcon}
</Button>
) : (
<span className={cn(headerLabelClassName, "px-2")}>
{icon && icon}
{resolvedTitle}
</span>
)}
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
size="icon-sm"
variant="ghost"
className="rounded-lg size-7"
disabled={isLoading}
aria-label={`Параметры колонки: ${resolvedTitle}`}
>
<Settings2Icon className="size-3.5 opacity-60" aria-hidden="true" />
</Button>
}
/>
<DropdownMenuContent className="w-40" align="start">
{menuItems}
</DropdownMenuContent>
</DropdownMenu>
{props.tableLayout?.columnsPinnable && canPin && isPinned && (
{props.tableLayout?.columnsPinnable && canPin && isPinned ? (
<Button
size="icon-sm"
variant="ghost"
@@ -319,7 +337,7 @@ function DataGridColumnHeaderInner<TData, TValue>({
>
<PinOffIcon className="size-3.5! opacity-50!" aria-hidden="true" />
</Button>
)}
) : null}
</div>
)
}
@@ -349,7 +349,7 @@ export function createUsersColumns(opts: {
},
{
id: 'current_connections',
accessorKey: 'current_connections',
accessorFn: (row) => Number(row.current_connections ?? 0),
header: ({ column }) => (
<DataGridColumnHeader
title="Соединения"
@@ -362,6 +362,7 @@ export function createUsersColumns(opts: {
enableSorting: true,
enableHiding: true,
enableResizing: true,
sortingFn: 'basic',
meta: {
skeleton: (
<div className="flex min-w-0 items-center gap-2">
@@ -442,7 +443,12 @@ export function createUsersColumns(opts: {
},
{
id: 'data_quota_bytes',
accessorKey: 'data_quota_bytes',
accessorFn: (row) => {
const quota = row.data_quota_bytes
if (typeof quota === 'number' && quota > 0) return quota
// «Без квоты» в UI показывает usage — сортируем по тому же смыслу
return Number(row.total_octets ?? 0)
},
header: ({ column }) => (
<DataGridColumnHeader title="Квота" visibility={true} column={column} />
),
@@ -451,6 +457,7 @@ export function createUsersColumns(opts: {
enableSorting: true,
enableHiding: true,
enableResizing: true,
sortingFn: 'basic',
meta: {
skeleton: (
<div className="flex min-w-0 flex-col gap-1">
@@ -534,9 +541,12 @@ export function createUsersColumns(opts: {
enableSorting: false,
enableHiding: false,
enableResizing: false,
enablePinning: false,
size: 52,
meta: {
skeleton: <Skeleton className="size-7 rounded-md" />,
cellClassName: 'bg-transparent!',
headerClassName: 'bg-transparent!',
},
},
]
@@ -276,9 +276,17 @@ export function UsersGridView() {
enableColumnVisibility
columnVisibility={columnVisibility}
onColumnVisibilityChange={setColumnVisibility}
pinLastColumn
pageSize={5}
initialSorting={[{ id: 'username', desc: false }]}
tableLayout={{
dense: true,
columnsResizable: true,
columnsMovable: true,
columnsVisibility: true,
columnsPinnable: false,
rowsPinnable: true,
}}
pinLastColumn={false}
emptyState={{
title: 'Нет пользователей',
description: 'Создайте первый аккаунт Telemt.',