diff --git a/apps/web/src/components/data-grid-cells.tsx b/apps/web/src/components/data-grid-cells.tsx
index 8870f85..1c7aa16 100644
--- a/apps/web/src/components/data-grid-cells.tsx
+++ b/apps/web/src/components/data-grid-cells.tsx
@@ -1,7 +1,9 @@
+import type { LucideIcon } from 'lucide-react'
import type { ReactNode } from 'react'
-import { cn } from '@cfdm/ui/lib/utils'
+import { IconTile } from '@/components/reui/icon-tile'
import { TruncatedText } from '@/components/truncated-text'
+import { cn } from '@cfdm/ui/lib/utils'
export function dataGridCellStack(
primary: ReactNode,
@@ -26,14 +28,49 @@ export function dataGridCellStack(
)
}
+/**
+ * Name cell DNA — IconTile elevated size-10.5 + truncate.
+ * Preview: https://reui.io/preview/base/stats-12
+ * Docs: https://reui.io/docs/components/base/icon-tile
+ */
+export function DataGridNameCell({
+ icon: Icon,
+ title,
+ subtitle,
+ iconClassName = 'text-muted-foreground',
+ className,
+}: {
+ icon: LucideIcon
+ title: ReactNode
+ subtitle?: ReactNode
+ iconClassName?: string
+ className?: string
+}) {
+ return (
+
+
+
+
+
+ {title}
+ {subtitle ? (
+ {subtitle}
+ ) : null}
+
+
+ )
+}
+
export function dataGridCellWithIcon(
icon: ReactNode,
children: ReactNode,
className?: string,
) {
return (
-
-
{icon}
+
+
+ {icon}
+
{children}
)
diff --git a/apps/web/src/components/row-actions.tsx b/apps/web/src/components/row-actions.tsx
index 7efca79..800c0cd 100644
--- a/apps/web/src/components/row-actions.tsx
+++ b/apps/web/src/components/row-actions.tsx
@@ -1,8 +1,25 @@
-import type { ReactNode } from 'react'
-import { PencilIcon, Trash2Icon } from 'lucide-react'
+import { useState, type ReactNode } from 'react'
+import type { LucideIcon } from 'lucide-react'
+import { MoreHorizontalIcon, PencilIcon, Trash2Icon } from 'lucide-react'
+
import { Button } from '@cfdm/ui/components/button'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@cfdm/ui/components/dropdown-menu'
+import { cn } from '@cfdm/ui/lib/utils'
+
import { ConfirmDialog } from './confirm-dialog'
+export interface RowActionExtra {
+ label: string
+ icon?: LucideIcon
+ onSelect: () => void
+ disabled?: boolean
+}
+
interface RowActionsProps {
onEdit?: () => void
onDelete?: () => void
@@ -10,10 +27,15 @@ interface RowActionsProps {
deleteTitle?: string
deleteDescription?: ReactNode
deleteLabel?: string
- extra?: ReactNode
+ extra?: RowActionExtra[]
className?: string
}
+/**
+ * Data-grid row actions — outline ⋯ menu.
+ * Preview: https://reui.io/preview/base/components/c-dropdown-menu-12
+ * Docs: https://reui.io/docs/components/base/dropdown-menu
+ */
export function RowActions({
onEdit,
onDelete,
@@ -24,23 +46,52 @@ export function RowActions({
extra,
className,
}: RowActionsProps) {
- if (!onEdit && !onDelete && !extra) return null
+ const [deleteOpen, setDeleteOpen] = useState(false)
+ const extras = extra ?? []
+ if (!onEdit && !onDelete && extras.length === 0) return null
return (
-
- {extra}
- {onEdit ? (
-
- ) : null}
+
+
+
+ }
+ >
+
+
+
+ {extras.map((item) => {
+ const Icon = item.icon
+ return (
+
+ {Icon ? : null}
+ {item.label}
+
+ )
+ })}
+ {onEdit ? (
+
+
+ {editLabel}
+
+ ) : null}
+ {onDelete ? (
+ setDeleteOpen(true)}>
+
+ {deleteLabel}
+
+ ) : null}
+
+
{onDelete ? (
-
-
- }
+ open={deleteOpen}
+ onOpenChange={setDeleteOpen}
title={deleteTitle}
description={deleteDescription}
destructive
diff --git a/apps/web/src/components/status-badge.tsx b/apps/web/src/components/status-badge.tsx
index 96e7172..969e768 100644
--- a/apps/web/src/components/status-badge.tsx
+++ b/apps/web/src/components/status-badge.tsx
@@ -1,40 +1,62 @@
import type { ComponentProps } from 'react'
+import { cn } from '@cfdm/ui/lib/utils'
+
import { Badge } from '@/components/reui/badge'
type BadgeVariant = NonNullable['variant']>
const STATUS_VARIANT: Record = {
- active: 'success',
- ok: 'success',
- paid: 'success',
- available: 'success',
- complete: 'success',
- paused: 'secondary',
+ active: 'success-light',
+ ok: 'success-light',
+ up: 'success-light',
+ paid: 'success-light',
+ available: 'success-light',
+ complete: 'success-light',
+ paused: 'invert-light',
archived: 'outline',
- error: 'destructive',
- denied: 'destructive',
- blocked: 'destructive',
- running: 'info',
- overdue: 'warning',
- stale: 'warning',
- timeout: 'warning',
- redirected: 'warning',
- partial: 'warning',
+ error: 'destructive-light',
+ denied: 'destructive-light',
+ blocked: 'destructive-light',
+ down: 'destructive-light',
+ running: 'info-light',
+ overdue: 'warning-light',
+ stale: 'warning-light',
+ timeout: 'warning-light',
+ redirected: 'warning-light',
+ partial: 'warning-light',
+}
+
+const DOT_COLOR: Record = {
+ 'success-light': 'bg-success',
+ success: 'bg-success',
+ 'info-light': 'bg-info',
+ info: 'bg-info',
+ 'warning-light': 'bg-warning',
+ warning: 'bg-warning',
+ 'destructive-light': 'bg-destructive',
+ destructive: 'bg-destructive',
+ 'invert-light': 'bg-muted-foreground',
+ secondary: 'bg-muted-foreground',
+ outline: 'bg-muted-foreground',
}
export function StatusBadge({
status,
label,
- size = 'default',
+ size = 'sm',
+ className,
}: {
status: string
label?: string
size?: NonNullable['size']>
+ className?: string
}) {
const variant = STATUS_VARIANT[status] ?? 'outline'
+ const dotColor = DOT_COLOR[variant] ?? 'bg-muted-foreground'
return (
-
+
+
{label ?? status}
)
diff --git a/apps/web/src/lib/custom-fields.tsx b/apps/web/src/lib/custom-fields.tsx
index ca395c2..0987352 100644
--- a/apps/web/src/lib/custom-fields.tsx
+++ b/apps/web/src/lib/custom-fields.tsx
@@ -8,7 +8,7 @@ export {
} from '@cfdm/shared/contracts/custom-fields'
import type { ReactNode } from 'react'
-import { Badge } from '@cfdm/ui/components/badge'
+import { Badge } from '@/components/reui/badge'
import {
type CustomFieldDef,
formatCustomFieldValue,
@@ -39,7 +39,7 @@ export function buildCustomFieldColumns(
}
if (def.type === 'bool') {
return (
-
+
{formatCustomFieldValue(def, val)}
)
diff --git a/apps/web/src/routes/_auth/accounts.tsx b/apps/web/src/routes/_auth/accounts.tsx
index 26e3cef..5ff5707 100644
--- a/apps/web/src/routes/_auth/accounts.tsx
+++ b/apps/web/src/routes/_auth/accounts.tsx
@@ -20,10 +20,10 @@ import { buildApiCredentials } from '@cfdm/shared/utils/api-credentials'
import { snapshotQueryOptions } from '@/queries/snapshot'
import { api, ApiError } from '@/lib/api-client'
import { Button } from '@cfdm/ui/components/button'
-import { Badge } from '@cfdm/ui/components/badge'
+import { Badge } from '@/components/reui/badge'
import { ResourcePage, columnDefFromDataGrid, KpiStatGrid } from '@/components/reui-kit'
import type { DataGridColumn } from '@/components/data-grid-types'
-import { dataGridCellStack } from '@/components/data-grid-cells'
+import { dataGridCellStack, DataGridNameCell } from '@/components/data-grid-cells'
import { CrudListPage } from '@/components/crud-list-page'
import { RowActions } from '@/components/row-actions'
import { HealthModeBanner } from '@/components/health-mode-banner'
@@ -62,10 +62,13 @@ const accountsSearchSchema = z.object({
health: z.string().optional(),
})
-const HEALTH_BADGE_VARIANT: Record = {
- 'stale-sync': 'secondary',
- 'low-balance': 'destructive',
- 'balance-mismatch': 'outline',
+const HEALTH_BADGE_VARIANT: Record<
+ AccountHealthFlag,
+ 'warning-light' | 'destructive-light' | 'outline'
+> = {
+ 'stale-sync': 'warning-light',
+ 'low-balance': 'destructive-light',
+ 'balance-mismatch': 'warning-light',
'no-creds': 'outline',
}
@@ -267,7 +270,13 @@ function AccountsPage() {
key: 'name',
header: 'Аккаунт',
icon: UserRoundIcon,
- cell: (a) => dataGridCellStack(a.name, providerById.get(a.providerId)?.name ?? '—'),
+ cell: (a) => (
+
+ ),
},
{
key: 'login',
@@ -285,12 +294,12 @@ function AccountsPage() {
cell: (a) => {
const flags = getAccountHealthFlags(a, healthCtx)
if (!flags.length) {
- return OK
+ return OK
}
return (
{flags.map((flag) => (
-
+
{ACCOUNT_HEALTH_LABELS[flag]}
))}
@@ -303,7 +312,7 @@ function AccountsPage() {
header: 'API-доступ',
icon: PlugIcon,
cell: (a) => (
-
+
{a.apiCredentialsSet ? 'установлены' : 'нет'}
),
@@ -323,7 +332,7 @@ function AccountsPage() {
sortValue: (a) => vpsCountByAccount.get(a.id) ?? 0,
cell: (a) => {
const count = vpsCountByAccount.get(a.id) ?? 0
- return count ? {count} : 0
+ return count ? {count} : 0
},
},
{
@@ -358,7 +367,7 @@ function AccountsPage() {
key: 'actions',
header: '',
sortable: false,
- className: 'w-32 text-right',
+ className: 'w-12 text-right',
cell: (a) => {
const provider = providerById.get(a.providerId)
const canSync = accountBillmanagerUiReady(a, provider)
@@ -369,17 +378,16 @@ function AccountsPage() {
deleteTitle="Удалить аккаунт?"
deleteDescription={`«${a.name}» будет удалён.`}
extra={
- canSync ? (
-
- ) : null
+ canSync
+ ? [
+ {
+ label: 'Синхронизировать',
+ icon: RefreshCwIcon,
+ disabled: syncMut.isPending && syncMut.variables === a.id,
+ onSelect: () => syncMut.mutate(a.id),
+ },
+ ]
+ : undefined
}
/>
)
@@ -450,7 +458,6 @@ function AccountsPage() {
columns={columnDefFromDataGrid(columns)}
data={filteredAccounts}
getRowId={(a) => a.id}
- pinLastColumn
emptyTitle={health || hasActiveAccountFilters(filters) ? 'Нет аккаунтов с этими фильтрами' : 'Нет записей'}
emptyDescription={
health || hasActiveAccountFilters(filters)
diff --git a/apps/web/src/routes/_auth/balance.tsx b/apps/web/src/routes/_auth/balance.tsx
index e11c3fa..14ec304 100644
--- a/apps/web/src/routes/_auth/balance.tsx
+++ b/apps/web/src/routes/_auth/balance.tsx
@@ -19,7 +19,7 @@ import { toast } from 'sonner'
import { snapshotQueryOptions, ratesQueryOptions } from '@/queries/snapshot'
import { api, ApiError } from '@/lib/api-client'
import { Button } from '@cfdm/ui/components/button'
-import { Badge } from '@cfdm/ui/components/badge'
+import { Badge } from '@/components/reui/badge'
import { ResourcePage, columnDefFromDataGrid } from '@/components/reui-kit'
import type { DataGridColumn } from '@/components/data-grid-types'
import { dataGridCellStack } from '@/components/data-grid-cells'
@@ -100,7 +100,11 @@ function BalancePage() {
header: 'Движение',
icon: ArrowLeftRightIcon,
cell: (r) => (
-
+
{r.direction === 'credit' ? 'Приход' : 'Списание'}
@@ -236,7 +240,6 @@ function BalancePage() {
columns={columnDefFromDataGrid(columns)}
data={rows}
getRowId={(r) => r.id}
- pinLastColumn
footerContent={
diff --git a/apps/web/src/routes/_auth/dashboard.tsx b/apps/web/src/routes/_auth/dashboard.tsx
index 404cdfd..44bbdec 100644
--- a/apps/web/src/routes/_auth/dashboard.tsx
+++ b/apps/web/src/routes/_auth/dashboard.tsx
@@ -277,7 +277,7 @@ function DashboardPage() {
iconClassName: 'text-primary',
to: '/vps',
footer: (
-
+
{totalCount} всего
),
@@ -290,7 +290,7 @@ function DashboardPage() {
iconClassName: 'text-info',
to: '/reports',
footer: (
-
+
оценка
),
@@ -308,7 +308,7 @@ function DashboardPage() {
iconClassName: 'text-success',
to: '/accounts',
footer: (
-
+
API
),
@@ -322,11 +322,11 @@ function DashboardPage() {
variant: runwayLow ? 'warning' : 'default',
to: '/accounts',
footer: runwayLow ? (
-
+
< 14 дн
) : (
-
+
запас
),
@@ -343,6 +343,7 @@ function DashboardPage() {
0 ? 'warning-light' : 'success-light'}
size="sm"
+ radius="full"
>
{expiringCount > 0 ? 'скоро' : 'в норме'}
@@ -360,6 +361,7 @@ function DashboardPage() {
0 ? 'destructive-light' : 'success-light'}
size="sm"
+ radius="full"
>
{issuesCount > 0 ? 'требует внимания' : 'в норме'}
@@ -406,7 +408,9 @@ function DashboardPage() {
Проблемы
{issues.length > 0 ? (
- {issues.length}
+
+ {issues.length}
+
) : null}
@@ -415,7 +419,9 @@ function DashboardPage() {
Аккаунты
{atRisk.length > 0 ? (
- {atRisk.length}
+
+ {atRisk.length}
+
) : null}
diff --git a/apps/web/src/routes/_auth/payments.tsx b/apps/web/src/routes/_auth/payments.tsx
index 0237022..7a7a477 100644
--- a/apps/web/src/routes/_auth/payments.tsx
+++ b/apps/web/src/routes/_auth/payments.tsx
@@ -143,7 +143,7 @@ function PaymentsPage() {
key: 'actions',
header: '',
sortable: false,
- className: 'w-24 text-right',
+ className: 'w-12 text-right',
cell: (p) => (
openEdit(p)}
@@ -250,7 +250,6 @@ function PaymentsPage() {
columns={columnDefFromDataGrid(columns)}
data={sorted}
getRowId={(p) => p.id}
- pinLastColumn
virtualization={sorted.length > 200}
height={560}
footerContent={
diff --git a/apps/web/src/routes/_auth/projects.tsx b/apps/web/src/routes/_auth/projects.tsx
index ef82363..849d55a 100644
--- a/apps/web/src/routes/_auth/projects.tsx
+++ b/apps/web/src/routes/_auth/projects.tsx
@@ -25,7 +25,7 @@ import {
type ProjectFiltersState,
} from '@/components/project-filters'
import { Button } from '@cfdm/ui/components/button'
-import { Badge } from '@cfdm/ui/components/badge'
+import { Badge } from '@/components/reui/badge'
import { EmptyState } from '@/components/empty-state'
import { ProjectColorDot } from '@/components/project-color-dot'
import { ProjectEditSheet, projectFormDefaults } from '@/components/domain/project-edit-sheet'
@@ -153,7 +153,7 @@ function ProjectsPage() {
className: 'text-right tabular-nums',
sortValue: (row) => row.vpsTotal,
cell: (row) => (
-
+
{row.vpsActive}/{row.vpsTotal}
),
@@ -183,7 +183,7 @@ function ProjectsPage() {
key: 'actions',
header: '',
sortable: false,
- className: 'w-24 text-right',
+ className: 'w-12 text-right',
cell: (row) => (
e.stopPropagation()}>
r.id}
- pinLastColumn
onRowClick={(row) =>
void navigate({
to: '/projects/$projectId',
diff --git a/apps/web/src/routes/_auth/providers.tsx b/apps/web/src/routes/_auth/providers.tsx
index 7636a23..8b51238 100644
--- a/apps/web/src/routes/_auth/providers.tsx
+++ b/apps/web/src/routes/_auth/providers.tsx
@@ -7,15 +7,14 @@ import { toast } from 'sonner'
import { snapshotQueryOptions } from '@/queries/snapshot'
import { api, ApiError } from '@/lib/api-client'
import { Button } from '@cfdm/ui/components/button'
-import { Badge } from '@cfdm/ui/components/badge'
+import { Badge } from '@/components/reui/badge'
import { ResourcePage, columnDefFromDataGrid } from '@/components/reui-kit'
import type { DataGridColumn } from '@/components/data-grid-types'
-import { dataGridCellWithIcon } from '@/components/data-grid-cells'
+import { DataGridNameCell } from '@/components/data-grid-cells'
import { CrudListPage } from '@/components/crud-list-page'
import { RowActions } from '@/components/row-actions'
import { ProviderEditSheet, providerFormDefaults } from '@/components/domain/provider-edit-sheet'
import type { ProviderFormValues } from '@/lib/schemas'
-import { faviconUrlFromWebsite } from '@/lib/format'
import type { Provider } from '@/types/entities'
export const Route = createFileRoute('/_auth/providers')({
@@ -79,20 +78,19 @@ function ProvidersPage() {
key: 'name',
header: 'Хостер',
icon: BuildingIcon,
- cell: (p) => {
- const icon = p.website ? (
-
- ) : (
-
- )
- return dataGridCellWithIcon(icon, {p.name})
- },
+ cell: (p) => (
+
+ ),
},
{
key: 'api',
header: 'API',
icon: PlugIcon,
- cell: (p) => {p.apiType},
+ cell: (p) => {p.apiType},
},
{
key: 'cur',
@@ -104,7 +102,7 @@ function ProvidersPage() {
key: 'actions',
header: '',
sortable: false,
- className: 'w-24 text-right',
+ className: 'w-12 text-right',
cell: (p) => (
openEdit(p)}
diff --git a/apps/web/src/routes/_auth/renewals.tsx b/apps/web/src/routes/_auth/renewals.tsx
index 23728f5..5697aaf 100644
--- a/apps/web/src/routes/_auth/renewals.tsx
+++ b/apps/web/src/routes/_auth/renewals.tsx
@@ -16,7 +16,7 @@ import {
FramePanel,
FrameTitle,
} from '@/components/reui/frame'
-import { Badge } from '@cfdm/ui/components/badge'
+import { Badge } from '@/components/reui/badge'
import { Button } from '@cfdm/ui/components/button'
import { SelectField } from '@/components/select-field'
import { getPaidUntilDate } from '@/lib/paid-until'
@@ -197,7 +197,11 @@ function RenewalsPage() {
{item.sublabel}
- {item.overdue ? Просрочено : null}
+ {item.overdue ? (
+
+ Просрочено
+
+ ) : null}
{item.date.toLocaleDateString('ru-RU')}
diff --git a/apps/web/src/routes/_auth/tariffs.tsx b/apps/web/src/routes/_auth/tariffs.tsx
index 66a512f..bc89acf 100644
--- a/apps/web/src/routes/_auth/tariffs.tsx
+++ b/apps/web/src/routes/_auth/tariffs.tsx
@@ -6,7 +6,7 @@ import { toast } from 'sonner'
import { snapshotQueryOptions } from '@/queries/snapshot'
import { api, ApiError } from '@/lib/api-client'
import { Alert, AlertDescription, AlertTitle } from '@cfdm/ui/components/alert'
-import { Badge } from '@cfdm/ui/components/badge'
+import { Badge } from '@/components/reui/badge'
import { ResourcePage, columnDefFromDataGrid, loadStoredColumnVisibility, dataGridColumnVisibilityOptions } from '@/components/reui-kit'
import type { ColumnVisibilityState } from '@tanstack/react-table'
import type { DataGridColumn } from '@/components/data-grid-types'
@@ -224,7 +224,7 @@ function TariffsPage() {
header: 'Диск',
icon: HardDriveIcon,
sortValue: (t) => t.diskType ?? '',
- cell: (t) => {t.diskType ?? '—'},
+ cell: (t) => {t.diskType ?? '—'},
},
{
key: 'location',
diff --git a/apps/web/src/routes/_auth/vps.$vpsId.tsx b/apps/web/src/routes/_auth/vps.$vpsId.tsx
index bfbb6e4..b6b9f27 100644
--- a/apps/web/src/routes/_auth/vps.$vpsId.tsx
+++ b/apps/web/src/routes/_auth/vps.$vpsId.tsx
@@ -17,7 +17,7 @@ import { QueryState } from '@/components/query-state'
import { StatusBadge } from '@/components/status-badge'
import { Skeleton } from '@cfdm/ui/components/skeleton'
import { Button } from '@cfdm/ui/components/button'
-import { Badge } from '@cfdm/ui/components/badge'
+import { Badge } from '@/components/reui/badge'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@cfdm/ui/components/tabs'
import {
Frame,
@@ -200,8 +200,16 @@ function VpsDetailPage() {
- {row.project ? {row.project} : null}
- {row.environment ? {row.environment} : null}
+ {row.project ? (
+
+ {row.project}
+
+ ) : null}
+ {row.environment ? (
+
+ {row.environment}
+
+ ) : null}
}>
diff --git a/apps/web/src/routes/_auth/vps.tsx b/apps/web/src/routes/_auth/vps.tsx
index 68f2156..1ef1f66 100644
--- a/apps/web/src/routes/_auth/vps.tsx
+++ b/apps/web/src/routes/_auth/vps.tsx
@@ -10,11 +10,12 @@ import { normalizeRatesPayload, effectiveVpsTariffCurrency, formatInProviderCurr
import { PageShell } from '@/components/page-shell'
import { PageHeader } from '@/components/page-header'
import { Button } from '@cfdm/ui/components/button'
-import { Badge } from '@cfdm/ui/components/badge'
+import { Badge } from '@/components/reui/badge'
import { ResourcePage, columnDefFromDataGrid, loadStoredColumnVisibility, dataGridColumnVisibilityOptions } from '@/components/reui-kit'
import type { ColumnVisibilityState } from '@tanstack/react-table'
import type { DataGridColumn } from '@/components/data-grid-types'
-import { dataGridCellStack, dataGridCellWithFlag } from '@/components/data-grid-cells'
+import { DataGridNameCell, dataGridCellStack, dataGridCellWithFlag } from '@/components/data-grid-cells'
+import { StatusBadge } from '@/components/status-badge'
import { CountryFlag } from '@/components/country-flag'
import { QueryState } from '@/components/query-state'
import { EmptyState } from '@/components/empty-state'
@@ -344,30 +345,35 @@ function VpsPage() {
header: 'IP / DNS',
icon: GlobeIcon,
sortValue: (v) => v.ip || v.dns || '',
- cell: (v) =>
- dataGridCellStack(
- v.ip ? (
-
- ) : (
-
—
- ),
- v.dns ? (
-
}
- >
- {v.dns}
-
- ) : undefined,
- ),
+ cell: (v) => (
+
void copyText(v.ip, 'IP скопирован')}
+ >
+ {v.ip}
+
+ ) : (
+ —
+ )
+ }
+ subtitle={
+ v.dns ? (
+
+ {v.dns}
+
+ ) : undefined
+ }
+ />
+ ),
},
{
key: 'domains',
@@ -439,11 +445,11 @@ function VpsPage() {
cell: (v) => (
{v.access === 'shared' ? (
- Общий
+
+ Общий
+
) : null}
-
- {vpsStatusLabel(v.status)}
-
+
),
},
@@ -484,9 +490,9 @@ function VpsPage() {
cell: (v) => {
const ext = v as Vps & { lastHealthStatus?: string; monitoringEnabled?: boolean }
if (!ext.monitoringEnabled) return —
- if (ext.lastHealthStatus === 'up') return up
- if (ext.lastHealthStatus === 'down') return down
- return —
+ if (ext.lastHealthStatus === 'up') return
+ if (ext.lastHealthStatus === 'down') return
+ return —
},
},
{
@@ -529,7 +535,7 @@ function VpsPage() {
header: '',
sortable: false,
enableHiding: false,
- className: 'w-24 text-right',
+ className: 'w-12 text-right',
cell: (v) => (
openEdit(v)}
@@ -537,19 +543,18 @@ function VpsPage() {
deleteTitle="Удалить VPS?"
deleteDescription={`IP ${v.ip} будет удалён безвозвратно.`}
extra={
- v.access !== 'shared' ? (
-
- ) : null
+ v.access !== 'shared'
+ ? [
+ {
+ label: 'Доступ',
+ icon: Share2Icon,
+ onSelect: () => {
+ setAccessVps(v)
+ setAccessOpen(true)
+ },
+ },
+ ]
+ : undefined
}
/>
),
@@ -683,7 +688,6 @@ function VpsPage() {
data={section.items}
getRowId={(v) => v.id}
emptyTitle="VPS не найдены"
- pinLastColumn
dense={filters.tableCompact}
enableRowSelection
onRowSelectionChange={setSelectedIds}