diff --git a/src/api/adminUsers.ts b/src/api/adminUsers.ts index 4406163..e8f90b6 100644 --- a/src/api/adminUsers.ts +++ b/src/api/adminUsers.ts @@ -134,10 +134,12 @@ export interface UserNodeUsageItem { node_name: string; country_code: string; total_bytes: number; + daily_bytes: number[]; } export interface UserNodeUsageResponse { items: UserNodeUsageItem[]; + categories: string[]; period_days: number; } @@ -545,11 +547,9 @@ export const adminUsersApi = { return response.data; }, - // Get node usage - getNodeUsage: async (userId: number, days = 7): Promise => { - const response = await apiClient.get(`/cabinet/admin/users/${userId}/node-usage`, { - params: { days }, - }); + // Get node usage (always 30 days with daily breakdown) + getNodeUsage: async (userId: number): Promise => { + const response = await apiClient.get(`/cabinet/admin/users/${userId}/node-usage`); return response.data; }, }; diff --git a/src/pages/AdminUserDetail.tsx b/src/pages/AdminUserDetail.tsx index 2da0495..c8f101f 100644 --- a/src/pages/AdminUserDetail.tsx +++ b/src/pages/AdminUserDetail.tsx @@ -268,18 +268,19 @@ export default function AdminUserDetail() { } }, [userId]); - const loadNodeUsage = useCallback( - async (days = 7) => { - if (!userId) return; - try { - const data = await adminUsersApi.getNodeUsage(userId, days); - setNodeUsage(data); - } catch { - // ignore - } - }, - [userId], - ); + const loadNodeUsage = useCallback(async () => { + if (!userId) return; + try { + const data = await adminUsersApi.getNodeUsage(userId); + setNodeUsage(data); + } catch { + // ignore + } + }, [userId]); + + const loadSubscriptionData = useCallback(async () => { + await Promise.all([loadPanelInfo(), loadNodeUsage()]); + }, [loadPanelInfo, loadNodeUsage]); const handleTicketReply = async () => { if (!selectedTicketId || !replyText.trim()) return; @@ -335,16 +336,10 @@ export default function AdminUserDetail() { if (activeTab === 'sync') loadSyncStatus(); if (activeTab === 'subscription') { loadTariffs(); - loadPanelInfo(); + loadSubscriptionData(); } if (activeTab === 'tickets') loadTickets(); - }, [activeTab, loadSyncStatus, loadTariffs, loadTickets, loadReferrals, loadPanelInfo]); - - useEffect(() => { - if (activeTab === 'subscription') { - loadNodeUsage(nodeUsageDays); - } - }, [activeTab, loadNodeUsage, nodeUsageDays]); + }, [activeTab, loadSyncStatus, loadTariffs, loadTickets, loadReferrals, loadSubscriptionData]); const handleUpdateBalance = async (isAdd: boolean) => { if (balanceAmount === '' || !userId) return; @@ -547,6 +542,19 @@ export default function AdminUserDetail() { }); }; + // Compute node usage for selected period from cached 30-day data + const nodeUsageForPeriod = (() => { + if (!nodeUsage || nodeUsage.items.length === 0) return []; + return nodeUsage.items + .map((item) => { + const daily = item.daily_bytes || []; + const sliced = daily.slice(-nodeUsageDays); + const total = sliced.reduce((sum, v) => sum + v, 0); + return { ...item, total_bytes: total }; + }) + .sort((a, b) => b.total_bytes - a.total_bytes); + })(); + const formatBytes = (bytes: number) => { if (bytes === 0) return '0 B'; if (bytes < 1024) return `${bytes} B`; @@ -1198,26 +1206,35 @@ export default function AdminUserDetail() { {t('admin.users.detail.nodeUsage')} -
- {[7, 14, 30].map((d) => ( - - ))} +
+
+ {[7, 14, 30].map((d) => ( + + ))} +
+
- {nodeUsage && nodeUsage.items.length > 0 ? ( + {nodeUsageForPeriod.length > 0 ? (
- {nodeUsage.items.map((item) => { - const maxBytes = nodeUsage.items[0].total_bytes; + {nodeUsageForPeriod.map((item) => { + const maxBytes = nodeUsageForPeriod[0].total_bytes; const pct = maxBytes > 0 ? (item.total_bytes / maxBytes) * 100 : 0; return (