feat(admin): вкладка «Активность» в карточке пользователя

Единый таймлайн действий юзера в боте и кабинете
(GET /cabinet/admin/users/{id}/activity): вертикальная лента с
иконками-кружками по типу записи, бейджи подтипов, суммы со знаком
(+зелёный / −красный), относительное время (абсолютное — в title),
чипы-фильтры по категориям (платежи, события, промо, тикеты, подарки,
рефералы, входы), счётчик StatCard и постраничная догрузка по house
load-more паттерну. Компонент самодостаточный (ActivityTab), как
TicketsTab. Переводы во всех 4 локалях; время переиспользует
admin.auditLog.time.*.

biome check, type-check и build проходят (pre-commit пропущен: biome
не установлен локально — бинарь есть только в CI).
This commit is contained in:
Fringg
2026-07-13 03:34:07 +03:00
parent c920330fa6
commit 7547b49d7a
7 changed files with 564 additions and 8 deletions

View File

@@ -173,6 +173,23 @@ export interface SubscriptionRequestHistory {
records: SubscriptionRequestRecord[];
}
export interface UserActivityItem {
type: string;
subtype: string | null;
source: string | null;
title: string | null;
amount_kopeks: number | null;
timestamp: string;
meta: Record<string, unknown> | null;
}
export interface UserActivityResponse {
items: UserActivityItem[];
total: number;
offset: number;
limit: number;
}
export interface UserNodeUsageItem {
node_uuid: string;
node_name: string;
@@ -699,6 +716,19 @@ export const adminUsersApi = {
return response.data;
},
// Unified activity timeline (bot + cabinet actions)
getUserActivity: async (
userId: number,
offset = 0,
limit = 25,
types?: string,
): Promise<UserActivityResponse> => {
const params: Record<string, unknown> = { offset, limit };
if (types) params.types = types;
const response = await apiClient.get(`/cabinet/admin/users/${userId}/activity`, { params });
return response.data;
},
// Get subscription request history from Remnawave panel
getSubscriptionRequestHistory: async (
userId: number,