Files
portal/app/resources/js/api/adminDashboard.ts
T
Дмитрий 6536c19c96 feat(дашборд): Этап A — сквозная вложенность Лиды до источника
Экран «Лиды» (/admin/leads): серверный список с фильтрами (дата/канал/поставщик/
статус/поиск) + пагинация (масштаб 10⁴+ лидов). Карточка лида (/admin/leads/{id}):
полная цепочка — ОТКУДА (поставщик B1/B2/B3 + канал + источник + регион) → КОМУ
(сделки клиентов через deals.source_crm_id = supplier_leads.vid). Дашборд: drill
Лиды +топ-10 последних + «Открыть все лиды →». Nav-пункт «Лиды». ПДн-телефон
маскируется (152-ФЗ). Тесты: backend 3 + FE 5 (38 FE всего зелёные).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 10:14:47 +03:00

210 lines
5.6 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;
};
clients: {
light: Light;
total_active: number;
new_count: number;
logged_in: number;
dormant: number;
};
}
export interface ClientsDetail {
kpi: {
total_active: number;
new_count: number;
logged_in: number;
got_leads: number;
paid: number;
};
new_clients: Array<{
id: number;
organization_name: string;
subdomain: string;
status: string;
created_at: string | null;
last_login_at: string | null;
delivered_in_month: number;
balance_rub: string;
}>;
dormant: Array<{
id: number;
organization_name: string;
subdomain: string;
last_login_at: string | null;
balance_rub: string;
}>;
}
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;
};
recent: Array<{
id: number;
received_at: string;
platform: string;
channel: string | null;
source: string | null;
phone_masked: string;
delivered: boolean;
processed: boolean;
}>;
}
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 }>;
}
/** Параметры периода: либо preset `period`, либо свой диапазон `date_from`/`date_to`. */
export interface PeriodParams {
period?: string;
date_from?: string;
date_to?: string;
}
export async function getDashboardSummary(params: PeriodParams): Promise<DashboardSummary> {
const { data } = await apiClient.get<DashboardSummary>('/api/admin/dashboard', { params });
return data;
}
export async function getDashboardFinance(params: PeriodParams): Promise<FinanceDetail> {
const { data } = await apiClient.get<FinanceDetail>('/api/admin/dashboard/finance', { params });
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;
}
export async function getDashboardClients(params: PeriodParams): Promise<ClientsDetail> {
const { data } = await apiClient.get<ClientsDetail>('/api/admin/dashboard/clients', { params });
return data;
}