93e8393014
- 5-я плитка дашборда со светофором (worst-of сервисов, поддержка grey=нет данных) - Drill-таблица: Сервис · Баланс · Хватит на N дней · Статус · кнопка «Пополнить» - Кнопка «Пополнить» (target=_blank) → страница оплаты сервиса; YC — прямо на биллинг - Клиент getDashboardBalances + типы; Vitest 12/12 (тайл, drill, href кнопки) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
154 lines
4.1 KiB
TypeScript
154 lines
4.1 KiB
TypeScript
import { apiClient } from './client';
|
|
|
|
/**
|
|
* SaaS-admin «Командный центр» — типизированный клиент read-only агрегатов.
|
|
*
|
|
* Все 3 эндпоинта — GET под группой ['saas-admin','admin-db'] (cross-tenant
|
|
* через pgsql_admin). CSRF не нужен (только чтение).
|
|
* Backend: AdminDashboardController. Spec:
|
|
* docs/superpowers/specs/2026-06-27-admin-command-center-design.md
|
|
*/
|
|
|
|
export type Light = 'green' | 'amber' | 'red';
|
|
|
|
export interface DashboardSummary {
|
|
period: string;
|
|
finance: {
|
|
topups_rub: string;
|
|
charges_rub: string;
|
|
active_clients: number;
|
|
new_clients: number;
|
|
negative_balance_count: number;
|
|
light: Light;
|
|
};
|
|
health: {
|
|
light: Light;
|
|
open_incidents: number;
|
|
job_errors_24h: number;
|
|
failed_jobs_24h: number;
|
|
last_sync_status: string;
|
|
last_sync_at: string | null;
|
|
};
|
|
leads: {
|
|
light: Light;
|
|
delivered_today: number;
|
|
received_today: number;
|
|
stuck: number;
|
|
unrouted: number;
|
|
};
|
|
supply: {
|
|
light: Light;
|
|
demand: number;
|
|
formula: number;
|
|
ordered: number;
|
|
mismatches: number;
|
|
total_orders: number;
|
|
total_limit: number;
|
|
snapshot_date: string | null;
|
|
};
|
|
balances: {
|
|
light: Light | 'grey';
|
|
count: number;
|
|
red: number;
|
|
};
|
|
}
|
|
|
|
export interface BalancesDetail {
|
|
light: Light | 'grey';
|
|
services: Array<{
|
|
service_key: string;
|
|
balance_amount: string | null;
|
|
currency: string;
|
|
daily_spend_estimate: string | null;
|
|
days_left: number | null;
|
|
light: Light | 'grey';
|
|
ok: boolean;
|
|
error: string | null;
|
|
checked_at: string | null;
|
|
topup_url: string | null;
|
|
}>;
|
|
}
|
|
|
|
export interface LeadsDetail {
|
|
light: Light;
|
|
kpi: {
|
|
delivered_today: number;
|
|
received_today: number;
|
|
stuck: number;
|
|
unrouted: number;
|
|
};
|
|
}
|
|
|
|
export interface SupplyDetail {
|
|
snapshot_date: string | null;
|
|
light: Light;
|
|
totals: { demand: number; formula: number; ordered: number; mismatches: number };
|
|
total_orders: number;
|
|
total_limit: number;
|
|
groups: Array<{
|
|
signal_type: string;
|
|
identifier: string;
|
|
demand: number;
|
|
formula: number;
|
|
ordered: number;
|
|
in_sync: boolean;
|
|
}>;
|
|
}
|
|
|
|
export interface FinanceDetail {
|
|
period: string;
|
|
kpi: {
|
|
topups_rub: string;
|
|
charges_rub: string;
|
|
net_inflow_rub: string;
|
|
negative_balance_count: number;
|
|
};
|
|
attention: Array<{
|
|
id: number;
|
|
subdomain: string;
|
|
organization_name: string;
|
|
balance_rub: string;
|
|
state: string;
|
|
}>;
|
|
top_by_turnover: Array<{
|
|
id: number;
|
|
organization_name: string;
|
|
topped_rub: string;
|
|
}>;
|
|
}
|
|
|
|
export interface HealthDetail {
|
|
overall_light: Light;
|
|
subsystems: Array<{ key: string; light: Light; detail: string }>;
|
|
}
|
|
|
|
export async function getDashboardSummary(period: string): Promise<DashboardSummary> {
|
|
const { data } = await apiClient.get<DashboardSummary>('/api/admin/dashboard', { params: { period } });
|
|
return data;
|
|
}
|
|
|
|
export async function getDashboardFinance(period: string): Promise<FinanceDetail> {
|
|
const { data } = await apiClient.get<FinanceDetail>('/api/admin/dashboard/finance', { params: { period } });
|
|
return data;
|
|
}
|
|
|
|
export async function getDashboardHealth(): Promise<HealthDetail> {
|
|
const { data } = await apiClient.get<HealthDetail>('/api/admin/dashboard/health');
|
|
return data;
|
|
}
|
|
|
|
export async function getDashboardLeads(): Promise<LeadsDetail> {
|
|
const { data } = await apiClient.get<LeadsDetail>('/api/admin/dashboard/leads');
|
|
return data;
|
|
}
|
|
|
|
export async function getDashboardSupply(): Promise<SupplyDetail> {
|
|
const { data } = await apiClient.get<SupplyDetail>('/api/admin/dashboard/supply');
|
|
return data;
|
|
}
|
|
|
|
export async function getDashboardBalances(): Promise<BalancesDetail> {
|
|
const { data } = await apiClient.get<BalancesDetail>('/api/admin/dashboard/balances');
|
|
return data;
|
|
}
|