feat(web): доработка UX/UI — дашборд, навигация и data foundation
Docker / build (push) Has been cancelled
Docker / build (push) Has been cancelled
Добавлены syncLog в snapshot, API статистики дашборда и маппинг цен тарифов; переработаны shell, главная страница, empty states и новые экраны журнала синка и проектов. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
/** Баланс API аккаунта (поддержка camelCase из API и snake_case в типах). */
|
||||
export function accountBalanceApi(account: {
|
||||
balance_api?: number | null
|
||||
balanceApi?: number | null
|
||||
}): number | null {
|
||||
const raw = account.balance_api ?? account.balanceApi
|
||||
if (raw == null) return null
|
||||
const n = Number(raw)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
|
||||
export function accountBalanceCurrency(account: {
|
||||
balance_currency?: string
|
||||
balanceCurrency?: string
|
||||
currency?: string
|
||||
}): string {
|
||||
return (
|
||||
account.balance_currency ??
|
||||
account.balanceCurrency ??
|
||||
account.currency ??
|
||||
'RUB'
|
||||
)
|
||||
}
|
||||
@@ -139,6 +139,11 @@ export const api = {
|
||||
if (!res.ok) throw new ApiError(res.statusText || 'Ошибка восстановления', res.status)
|
||||
return res.json()
|
||||
},
|
||||
|
||||
fetchDashboardStats: () =>
|
||||
fetchApi<import('@/queries/dashboard').DashboardStats>('/api/dashboard/stats'),
|
||||
|
||||
fetchProjects: () => fetchApi<{ id: string; name: string }[]>('/api/projects'),
|
||||
}
|
||||
|
||||
export type {
|
||||
|
||||
@@ -31,16 +31,18 @@ function ledgerBalanceInCurrency(
|
||||
return credits - debits
|
||||
}
|
||||
|
||||
import { accountBalanceApi } from '@/lib/account'
|
||||
|
||||
export function accountHasApiLedgerMismatch(
|
||||
account: ProviderAccount,
|
||||
balanceLedger: BalanceLedgerRow[],
|
||||
): boolean {
|
||||
if (account.balance_api == null || !Number.isFinite(Number(account.balance_api))) return false
|
||||
const apiBalance = accountBalanceApi(account)
|
||||
if (apiBalance == null) return false
|
||||
const rows = ledgerRowsInAccountCurrency(account, balanceLedger)
|
||||
if (rows.length === 0) return false
|
||||
const ledger = ledgerBalanceInCurrency(account, balanceLedger)
|
||||
if (!Number.isFinite(ledger)) return false
|
||||
const apiBalance = Number(account.balance_api)
|
||||
const diff = Math.abs(apiBalance - ledger)
|
||||
const tol = Math.max(10, Math.abs(apiBalance) * 0.05)
|
||||
return diff > tol
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
export function formatRelativeSyncTime(iso: string | null | undefined): string {
|
||||
if (!iso) return 'нет данных'
|
||||
const t = new Date(iso).getTime()
|
||||
if (Number.isNaN(t)) return 'нет данных'
|
||||
const diffMs = Date.now() - t
|
||||
const mins = Math.floor(diffMs / 60_000)
|
||||
if (mins < 1) return 'только что'
|
||||
if (mins < 60) return `${mins} мин назад`
|
||||
const hours = Math.floor(mins / 60)
|
||||
if (hours < 48) return `${hours} ч назад`
|
||||
const days = Math.floor(hours / 24)
|
||||
return `${days} дн назад`
|
||||
}
|
||||
Reference in New Issue
Block a user