feat(data-grid): add sorting function support for DataGridColumn and update vps tariff calculations
Docker / build (push) Failing after 22s

Enhanced DataGridColumn interface to include a sorting function option. Updated the frame-data-grid component to utilize the new sorting function. Introduced a new function for calculating comparable daily rates for VPS tariffs, improving the accuracy of monthly burn calculations. Updated the VpsPage to implement the new sorting function and daily rate logic.
This commit is contained in:
Denozordec
2026-07-31 17:00:25 +07:00
parent 53d5ed5d33
commit 5bbf0061cf
5 changed files with 118 additions and 7 deletions
@@ -8,6 +8,8 @@ export interface DataGridColumn<T> {
icon?: LucideIcon
sortable?: boolean
sortValue?: (row: T) => string | number
/** TanStack sortingFn; для числовых sortValue — `'basic'`. */
sortingFn?: 'auto' | 'alphanumeric' | 'basic' | 'text' | 'datetime'
headerTitle?: string
className?: string
headerClassName?: string
@@ -418,6 +418,7 @@ export function columnDefFromDataGrid<T>(
accessorFn: c.sortValue
? (row: T) => c.sortValue!(row)
: (row: T) => (row as Record<string, unknown>)[c.key] as string | number,
sortingFn: c.sortingFn ?? 'auto',
}
: {}),
header: Icon
+75
View File
@@ -0,0 +1,75 @@
import { describe, expect, it } from 'vitest'
import {
vpsTariffComparableDailyRate,
vpsTariffMonthlyBurn,
vpsTariffRateAmount,
} from './format'
describe('vpsTariffComparableDailyRate', () => {
it('нормализует месячный тариф к суточной стоимости', () => {
expect(
vpsTariffComparableDailyRate({
tariffType: 'monthly',
monthlyRate: 75,
dailyRate: null,
}),
).toBeCloseTo(75 / 30, 6)
})
it('оставляет суточный тариф как есть', () => {
expect(
vpsTariffComparableDailyRate({
tariffType: 'daily',
dailyRate: 5.59,
monthlyRate: 167.7,
}),
).toBeCloseTo(5.59, 6)
})
it('сортирует 5,59/сутки дороже 75/мес', () => {
const daily = vpsTariffComparableDailyRate({
tariffType: 'daily',
dailyRate: 5.59,
})
const monthly = vpsTariffComparableDailyRate({
tariffType: 'monthly',
monthlyRate: 75,
})
expect(daily).toBeGreaterThan(monthly)
})
it('для daily без dailyRate берёт monthly/30', () => {
expect(
vpsTariffComparableDailyRate({
tariffType: 'daily',
dailyRate: 0,
monthlyRate: 90,
}),
).toBeCloseTo(3, 6)
})
})
describe('vpsTariffMonthlyBurn', () => {
it('даёт месячный эквивалент суточного тарифа', () => {
expect(
vpsTariffMonthlyBurn({ tariffType: 'daily', dailyRate: 5.59 }),
).toBeCloseTo(5.59 * 30, 6)
})
it('для месячного возвращает monthlyRate', () => {
expect(
vpsTariffMonthlyBurn({ tariffType: 'monthly', monthlyRate: 75 }),
).toBeCloseTo(75, 6)
})
})
describe('vpsTariffRateAmount', () => {
it('для отображения берёт ставку периода без нормализации', () => {
expect(
vpsTariffRateAmount({ tariffType: 'daily', dailyRate: 5.59, monthlyRate: 167 }),
).toBe(5.59)
expect(
vpsTariffRateAmount({ tariffType: 'monthly', monthlyRate: 75, dailyRate: 2.5 }),
).toBe(75)
})
})
+23 -5
View File
@@ -151,16 +151,34 @@ export function vpsTariffRateAmount(vps: {
return monthly ?? 0
}
/** Месячный burn-rate для сортировки и отчётов. */
/**
* Сопоставимая суточная стоимость (единый знаменатель для сортировки).
* Месячный тариф → / 30; суточный → dailyRate (fallback: monthly/30).
*/
export function vpsTariffComparableDailyRate(vps: {
tariffType?: string | null
dailyRate?: number | string | null
monthlyRate?: number | string | null
}): number {
const daily = tariffRateNumber(vps.dailyRate)
const monthly = tariffRateNumber(vps.monthlyRate)
if (vps.tariffType === 'daily') {
if (daily != null && daily > 0) return daily
if (monthly != null && monthly > 0) return monthly / 30
return 0
}
if (monthly != null && monthly > 0) return monthly / 30
if (daily != null && daily > 0) return daily
return 0
}
/** Месячный burn-rate для сортировки и отчётов (= comparable daily × 30). */
export function vpsTariffMonthlyBurn(vps: {
tariffType?: string | null
dailyRate?: number | string | null
monthlyRate?: number | string | null
}): number {
const daily = tariffRateNumber(vps.dailyRate) ?? 0
const monthly = tariffRateNumber(vps.monthlyRate) ?? 0
if (vps.tariffType === 'daily') return daily * 30
return monthly
return vpsTariffComparableDailyRate(vps) * 30
}
const ENVIRONMENT_LABELS: Record<string, string> = {
+17 -2
View File
@@ -6,7 +6,7 @@ import { toast } from 'sonner'
import { snapshotQueryOptions, ratesQueryOptions } from '@/queries/snapshot'
import { api, ApiError } from '@/lib/api-client'
import type { VpsFormValues } from '@/lib/schemas'
import { normalizeRatesPayload, effectiveVpsTariffCurrency, formatInProviderCurrency, vpsStatusLabel, tariffTypeLabel, vpsTariffRateAmount, vpsTariffMonthlyBurn } from '@/lib/format'
import { normalizeRatesPayload, effectiveVpsTariffCurrency, formatInProviderCurrency, vpsStatusLabel, tariffTypeLabel, vpsTariffRateAmount, vpsTariffComparableDailyRate, convertWithProviderRate } from '@/lib/format'
import { PageShell } from '@/components/page-shell'
import { PageHeader } from '@/components/page-header'
import { Button } from '@cfdm/ui/components/button'
@@ -405,6 +405,7 @@ function VpsPage() {
key: 'specs',
header: 'Ресурсы',
icon: CpuIcon,
sortingFn: 'basic',
sortValue: (v) => v.vcpu,
cell: (v) => (
<span className="tabular-nums text-muted-foreground">
@@ -431,7 +432,21 @@ function VpsPage() {
key: 'tariff',
header: 'Тариф',
icon: CreditCardIcon,
sortValue: (v) => vpsTariffMonthlyBurn(v),
// Единый знаменатель: суточная стоимость в базовой валюте (5,59/сутки > 75/мес).
sortingFn: 'basic',
sortValue: (v) => {
const provider = providerById.get(v.providerId)
const daily = vpsTariffComparableDailyRate(v)
if (!(daily > 0)) return 0
const currency = effectiveVpsTariffCurrency(v, provider)
return convertWithProviderRate(
daily,
currency,
provider,
snapshot?.settings ?? null,
ratesData,
).value
},
cell: (v) => {
const provider = providerById.get(v.providerId)
const currency = effectiveVpsTariffCurrency(v, provider)