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

View File

@@ -443,27 +443,27 @@ export default function AdminUserDetail() {
if (!userId) return; if (!userId) return;
try { try {
setPanelInfoLoading(true); setPanelInfoLoading(true);
const data = await adminUsersApi.getPanelInfo(userId); const data = await adminUsersApi.getPanelInfo(userId, activeSubscriptionId ?? undefined);
setPanelInfo(data); setPanelInfo(data);
} catch { } catch {
} finally { } finally {
setPanelInfoLoading(false); setPanelInfoLoading(false);
} }
}, [userId]); }, [userId, activeSubscriptionId]);
const loadNodeUsage = useCallback(async () => { const loadNodeUsage = useCallback(async () => {
if (!userId) return; if (!userId) return;
try { try {
const data = await adminUsersApi.getNodeUsage(userId); const data = await adminUsersApi.getNodeUsage(userId, activeSubscriptionId ?? undefined);
setNodeUsage(data); setNodeUsage(data);
} catch {} } catch {}
}, [userId]); }, [userId, activeSubscriptionId]);
const loadDevices = useCallback(async () => { const loadDevices = useCallback(async () => {
if (!userId) return; if (!userId) return;
try { try {
setDevicesLoading(true); setDevicesLoading(true);
const data = await adminUsersApi.getUserDevices(userId); const data = await adminUsersApi.getUserDevices(userId, activeSubscriptionId ?? undefined);
setDevices(data.devices); setDevices(data.devices);
setDevicesTotal(data.total); setDevicesTotal(data.total);
setDeviceLimit(data.device_limit); setDeviceLimit(data.device_limit);
@@ -471,7 +471,7 @@ export default function AdminUserDetail() {
} finally { } finally {
setDevicesLoading(false); setDevicesLoading(false);
} }
}, [userId]); }, [userId, activeSubscriptionId]);
const loadSubscriptionData = useCallback(async () => { const loadSubscriptionData = useCallback(async () => {
await Promise.all([loadPanelInfo(), loadNodeUsage(), loadDevices()]); await Promise.all([loadPanelInfo(), loadNodeUsage(), loadDevices()]);
@@ -697,7 +697,7 @@ export default function AdminUserDetail() {
if (!userId) return; if (!userId) return;
setActionLoading(true); setActionLoading(true);
try { try {
await adminUsersApi.deleteUserDevice(userId, hwid); await adminUsersApi.deleteUserDevice(userId, hwid, activeSubscriptionId ?? undefined);
notify.success(t('admin.users.detail.devices.deleted')); notify.success(t('admin.users.detail.devices.deleted'));
await loadDevices(); await loadDevices();
} catch { } catch {
@@ -711,7 +711,7 @@ export default function AdminUserDetail() {
if (!userId) return; if (!userId) return;
setActionLoading(true); setActionLoading(true);
try { try {
await adminUsersApi.resetUserDevices(userId); await adminUsersApi.resetUserDevices(userId, activeSubscriptionId ?? undefined);
notify.success(t('admin.users.detail.devices.allDeleted')); notify.success(t('admin.users.detail.devices.allDeleted'));
await loadDevices(); await loadDevices();
} catch { } catch {
@@ -1681,7 +1681,20 @@ export default function AdminUserDetail() {
className="input" className="input"
> >
<option value="">{t('admin.users.detail.subscription.selectTariff')}</option> <option value="">{t('admin.users.detail.subscription.selectTariff')}</option>
{tariffs.map((tariffItem) => ( {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}> <option key={tariffItem.id} value={tariffItem.id}>
{tariffItem.name} {tariffItem.name}
</option> </option>