From aa989a6ade0a25a0dc47ed1bb6e9a8fc542700df Mon Sep 17 00:00:00 2001 From: Fringg Date: Sat, 28 Mar 2026 19:50:10 +0300 Subject: [PATCH] feat: add subscription selector to admin sync tab for multi-tariff - Add subscription dropdown to sync tab when user has 2+ subscriptions - Pass subscription_id to getSyncStatus, syncFromPanel, syncToPanel - loadSyncStatus re-fetches when selected subscription changes - Show tariff name in UUID info section from API response - Add selectSubscription i18n keys to all 4 locales --- src/api/adminUsers.ts | 22 +++++++++++++--- src/locales/en.json | 1 + src/locales/fa.json | 1 + src/locales/ru.json | 1 + src/locales/zh.json | 1 + src/pages/AdminUserDetail.tsx | 47 +++++++++++++++++++++++++++++------ 6 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/api/adminUsers.ts b/src/api/adminUsers.ts index 67ab7d7..6a7735f 100644 --- a/src/api/adminUsers.ts +++ b/src/api/adminUsers.ts @@ -251,6 +251,8 @@ export interface PanelSyncStatusResponse { user_id: number; telegram_id: number; remnawave_uuid: string | null; + subscription_id: number | null; + subscription_tariff_name: string | null; last_sync: string | null; bot_subscription_status: string | null; bot_subscription_end_date: string | null; @@ -561,8 +563,12 @@ export const adminUsersApi = { }, // Sync status - getSyncStatus: async (userId: number): Promise => { - const response = await apiClient.get(`/cabinet/admin/users/${userId}/sync/status`); + getSyncStatus: async ( + userId: number, + subscriptionId?: number, + ): Promise => { + const params = subscriptionId != null ? { subscription_id: subscriptionId } : undefined; + const response = await apiClient.get(`/cabinet/admin/users/${userId}/sync/status`, { params }); return response.data; }, @@ -570,8 +576,12 @@ export const adminUsersApi = { syncFromPanel: async ( userId: number, data: SyncFromPanelRequest = {}, + subscriptionId?: number, ): Promise => { - const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/from-panel`, data); + const params = subscriptionId != null ? { subscription_id: subscriptionId } : undefined; + const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/from-panel`, data, { + params, + }); return response.data; }, @@ -579,8 +589,12 @@ export const adminUsersApi = { syncToPanel: async ( userId: number, data: SyncToPanelRequest = {}, + subscriptionId?: number, ): Promise => { - const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/to-panel`, data); + const params = subscriptionId != null ? { subscription_id: subscriptionId } : undefined; + const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/to-panel`, data, { + params, + }); return response.data; }, diff --git a/src/locales/en.json b/src/locales/en.json index 0107dd3..641de93 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -2950,6 +2950,7 @@ "recentTransactions": "Recent transactions" }, "sync": { + "selectSubscription": "Select subscription", "hasDifferences": "Differences found", "synced": "Synchronized", "bot": "Bot", diff --git a/src/locales/fa.json b/src/locales/fa.json index 3ad3855..029c69d 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -2578,6 +2578,7 @@ "recentTransactions": "تراکنش‌های اخیر" }, "sync": { + "selectSubscription": "انتخاب اشتراک", "hasDifferences": "اختلاف وجود دارد", "synced": "همگام‌سازی شده", "bot": "ربات", diff --git a/src/locales/ru.json b/src/locales/ru.json index dbe3b7c..2f2edc2 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -3471,6 +3471,7 @@ "recentTransactions": "Последние транзакции" }, "sync": { + "selectSubscription": "Выберите подписку", "hasDifferences": "Есть расхождения", "synced": "Синхронизировано", "bot": "Бот", diff --git a/src/locales/zh.json b/src/locales/zh.json index 158f3da..001db8d 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -2577,6 +2577,7 @@ "recentTransactions": "最近交易" }, "sync": { + "selectSubscription": "选择订阅", "hasDifferences": "存在差异", "synced": "已同步", "bot": "机器人", diff --git a/src/pages/AdminUserDetail.tsx b/src/pages/AdminUserDetail.tsx index 12bbab9..0202b51 100644 --- a/src/pages/AdminUserDetail.tsx +++ b/src/pages/AdminUserDetail.tsx @@ -386,12 +386,12 @@ export default function AdminUserDetail() { const loadSyncStatus = useCallback(async () => { if (!userId) return; try { - const data = await adminUsersApi.getSyncStatus(userId); + const data = await adminUsersApi.getSyncStatus(userId, activeSubscriptionId ?? undefined); setSyncStatus(data); } catch (error) { console.error('Failed to load sync status:', error); } - }, [userId]); + }, [userId, activeSubscriptionId]); const loadTariffs = useCallback(async () => { if (!userId) return; @@ -656,10 +656,14 @@ export default function AdminUserDetail() { if (!userId) return; setActionLoading(true); try { - await adminUsersApi.syncFromPanel(userId, { - update_subscription: true, - update_traffic: true, - }); + await adminUsersApi.syncFromPanel( + userId, + { + update_subscription: true, + update_traffic: true, + }, + activeSubscriptionId ?? undefined, + ); await loadUser(); await loadSyncStatus(); } catch (error) { @@ -673,7 +677,11 @@ export default function AdminUserDetail() { if (!userId) return; setActionLoading(true); try { - await adminUsersApi.syncToPanel(userId, { create_if_missing: true }); + await adminUsersApi.syncToPanel( + userId, + { create_if_missing: true }, + activeSubscriptionId ?? undefined, + ); await loadUser(); await loadSyncStatus(); } catch (error) { @@ -2309,6 +2317,26 @@ export default function AdminUserDetail() { {/* Sync Tab */} {activeTab === 'sync' && (
+ {/* Subscription selector for multi-tariff */} + {userSubscriptions.length > 1 && ( +
+
+ {t('admin.users.detail.sync.selectSubscription')} +
+ +
+ )} + {/* Sync status */} {syncStatus && (
+ {syncStatus?.subscription_tariff_name && ( +
+ {syncStatus.subscription_tariff_name} +
+ )}
Remnawave UUID
{syncStatus?.remnawave_uuid ||