feat(web): доработка UX/UI — дашборд, навигация и data foundation
Docker / build (push) Has been cancelled

Добавлены syncLog в snapshot, API статистики дашборда и маппинг цен тарифов; переработаны shell, главная страница, empty states и новые экраны журнала синка и проектов.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-06-28 00:27:09 +07:00
co-authored by Cursor
parent 9df91bf2cc
commit 1e291b3759
35 changed files with 1527 additions and 264 deletions
+23
View File
@@ -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'
)
}
+5
View File
@@ -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 {
+4 -2
View File
@@ -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
+13
View File
@@ -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} дн назад`
}