fix: admin per-subscription panel data + hide purchased tariffs in create

- getPanelInfo/getNodeUsage/getUserDevices: pass subscriptionId
- deleteUserDevice/resetUserDevices: pass subscriptionId
- Reload panel data when switching between subscriptions
- Create subscription: filter out already purchased tariffs
This commit is contained in:
c0mrade
2026-03-24 16:55:09 +03:00
parent fe75fa45f0
commit c7c2167908
2 changed files with 46 additions and 20 deletions

View File

@@ -603,26 +603,33 @@ export const adminUsersApi = {
},
// Get panel info
getPanelInfo: async (userId: number): Promise<UserPanelInfo> => {
const response = await apiClient.get(`/cabinet/admin/users/${userId}/panel-info`);
getPanelInfo: async (userId: number, subscriptionId?: number): Promise<UserPanelInfo> => {
const response = await apiClient.get(`/cabinet/admin/users/${userId}/panel-info`, {
params: subscriptionId != null ? { subscription_id: subscriptionId } : undefined,
});
return response.data;
},
// Get node usage (always 30 days with daily breakdown)
getNodeUsage: async (userId: number): Promise<UserNodeUsageResponse> => {
const response = await apiClient.get(`/cabinet/admin/users/${userId}/node-usage`);
getNodeUsage: async (userId: number, subscriptionId?: number): Promise<UserNodeUsageResponse> => {
const response = await apiClient.get(`/cabinet/admin/users/${userId}/node-usage`, {
params: subscriptionId != null ? { subscription_id: subscriptionId } : undefined,
});
return response.data;
},
// Get user devices
getUserDevices: async (
userId: number,
subscriptionId?: number,
): Promise<{
devices: { hwid: string; platform: string; device_model: string; created_at: string | null }[];
total: number;
device_limit: number;
}> => {
const response = await apiClient.get(`/cabinet/admin/users/${userId}/devices`);
const response = await apiClient.get(`/cabinet/admin/users/${userId}/devices`, {
params: subscriptionId != null ? { subscription_id: subscriptionId } : undefined,
});
return response.data;
},
@@ -630,16 +637,22 @@ export const adminUsersApi = {
deleteUserDevice: async (
userId: number,
hwid: string,
subscriptionId?: number,
): Promise<{ success: boolean; message: string; deleted_hwid: string | null }> => {
const response = await apiClient.delete(`/cabinet/admin/users/${userId}/devices/${hwid}`);
const response = await apiClient.delete(`/cabinet/admin/users/${userId}/devices/${hwid}`, {
params: subscriptionId != null ? { subscription_id: subscriptionId } : undefined,
});
return response.data;
},
// Reset all devices
resetUserDevices: async (
userId: number,
subscriptionId?: number,
): Promise<{ success: boolean; message: string; deleted_count: number }> => {
const response = await apiClient.delete(`/cabinet/admin/users/${userId}/devices`);
const response = await apiClient.delete(`/cabinet/admin/users/${userId}/devices`, {
params: subscriptionId != null ? { subscription_id: subscriptionId } : undefined,
});
return response.data;
},

View File

@@ -443,27 +443,27 @@ export default function AdminUserDetail() {
if (!userId) return;
try {
setPanelInfoLoading(true);
const data = await adminUsersApi.getPanelInfo(userId);
const data = await adminUsersApi.getPanelInfo(userId, activeSubscriptionId ?? undefined);
setPanelInfo(data);
} catch {
} finally {
setPanelInfoLoading(false);
}
}, [userId]);
}, [userId, activeSubscriptionId]);
const loadNodeUsage = useCallback(async () => {
if (!userId) return;
try {
const data = await adminUsersApi.getNodeUsage(userId);
const data = await adminUsersApi.getNodeUsage(userId, activeSubscriptionId ?? undefined);
setNodeUsage(data);
} catch {}
}, [userId]);
}, [userId, activeSubscriptionId]);
const loadDevices = useCallback(async () => {
if (!userId) return;
try {
setDevicesLoading(true);
const data = await adminUsersApi.getUserDevices(userId);
const data = await adminUsersApi.getUserDevices(userId, activeSubscriptionId ?? undefined);
setDevices(data.devices);
setDevicesTotal(data.total);
setDeviceLimit(data.device_limit);
@@ -471,7 +471,7 @@ export default function AdminUserDetail() {
} finally {
setDevicesLoading(false);
}
}, [userId]);
}, [userId, activeSubscriptionId]);
const loadSubscriptionData = useCallback(async () => {
await Promise.all([loadPanelInfo(), loadNodeUsage(), loadDevices()]);
@@ -697,7 +697,7 @@ export default function AdminUserDetail() {
if (!userId) return;
setActionLoading(true);
try {
await adminUsersApi.deleteUserDevice(userId, hwid);
await adminUsersApi.deleteUserDevice(userId, hwid, activeSubscriptionId ?? undefined);
notify.success(t('admin.users.detail.devices.deleted'));
await loadDevices();
} catch {
@@ -711,7 +711,7 @@ export default function AdminUserDetail() {
if (!userId) return;
setActionLoading(true);
try {
await adminUsersApi.resetUserDevices(userId);
await adminUsersApi.resetUserDevices(userId, activeSubscriptionId ?? undefined);
notify.success(t('admin.users.detail.devices.allDeleted'));
await loadDevices();
} catch {
@@ -1681,11 +1681,24 @@ export default function AdminUserDetail() {
className="input"
>
<option value="">{t('admin.users.detail.subscription.selectTariff')}</option>
{tariffs.map((tariffItem) => (
<option key={tariffItem.id} value={tariffItem.id}>
{tariffItem.name}
</option>
))}
{tariffs
.filter((tariffItem) => {
// In multi-tariff: hide tariffs user already has
if (userSubscriptions.length > 0) {
const purchasedIds = new Set(
userSubscriptions
.filter((s) => s.is_active || s.status === 'trial')
.map((s) => s.tariff_id),
);
return !purchasedIds.has(tariffItem.id);
}
return true;
})
.map((tariffItem) => (
<option key={tariffItem.id} value={tariffItem.id}>
{tariffItem.name}
</option>
))}
</select>
<input
type="number"