diff --git a/apps/web/src/routes/_auth/providers.tsx b/apps/web/src/routes/_auth/providers.tsx
index d75aeb7..07ede40 100644
--- a/apps/web/src/routes/_auth/providers.tsx
+++ b/apps/web/src/routes/_auth/providers.tsx
@@ -1,7 +1,7 @@
import { createFileRoute } from '@tanstack/react-router'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useState } from 'react'
-import { PlusIcon, PencilIcon, Trash2Icon, BuildingIcon } from 'lucide-react'
+import { PlusIcon, PencilIcon, Trash2Icon, BuildingIcon, PlugIcon, CircleDollarSignIcon } from 'lucide-react'
import { toast } from 'sonner'
import { snapshotQueryOptions } from '@/queries/snapshot'
@@ -12,6 +12,7 @@ import { Button } from '@cfdm/ui/components/button'
import { Badge } from '@cfdm/ui/components/badge'
import { DataGridCard, columnDefFromDataTable } from '@/components/data-grid-card'
import type { DataTableColumn } from '@/components/data-table-card'
+import { dataGridCellWithIcon } from '@/components/data-grid-cells'
import { QueryState } from '@/components/query-state'
import { TableSkeleton } from '@/components/skeletons'
import { ConfirmDialog } from '@/components/confirm-dialog'
@@ -86,22 +87,32 @@ function ProvidersPage() {
{
key: 'name',
header: 'Хостер',
- cell: (p) => (
-
- {p.website ? (
-
})
- ) : (
-
- )}
-
{p.name}
-
- ),
+ icon: BuildingIcon,
+ cell: (p) => {
+ const icon = p.website ? (
+
})
+ ) : (
+
+ )
+ return dataGridCellWithIcon(icon,
{p.name})
+ },
+ },
+ {
+ key: 'api',
+ header: 'API',
+ icon: PlugIcon,
+ cell: (p) =>
{p.apiType},
+ },
+ {
+ key: 'cur',
+ header: 'Валюта',
+ icon: CircleDollarSignIcon,
+ cell: (p) =>
{p.baseCurrency ?? '—'},
},
- { key: 'api', header: 'API', cell: (p) =>
{p.apiType} },
- { key: 'cur', header: 'Валюта', cell: (p) =>
{p.baseCurrency ?? '—'} },
{
key: 'actions',
header: '',
+ sortable: false,
className: 'w-24 text-right',
cell: (p) => (
diff --git a/apps/web/src/routes/_auth/tariffs.tsx b/apps/web/src/routes/_auth/tariffs.tsx
index 248a9c5..431a0b5 100644
--- a/apps/web/src/routes/_auth/tariffs.tsx
+++ b/apps/web/src/routes/_auth/tariffs.tsx
@@ -7,10 +7,11 @@ import { PageHeader } from '@/components/page-header'
import { Badge } from '@cfdm/ui/components/badge'
import { DataGridCard, columnDefFromDataTable } from '@/components/data-grid-card'
import type { DataTableColumn } from '@/components/data-table-card'
+import { dataGridCellStack } from '@/components/data-grid-cells'
import { QueryState } from '@/components/query-state'
import { TableSkeleton } from '@/components/skeletons'
import { EmptyState } from '@/components/empty-state'
-import { ServerCogIcon } from 'lucide-react'
+import { ServerCogIcon, ServerIcon, UserRoundIcon, CpuIcon, CoinsIcon, HardDriveIcon } from 'lucide-react'
import type { ActiveTariff } from '@/types/entities'
import { providerByIdMap, accountSelectLabel } from '@/lib/billmanager'
@@ -30,19 +31,29 @@ function TariffsPage() {
{
key: 'name',
header: 'Тариф',
+ icon: ServerIcon,
cell: (t) =>
{t.name || `#${t.pricelistId ?? t.id}`},
},
{
key: 'account',
header: 'Аккаунт',
+ icon: UserRoundIcon,
+ sortValue: (t) => {
+ const acc = snapshot?.providerAccounts.find((a) => a.id === t.providerAccountId)
+ return acc ? accountSelectLabel(acc, providerById) : ''
+ },
cell: (t) => {
const acc = snapshot?.providerAccounts.find((a) => a.id === t.providerAccountId)
- return acc ? accountSelectLabel(acc, providerById) : '—'
+ if (!acc) return '—'
+ const providerName = providerById.get(acc.providerId)?.name ?? '—'
+ return dataGridCellStack(acc.name, providerName)
},
},
{
key: 'specs',
header: 'Ресурсы',
+ icon: CpuIcon,
+ sortValue: (t) => t.vcpu ?? 0,
cell: (t) => (
{t.vcpu ?? '—'} vCPU / {t.ramGb ?? '—'} GB / {t.diskGb ?? '—'} GB
@@ -52,9 +63,18 @@ function TariffsPage() {
{
key: 'price',
header: 'Цена/мес',
- cell: (t) => {formatCurrency(Number(t.monthlyRate ?? 0), t.currency ?? 'RUB')},
+ icon: CoinsIcon,
+ headerClassName: 'text-right',
+ className: 'text-right',
+ sortValue: (t) => Number(t.monthlyRate ?? 0),
+ cell: (t) => {formatCurrency(Number(t.monthlyRate ?? 0), t.currency ?? 'RUB')},
+ },
+ {
+ key: 'disk',
+ header: 'Диск',
+ icon: HardDriveIcon,
+ cell: (t) => {t.diskType ?? '—'},
},
- { key: 'disk', header: 'Диск', cell: (t) => {t.diskType ?? '—'} },
]
return (
diff --git a/apps/web/src/routes/_auth/vps.tsx b/apps/web/src/routes/_auth/vps.tsx
index c13eb8a..c9e00c5 100644
--- a/apps/web/src/routes/_auth/vps.tsx
+++ b/apps/web/src/routes/_auth/vps.tsx
@@ -2,7 +2,7 @@ import { createFileRoute } from '@tanstack/react-router'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useState, useMemo } from 'react'
import { Controller } from 'react-hook-form'
-import { PlusIcon, PencilIcon, Trash2Icon } from 'lucide-react'
+import { PlusIcon, PencilIcon, Trash2Icon, GlobeIcon, UserRoundIcon, FolderKanbanIcon, CpuIcon, CircleDotIcon, CreditCardIcon, MapPinIcon } from 'lucide-react'
import { toast } from 'sonner'
import { format, parseISO, isValid } from 'date-fns'
@@ -16,6 +16,7 @@ import { Button } from '@cfdm/ui/components/button'
import { Badge } from '@cfdm/ui/components/badge'
import { DataGridCard, columnDefFromDataTable } from '@/components/data-grid-card'
import type { DataTableColumn } from '@/components/data-table-card'
+import { dataGridCellStack, dataGridCellWithFlag } from '@/components/data-grid-cells'
import { QueryState } from '@/components/query-state'
import { TableSkeleton } from '@/components/skeletons'
import { ConfirmDialog } from '@/components/confirm-dialog'
@@ -216,25 +217,58 @@ function VpsPage() {
{
key: 'ip',
header: 'IP / DNS',
- cell: (v) => (
-
- {v.ip || '—'}
- {v.dns ? {v.dns} : null}
-
- ),
+ icon: GlobeIcon,
+ sortValue: (v) => v.ip || v.dns || '',
+ cell: (v) => dataGridCellStack(v.ip || '—', v.dns || undefined),
},
{
key: 'account',
header: 'Аккаунт',
+ icon: UserRoundIcon,
+ sortValue: (v) => {
+ const acc = snapshot?.providerAccounts.find((a) => a.id === v.providerAccountId)
+ return acc ? accountSelectLabel(acc, providerById) : ''
+ },
cell: (v) => {
const acc = snapshot?.providerAccounts.find((a) => a.id === v.providerAccountId)
- return acc ? accountSelectLabel(acc, providerById) : '—'
+ if (!acc) return '—'
+ const providerName = providerById.get(acc.providerId)?.name ?? '—'
+ return dataGridCellStack(acc.name, providerName)
},
},
- { key: 'project', header: 'Проект', cell: (v) => {v.project || '—'} },
+ {
+ key: 'location',
+ header: 'Локация',
+ icon: MapPinIcon,
+ sortValue: (v) => [v.country, v.city].filter(Boolean).join(', '),
+ cell: (v) => {
+ const country = v.country?.trim()
+ const city = v.city?.trim()
+ if (!country && !city) return —
+ const countryEntry = country ? COUNTRY_BY_NAME_RU[country] : undefined
+ const flag = countryEntry
+ ? getCountryFlagEmojiByCode(countryEntry.code)
+ : country
+ ? getCountryFlagEmoji(country)
+ : null
+ const primary = country || city || '—'
+ const secondary = country && city ? city : undefined
+ return flag
+ ? dataGridCellWithFlag(flag, primary, secondary)
+ : dataGridCellStack(primary, secondary)
+ },
+ },
+ {
+ key: 'project',
+ header: 'Проект',
+ icon: FolderKanbanIcon,
+ cell: (v) => {v.project || '—'},
+ },
{
key: 'specs',
header: 'Ресурсы',
+ icon: CpuIcon,
+ sortValue: (v) => v.vcpu,
cell: (v) => (
{v.vcpu} vCPU / {v.ramGb} GB / {v.diskGb} GB
@@ -244,6 +278,7 @@ function VpsPage() {
{
key: 'status',
header: 'Статус',
+ icon: CircleDotIcon,
cell: (v) => (
{vpsStatusLabel(v.status)}
@@ -253,21 +288,22 @@ function VpsPage() {
{
key: 'tariff',
header: 'Тариф',
+ icon: CreditCardIcon,
+ sortValue: (v) => (v.tariffType === 'daily' ? Number(v.dailyRate || 0) * 30 : Number(v.monthlyRate || 0)),
cell: (v) => {
const provider = providerById.get(v.providerId)
const currency = effectiveVpsTariffCurrency(v, provider)
const amount = v.tariffType === 'daily' ? Number(v.dailyRate || 0) * 30 : Number(v.monthlyRate || 0)
- return (
-
- {formatInProviderCurrency(amount, currency, provider, snapshot?.settings ?? [], ratesData)}
- {tariffTypeLabel(v.tariffType)}
-
+ return dataGridCellStack(
+ formatInProviderCurrency(amount, currency, provider, snapshot?.settings ?? [], ratesData),
+ tariffTypeLabel(v.tariffType),
)
},
},
{
key: 'actions',
header: '',
+ sortable: false,
className: 'w-24 text-right',
cell: (v) => (