fix(sync): загрузка тарифов при обычной синхронизации аккаунтов
Docker / build (push) Has been cancelled
Docker / build (push) Has been cancelled
Обычный синк больше не пропускает тарифы; добавлены кнопки «Синхронизировать все» и «Загрузить тарифы», исправлен dropdown на странице аккаунтов. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -73,7 +73,7 @@ export const syncRoutes: FastifyPluginAsync = async (app) => {
|
||||
}
|
||||
|
||||
const onlyTariffs = Boolean(req.body?.onlyTariffs)
|
||||
const opts = onlyTariffs ? { skipVpsPayments: true } : { skipTariffs: true }
|
||||
const opts = onlyTariffs ? { skipVpsPayments: true } : {}
|
||||
|
||||
try {
|
||||
const result = await runBillmanagerAccountSync(syncRow, opts)
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
import { accountBalanceApi, accountBalanceCurrency } from '@/lib/account'
|
||||
|
||||
import type { ProviderAccount, BillingMode } from '@/types/entities'
|
||||
import { providerByIdMap, accountBillmanagerUiReady } from '@/lib/billmanager'
|
||||
import { providerByIdMap, accountBillmanagerUiReady, billmanagerSyncableAccounts } from '@/lib/billmanager'
|
||||
import { billingModeLabel, formatCurrency } from '@/lib/format'
|
||||
|
||||
export const Route = createFileRoute('/_auth/accounts')({
|
||||
@@ -98,9 +98,29 @@ function AccountsPage() {
|
||||
})
|
||||
const syncMut = useMutation({
|
||||
mutationFn: (id: string) => api.syncAccount(id),
|
||||
onSuccess: (data) => {
|
||||
void queryClient.invalidateQueries({ queryKey: ['snapshot'] })
|
||||
const synced = (data as { synced?: { vpsCount?: number; paymentsCount?: number; tariffsCount?: number } })
|
||||
?.synced
|
||||
const parts: string[] = []
|
||||
if (synced?.vpsCount != null) parts.push(`VPS ${synced.vpsCount}`)
|
||||
if (synced?.paymentsCount) parts.push(`платежи ${synced.paymentsCount}`)
|
||||
if (synced?.tariffsCount) parts.push(`тарифы ${synced.tariffsCount}`)
|
||||
toast.success(parts.length ? `Синк: ${parts.join(', ')}` : 'Синк завершён')
|
||||
},
|
||||
onError: (e: unknown) => toast.error(e instanceof ApiError ? e.message : 'Ошибка синка'),
|
||||
})
|
||||
const syncAllMut = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!snapshot) return
|
||||
const accounts = billmanagerSyncableAccounts(snapshot.providerAccounts, snapshot.providers)
|
||||
for (const a of accounts) {
|
||||
await api.syncAccount(a.id)
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ['snapshot'] })
|
||||
toast.success('Синк запущен')
|
||||
toast.success('Синхронизация всех аккаунтов завершена')
|
||||
},
|
||||
onError: (e: unknown) => toast.error(e instanceof ApiError ? e.message : 'Ошибка синка'),
|
||||
})
|
||||
@@ -122,6 +142,9 @@ function AccountsPage() {
|
||||
}
|
||||
|
||||
const providerById = snapshot ? providerByIdMap(snapshot.providers) : new Map()
|
||||
const syncableCount = snapshot
|
||||
? billmanagerSyncableAccounts(snapshot.providerAccounts, snapshot.providers).length
|
||||
: 0
|
||||
|
||||
const columns: DataTableColumn<ProviderAccount>[] = [
|
||||
{
|
||||
@@ -184,7 +207,7 @@ function AccountsPage() {
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuContent align="end" className="w-auto min-w-44">
|
||||
<DropdownMenuItem
|
||||
disabled={!canSync || (syncMut.isPending && syncMut.variables === a.id)}
|
||||
onClick={() => syncMut.mutate(a.id)}
|
||||
@@ -223,7 +246,21 @@ function AccountsPage() {
|
||||
<PageHeader
|
||||
title="Аккаунты хостеров"
|
||||
description="Аккаунты провайдеров с API-доступом"
|
||||
actions={<Button onClick={openCreate}><PlusIcon data-icon="inline-start" />Добавить</Button>}
|
||||
actions={
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{syncableCount > 0 ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={syncAllMut.isPending}
|
||||
onClick={() => syncAllMut.mutate()}
|
||||
>
|
||||
<RefreshCwIcon data-icon="inline-start" />
|
||||
Синхронизировать все
|
||||
</Button>
|
||||
) : null}
|
||||
<Button onClick={openCreate}><PlusIcon data-icon="inline-start" />Добавить</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<QueryState
|
||||
data={snapshot}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { createFileRoute, Link } from '@tanstack/react-router'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { snapshotQueryOptions } from '@/queries/snapshot'
|
||||
import { api, ApiError } from '@/lib/api-client'
|
||||
import { PageShell } from '@/components/page-shell'
|
||||
import { PageHeader } from '@/components/page-header'
|
||||
import { Badge } from '@cfdm/ui/components/badge'
|
||||
@@ -11,10 +13,10 @@ import { dataGridCellStack } from '@/components/data-grid-cells'
|
||||
import { QueryState } from '@/components/query-state'
|
||||
import { TableSkeleton } from '@/components/skeletons'
|
||||
import { Button } from '@cfdm/ui/components/button'
|
||||
import { ServerIcon, UserRoundIcon, CpuIcon, CoinsIcon, HardDriveIcon } from 'lucide-react'
|
||||
import { ServerIcon, UserRoundIcon, CpuIcon, CoinsIcon, HardDriveIcon, RefreshCwIcon } from 'lucide-react'
|
||||
|
||||
import type { ActiveTariff } from '@/types/entities'
|
||||
import { providerByIdMap, accountSelectLabel } from '@/lib/billmanager'
|
||||
import { providerByIdMap, accountSelectLabel, billmanagerSyncableAccounts } from '@/lib/billmanager'
|
||||
import { formatCurrency } from '@/lib/format'
|
||||
|
||||
export const Route = createFileRoute('/_auth/tariffs')({
|
||||
@@ -24,8 +26,36 @@ export const Route = createFileRoute('/_auth/tariffs')({
|
||||
})
|
||||
|
||||
function TariffsPage() {
|
||||
const queryClient = useQueryClient()
|
||||
const { data: snapshot, isLoading, isError, error, refetch } = useQuery(snapshotQueryOptions())
|
||||
const providerById = snapshot ? providerByIdMap(snapshot.providers) : new Map()
|
||||
const syncableCount = snapshot
|
||||
? billmanagerSyncableAccounts(snapshot.providerAccounts, snapshot.providers).length
|
||||
: 0
|
||||
|
||||
const syncTariffsMut = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!snapshot) return { tariffsCount: 0 }
|
||||
const accounts = billmanagerSyncableAccounts(snapshot.providerAccounts, snapshot.providers)
|
||||
let tariffsCount = 0
|
||||
for (const a of accounts) {
|
||||
const res = (await api.syncAccount(a.id)) as {
|
||||
synced?: { tariffsCount?: number }
|
||||
}
|
||||
tariffsCount += res?.synced?.tariffsCount ?? 0
|
||||
}
|
||||
return { tariffsCount, accounts: accounts.length }
|
||||
},
|
||||
onSuccess: ({ tariffsCount, accounts }) => {
|
||||
void queryClient.invalidateQueries({ queryKey: ['snapshot'] })
|
||||
if (accounts === 0) {
|
||||
toast.error('Нет аккаунтов BILLmanager с настроенным API')
|
||||
return
|
||||
}
|
||||
toast.success(`Загружено тарифов: ${tariffsCount}`)
|
||||
},
|
||||
onError: (e: unknown) => toast.error(e instanceof ApiError ? e.message : 'Ошибка загрузки тарифов'),
|
||||
})
|
||||
|
||||
const columns: DataTableColumn<ActiveTariff>[] = [
|
||||
{
|
||||
@@ -79,7 +109,18 @@ function TariffsPage() {
|
||||
|
||||
return (
|
||||
<PageShell>
|
||||
<PageHeader title="Активные тарифы" description="Тарифы, загруженные из BILLmanager vds.order" />
|
||||
<PageHeader
|
||||
title="Активные тарифы"
|
||||
description="Тарифы, загруженные из BILLmanager vds.order"
|
||||
actions={
|
||||
syncableCount > 0 ? (
|
||||
<Button variant="outline" disabled={syncTariffsMut.isPending} onClick={() => syncTariffsMut.mutate()}>
|
||||
<RefreshCwIcon data-icon="inline-start" />
|
||||
Загрузить тарифы
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
<QueryState
|
||||
data={snapshot}
|
||||
isLoading={isLoading}
|
||||
@@ -89,9 +130,19 @@ function TariffsPage() {
|
||||
skeleton={<TableSkeleton />}
|
||||
empty={snapshot?.activeTariffs.length === 0}
|
||||
emptyTitle="Тарифы не загружены"
|
||||
emptyDescription="Выполните синхронизацию аккаунта BILLmanager, чтобы загрузить тарифы"
|
||||
emptyDescription="Синхронизация аккаунта BILLmanager загружает тарифы вместе с VPS и платежами"
|
||||
emptyAction={
|
||||
<Button render={<Link to="/accounts" />}>Перейти к аккаунтам</Button>
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{syncableCount > 0 ? (
|
||||
<Button disabled={syncTariffsMut.isPending} onClick={() => syncTariffsMut.mutate()}>
|
||||
<RefreshCwIcon data-icon="inline-start" />
|
||||
Загрузить тарифы
|
||||
</Button>
|
||||
) : null}
|
||||
<Button variant="outline" render={<Link to="/accounts" />}>
|
||||
Перейти к аккаунтам
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{(snap) => <DataGridCard columns={columnDefFromDataTable(columns)} data={snap.activeTariffs} rowId={(t) => t.id} />}
|
||||
|
||||
Reference in New Issue
Block a user