b6b8d6e0d6
Воронка подсвечивает красным шаг, где теряем больше всего людей. Гости с VPN и хостингов показаны отдельным числом, а не подмешаны в конверсию. Город при VPN честно помечается как недостоверный. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
66 lines
1.9 KiB
TypeScript
66 lines
1.9 KiB
TypeScript
import { apiClient } from './client';
|
|
|
|
/**
|
|
* SaaS-admin «Посетители» — свой счётчик (НЕ Метрика).
|
|
* Роботы сюда не попадают вовсе: они не исполняют JS страницы.
|
|
* Backend: AdminVisitorsController. Spec:
|
|
* docs/superpowers/specs/2026-07-13-visitors-analytics-design.md
|
|
*/
|
|
export type Period = 'today' | '7d' | '30d';
|
|
|
|
export interface Funnel {
|
|
period: string;
|
|
funnel: {
|
|
view: number;
|
|
alive: number;
|
|
cta_click: number;
|
|
register_open: number;
|
|
register_done: number;
|
|
login_done: number;
|
|
};
|
|
vpn_or_hosting: number;
|
|
}
|
|
|
|
export interface ChannelRow {
|
|
channel: string;
|
|
people: number;
|
|
cta_clicks: number;
|
|
registrations: number;
|
|
logins: number;
|
|
}
|
|
|
|
export interface VisitRow {
|
|
id: string;
|
|
first_seen_at: string;
|
|
channel: string;
|
|
device: string;
|
|
city: string | null;
|
|
asn_org: string | null;
|
|
is_datacenter: boolean;
|
|
is_human: boolean;
|
|
ip: string | null;
|
|
tenant_id: number | null;
|
|
steps: string[];
|
|
}
|
|
|
|
export interface PortalActivity {
|
|
screens: Array<{ screen: string; opens: number; people: number }>;
|
|
clients: Array<{ name: string; subdomain: string; last_seen_at: string }>;
|
|
}
|
|
|
|
export const getFunnel = (p: Period) =>
|
|
apiClient.get<Funnel>('/api/admin/visitors/funnel', { params: { period: p } }).then((r) => r.data);
|
|
|
|
export const getChannels = (p: Period) =>
|
|
apiClient
|
|
.get<{ channels: ChannelRow[] }>('/api/admin/visitors/channels', { params: { period: p } })
|
|
.then((r) => r.data.channels);
|
|
|
|
export const getVisits = (p: Period) =>
|
|
apiClient
|
|
.get<{ visits: VisitRow[] }>('/api/admin/visitors/visits', { params: { period: p } })
|
|
.then((r) => r.data.visits);
|
|
|
|
export const getPortalActivity = (p: Period) =>
|
|
apiClient.get<PortalActivity>('/api/admin/visitors/portal', { params: { period: p } }).then((r) => r.data);
|