Files
portal/app/resources/js/api/adminDashboard.ts
T
Дмитрий 02a8a90e4d feat дашборд: Этап 2 — живые плитки Лиды и Заказ у поставщика
Backend: AdminDashboardController +leads/+supply эндпоинты, summary дополнен
плитками leads/supply; сверка заказа вынесена в чистый сервис
SupplyReconciliation (спрос → формула computeOrder=max(max,⌈Σ/3⌉) → факт →
рассинхрон). Лиды: доставлено сегодня / зависшие 4ч+ / нераспределённые /
% доставки — cross-tenant под pgsql_admin.

Frontend: плитки Лиды и Заказ оживлены (убраны заглушки «Этап 2»), drill
с KPI и таблицей групп спрос→формула→факт→совпадает.

Тесты: SupplyReconciliation unit 3/3, Leads/Supply/Summary feature,
admin-срез 87 зелёных, фронт 10/10. stan 0, pint/eslint/type-check/build чисто.
phpstan-baseline перегенерирован (getJson false-positive на новых тестах).

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

123 lines
3.3 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;
failed_jobs_24h: number;
last_sync_status: string;
last_sync_at: string | null;
};
leads: {
light: Light;
delivered_today: number;
stuck: number;
unrouted: number;
delivery_pct: number;
};
supply: {
light: Light;
demand: number;
formula: number;
ordered: number;
mismatches: number;
};
}
export interface LeadsDetail {
light: Light;
kpi: {
delivered_today: number;
stuck: number;
unrouted: number;
unassigned_deals: number;
delivery_pct: number;
};
}
export interface SupplyDetail {
snapshot_date: string | null;
light: Light;
totals: { demand: number; formula: number; ordered: number; mismatches: 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;
}