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 icon?: LucideIcon
sortable?: boolean sortable?: boolean
sortValue?: (row: T) => string | number sortValue?: (row: T) => string | number
/** TanStack sortingFn; для числовых sortValue — `'basic'`. */
sortingFn?: 'auto' | 'alphanumeric' | 'basic' | 'text' | 'datetime'
headerTitle?: string headerTitle?: string
className?: string className?: string
headerClassName?: string headerClassName?: string
@@ -418,6 +418,7 @@ export function columnDefFromDataGrid<T>(
accessorFn: c.sortValue accessorFn: c.sortValue
? (row: T) => c.sortValue!(row) ? (row: T) => c.sortValue!(row)
: (row: T) => (row as Record<string, unknown>)[c.key] as string | number, : (row: T) => (row as Record<string, unknown>)[c.key] as string | number,
sortingFn: c.sortingFn ?? 'auto',
} }
: {}), : {}),
header: Icon 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 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: { export function vpsTariffMonthlyBurn(vps: {
tariffType?: string | null tariffType?: string | null
dailyRate?: number | string | null dailyRate?: number | string | null
monthlyRate?: number | string | null monthlyRate?: number | string | null
}): number { }): number {
const daily = tariffRateNumber(vps.dailyRate) ?? 0 return vpsTariffComparableDailyRate(vps) * 30
const monthly = tariffRateNumber(vps.monthlyRate) ?? 0
if (vps.tariffType === 'daily') return daily * 30
return monthly
} }
const ENVIRONMENT_LABELS: Record<string, string> = { 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 { snapshotQueryOptions, ratesQueryOptions } from '@/queries/snapshot'
import { api, ApiError } from '@/lib/api-client' import { api, ApiError } from '@/lib/api-client'
import type { VpsFormValues } from '@/lib/schemas' 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 { PageShell } from '@/components/page-shell'
import { PageHeader } from '@/components/page-header' import { PageHeader } from '@/components/page-header'
import { Button } from '@cfdm/ui/components/button' import { Button } from '@cfdm/ui/components/button'
@@ -405,6 +405,7 @@ function VpsPage() {
key: 'specs', key: 'specs',
header: 'Ресурсы', header: 'Ресурсы',
icon: CpuIcon, icon: CpuIcon,
sortingFn: 'basic',
sortValue: (v) => v.vcpu, sortValue: (v) => v.vcpu,
cell: (v) => ( cell: (v) => (
<span className="tabular-nums text-muted-foreground"> <span className="tabular-nums text-muted-foreground">
@@ -431,7 +432,21 @@ function VpsPage() {
key: 'tariff', key: 'tariff',
header: 'Тариф', header: 'Тариф',
icon: CreditCardIcon, 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) => { cell: (v) => {
const provider = providerById.get(v.providerId) const provider = providerById.get(v.providerId)
const currency = effectiveVpsTariffCurrency(v, provider) const currency = effectiveVpsTariffCurrency(v, provider)