Files
bedolaga-cabinet/src/api/admin.ts
Fringg 84f0e4e9b4 feat: add media attachment support for admin ticket replies
Admin can now attach photos, videos, and documents when replying to
tickets. Upload happens on file selection via existing media upload
endpoint. Includes attachment preview with type-specific icons,
upload progress, error handling, race condition protection, and
proper blob URL lifecycle management.

Added video player rendering for video attachments in message thread.
Updated ticket list preview to show [media_type] for media-only messages.
2026-03-19 06:39:05 +03:00

381 lines
9.6 KiB
TypeScript

import apiClient from './client';
export interface AdminTicketUser {
id: number;
telegram_id: number;
username: string | null;
first_name: string | null;
last_name: string | null;
}
export interface AdminTicketMessage {
id: number;
message_text: string;
is_from_admin: boolean;
has_media: boolean;
media_type: string | null;
media_file_id: string | null;
media_caption: string | null;
created_at: string;
}
export interface AdminTicket {
id: number;
title: string;
status: string;
priority: string;
created_at: string;
updated_at: string;
closed_at: string | null;
messages_count: number;
user: AdminTicketUser | null;
last_message: AdminTicketMessage | null;
}
export interface AdminTicketDetail {
id: number;
title: string;
status: string;
priority: string;
created_at: string;
updated_at: string;
closed_at: string | null;
is_reply_blocked: boolean;
user: AdminTicketUser | null;
messages: AdminTicketMessage[];
}
export interface AdminTicketStats {
total: number;
open: number;
pending: number;
answered: number;
closed: number;
}
export interface TicketSettings {
sla_enabled: boolean;
sla_minutes: number;
sla_check_interval_seconds: number;
sla_reminder_cooldown_minutes: number;
support_system_mode: string; // tickets, contact, both
cabinet_user_notifications_enabled: boolean;
cabinet_admin_notifications_enabled: boolean;
}
export interface TicketSettingsUpdate {
sla_enabled?: boolean;
sla_minutes?: number;
sla_check_interval_seconds?: number;
sla_reminder_cooldown_minutes?: number;
support_system_mode?: string;
cabinet_user_notifications_enabled?: boolean;
cabinet_admin_notifications_enabled?: boolean;
}
export interface AdminTicketListResponse {
items: AdminTicket[];
total: number;
page: number;
per_page: number;
pages: number;
}
export const adminApi = {
// Check if current user is admin
checkIsAdmin: async (): Promise<{ is_admin: boolean }> => {
const response = await apiClient.get('/cabinet/auth/me/is-admin');
return response.data;
},
// Get ticket statistics
getTicketStats: async (): Promise<AdminTicketStats> => {
const response = await apiClient.get('/cabinet/admin/tickets/stats');
return response.data;
},
// Get all tickets
getTickets: async (
params: {
page?: number;
per_page?: number;
status?: string;
priority?: string;
user_id?: number;
} = {},
): Promise<AdminTicketListResponse> => {
const response = await apiClient.get('/cabinet/admin/tickets', { params });
return response.data;
},
// Get single ticket with messages
getTicket: async (ticketId: number): Promise<AdminTicketDetail> => {
const response = await apiClient.get(`/cabinet/admin/tickets/${ticketId}`);
return response.data;
},
// Reply to ticket
replyToTicket: async (
ticketId: number,
message: string,
media?: { media_type?: string; media_file_id?: string; media_caption?: string },
): Promise<AdminTicketMessage> => {
const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/reply`, {
message,
...media,
});
return response.data;
},
// Update ticket status
updateTicketStatus: async (ticketId: number, status: string): Promise<AdminTicketDetail> => {
const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/status`, { status });
return response.data;
},
// Update ticket priority
updateTicketPriority: async (ticketId: number, priority: string): Promise<AdminTicketDetail> => {
const response = await apiClient.post(`/cabinet/admin/tickets/${ticketId}/priority`, {
priority,
});
return response.data;
},
// Get ticket settings
getTicketSettings: async (): Promise<TicketSettings> => {
const response = await apiClient.get('/cabinet/admin/tickets/settings');
return response.data;
},
// Update ticket settings
updateTicketSettings: async (settings: TicketSettingsUpdate): Promise<TicketSettings> => {
const response = await apiClient.patch('/cabinet/admin/tickets/settings', settings);
return response.data;
},
};
export interface NodeStatus {
uuid: string;
name: string;
address: string;
is_connected: boolean;
is_disabled: boolean;
users_online: number;
traffic_used_bytes?: number;
uptime?: string;
xray_version?: string;
node_version?: string;
last_status_message?: string;
xray_uptime?: string;
is_xray_running?: boolean;
cpu_count?: number;
cpu_model?: string;
total_ram?: string;
country_code?: string;
}
export interface NodesOverview {
total: number;
online: number;
offline: number;
disabled: number;
total_users_online: number;
nodes: NodeStatus[];
}
export interface RevenueData {
date: string;
amount_kopeks: number;
amount_rubles: number;
}
export interface SubscriptionStats {
total: number;
active: number;
trial: number;
paid: number;
expired: number;
purchased_today: number;
purchased_week: number;
purchased_month: number;
trial_to_paid_conversion: number;
}
export interface FinancialStats {
income_today_kopeks: number;
income_today_rubles: number;
income_month_kopeks: number;
income_month_rubles: number;
income_total_kopeks: number;
income_total_rubles: number;
subscription_income_kopeks: number;
subscription_income_rubles: number;
}
export interface ServerStats {
total_servers: number;
available_servers: number;
servers_with_connections: number;
total_revenue_kopeks: number;
total_revenue_rubles: number;
}
export interface TariffStatItem {
tariff_id: number;
tariff_name: string;
active_subscriptions: number;
trial_subscriptions: number;
purchased_today: number;
purchased_week: number;
purchased_month: number;
}
export interface TariffStats {
tariffs: TariffStatItem[];
total_tariff_subscriptions: number;
}
export interface DashboardStats {
nodes: NodesOverview;
subscriptions: SubscriptionStats;
financial: FinancialStats;
servers: ServerStats;
revenue_chart: RevenueData[];
tariff_stats?: TariffStats;
}
export interface TopReferrerItem {
user_id: number;
telegram_id: number | null;
username?: string;
email?: string | null;
display_name: string;
invited_count: number;
invited_today: number;
invited_week: number;
invited_month: number;
earnings_today_kopeks: number;
earnings_week_kopeks: number;
earnings_month_kopeks: number;
earnings_total_kopeks: number;
}
export interface TopReferrersResponse {
by_earnings: TopReferrerItem[];
by_invited: TopReferrerItem[];
total_referrers: number;
total_referrals: number;
total_earnings_kopeks: number;
}
export interface TopCampaignItem {
id: number;
name: string;
start_parameter: string;
bonus_type: string;
is_active: boolean;
registrations: number;
conversions: number;
conversion_rate: number;
total_revenue_kopeks: number;
avg_revenue_per_user_kopeks: number;
created_at?: string;
}
export interface TopCampaignsResponse {
campaigns: TopCampaignItem[];
total_campaigns: number;
total_registrations: number;
total_revenue_kopeks: number;
}
export interface RecentPaymentItem {
id: number;
user_id: number;
telegram_id: number | null;
email?: string | null;
username?: string | null;
display_name: string;
amount_kopeks: number;
amount_rubles: number;
type: string;
type_display: string;
payment_method?: string | null;
description?: string | null;
created_at: string;
is_completed: boolean;
}
export interface RecentPaymentsResponse {
payments: RecentPaymentItem[];
total_count: number;
total_today_kopeks: number;
total_week_kopeks: number;
}
export interface SystemInfo {
bot_version: string;
python_version: string;
uptime_seconds: number;
users_total: number;
subscriptions_active: number;
}
export const statsApi = {
// Get system info
getSystemInfo: async (): Promise<SystemInfo> => {
const response = await apiClient.get('/cabinet/admin/stats/system-info');
return response.data;
},
// Get complete dashboard stats
getDashboardStats: async (): Promise<DashboardStats> => {
const response = await apiClient.get('/cabinet/admin/stats/dashboard');
return response.data;
},
// Get nodes status
getNodesStatus: async (): Promise<NodesOverview> => {
const response = await apiClient.get('/cabinet/admin/stats/nodes');
return response.data;
},
// Restart a node
restartNode: async (nodeUuid: string): Promise<{ success: boolean; message: string }> => {
const response = await apiClient.post(`/cabinet/admin/stats/nodes/${nodeUuid}/restart`);
return response.data;
},
// Toggle node (enable/disable)
toggleNode: async (
nodeUuid: string,
): Promise<{ success: boolean; message: string; is_disabled: boolean }> => {
const response = await apiClient.post(`/cabinet/admin/stats/nodes/${nodeUuid}/toggle`);
return response.data;
},
// Get top referrers
getTopReferrers: async (limit: number = 20): Promise<TopReferrersResponse> => {
const response = await apiClient.get('/cabinet/admin/stats/referrals/top', {
params: { limit },
});
return response.data;
},
// Get top campaigns
getTopCampaigns: async (limit: number = 20): Promise<TopCampaignsResponse> => {
const response = await apiClient.get('/cabinet/admin/stats/campaigns/top', {
params: { limit },
});
return response.data;
},
// Get recent payments
getRecentPayments: async (limit: number = 50): Promise<RecentPaymentsResponse> => {
const response = await apiClient.get('/cabinet/admin/stats/payments/recent', {
params: { limit },
});
return response.data;
},
};