mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
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
This commit is contained in:
@@ -251,6 +251,8 @@ export interface PanelSyncStatusResponse {
|
|||||||
user_id: number;
|
user_id: number;
|
||||||
telegram_id: number;
|
telegram_id: number;
|
||||||
remnawave_uuid: string | null;
|
remnawave_uuid: string | null;
|
||||||
|
subscription_id: number | null;
|
||||||
|
subscription_tariff_name: string | null;
|
||||||
last_sync: string | null;
|
last_sync: string | null;
|
||||||
bot_subscription_status: string | null;
|
bot_subscription_status: string | null;
|
||||||
bot_subscription_end_date: string | null;
|
bot_subscription_end_date: string | null;
|
||||||
@@ -561,8 +563,12 @@ export const adminUsersApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Sync status
|
// Sync status
|
||||||
getSyncStatus: async (userId: number): Promise<PanelSyncStatusResponse> => {
|
getSyncStatus: async (
|
||||||
const response = await apiClient.get(`/cabinet/admin/users/${userId}/sync/status`);
|
userId: number,
|
||||||
|
subscriptionId?: number,
|
||||||
|
): Promise<PanelSyncStatusResponse> => {
|
||||||
|
const params = subscriptionId != null ? { subscription_id: subscriptionId } : undefined;
|
||||||
|
const response = await apiClient.get(`/cabinet/admin/users/${userId}/sync/status`, { params });
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -570,8 +576,12 @@ export const adminUsersApi = {
|
|||||||
syncFromPanel: async (
|
syncFromPanel: async (
|
||||||
userId: number,
|
userId: number,
|
||||||
data: SyncFromPanelRequest = {},
|
data: SyncFromPanelRequest = {},
|
||||||
|
subscriptionId?: number,
|
||||||
): Promise<SyncFromPanelResponse> => {
|
): Promise<SyncFromPanelResponse> => {
|
||||||
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;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -579,8 +589,12 @@ export const adminUsersApi = {
|
|||||||
syncToPanel: async (
|
syncToPanel: async (
|
||||||
userId: number,
|
userId: number,
|
||||||
data: SyncToPanelRequest = {},
|
data: SyncToPanelRequest = {},
|
||||||
|
subscriptionId?: number,
|
||||||
): Promise<SyncToPanelResponse> => {
|
): Promise<SyncToPanelResponse> => {
|
||||||
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;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -2950,6 +2950,7 @@
|
|||||||
"recentTransactions": "Recent transactions"
|
"recentTransactions": "Recent transactions"
|
||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
|
"selectSubscription": "Select subscription",
|
||||||
"hasDifferences": "Differences found",
|
"hasDifferences": "Differences found",
|
||||||
"synced": "Synchronized",
|
"synced": "Synchronized",
|
||||||
"bot": "Bot",
|
"bot": "Bot",
|
||||||
|
|||||||
@@ -2578,6 +2578,7 @@
|
|||||||
"recentTransactions": "تراکنشهای اخیر"
|
"recentTransactions": "تراکنشهای اخیر"
|
||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
|
"selectSubscription": "انتخاب اشتراک",
|
||||||
"hasDifferences": "اختلاف وجود دارد",
|
"hasDifferences": "اختلاف وجود دارد",
|
||||||
"synced": "همگامسازی شده",
|
"synced": "همگامسازی شده",
|
||||||
"bot": "ربات",
|
"bot": "ربات",
|
||||||
|
|||||||
@@ -3471,6 +3471,7 @@
|
|||||||
"recentTransactions": "Последние транзакции"
|
"recentTransactions": "Последние транзакции"
|
||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
|
"selectSubscription": "Выберите подписку",
|
||||||
"hasDifferences": "Есть расхождения",
|
"hasDifferences": "Есть расхождения",
|
||||||
"synced": "Синхронизировано",
|
"synced": "Синхронизировано",
|
||||||
"bot": "Бот",
|
"bot": "Бот",
|
||||||
|
|||||||
@@ -2577,6 +2577,7 @@
|
|||||||
"recentTransactions": "最近交易"
|
"recentTransactions": "最近交易"
|
||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
|
"selectSubscription": "选择订阅",
|
||||||
"hasDifferences": "存在差异",
|
"hasDifferences": "存在差异",
|
||||||
"synced": "已同步",
|
"synced": "已同步",
|
||||||
"bot": "机器人",
|
"bot": "机器人",
|
||||||
|
|||||||
@@ -386,12 +386,12 @@ export default function AdminUserDetail() {
|
|||||||
const loadSyncStatus = useCallback(async () => {
|
const loadSyncStatus = useCallback(async () => {
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
try {
|
try {
|
||||||
const data = await adminUsersApi.getSyncStatus(userId);
|
const data = await adminUsersApi.getSyncStatus(userId, activeSubscriptionId ?? undefined);
|
||||||
setSyncStatus(data);
|
setSyncStatus(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load sync status:', error);
|
console.error('Failed to load sync status:', error);
|
||||||
}
|
}
|
||||||
}, [userId]);
|
}, [userId, activeSubscriptionId]);
|
||||||
|
|
||||||
const loadTariffs = useCallback(async () => {
|
const loadTariffs = useCallback(async () => {
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
@@ -656,10 +656,14 @@ export default function AdminUserDetail() {
|
|||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
setActionLoading(true);
|
setActionLoading(true);
|
||||||
try {
|
try {
|
||||||
await adminUsersApi.syncFromPanel(userId, {
|
await adminUsersApi.syncFromPanel(
|
||||||
update_subscription: true,
|
userId,
|
||||||
update_traffic: true,
|
{
|
||||||
});
|
update_subscription: true,
|
||||||
|
update_traffic: true,
|
||||||
|
},
|
||||||
|
activeSubscriptionId ?? undefined,
|
||||||
|
);
|
||||||
await loadUser();
|
await loadUser();
|
||||||
await loadSyncStatus();
|
await loadSyncStatus();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -673,7 +677,11 @@ export default function AdminUserDetail() {
|
|||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
setActionLoading(true);
|
setActionLoading(true);
|
||||||
try {
|
try {
|
||||||
await adminUsersApi.syncToPanel(userId, { create_if_missing: true });
|
await adminUsersApi.syncToPanel(
|
||||||
|
userId,
|
||||||
|
{ create_if_missing: true },
|
||||||
|
activeSubscriptionId ?? undefined,
|
||||||
|
);
|
||||||
await loadUser();
|
await loadUser();
|
||||||
await loadSyncStatus();
|
await loadSyncStatus();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -2309,6 +2317,26 @@ export default function AdminUserDetail() {
|
|||||||
{/* Sync Tab */}
|
{/* Sync Tab */}
|
||||||
{activeTab === 'sync' && (
|
{activeTab === 'sync' && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
{/* Subscription selector for multi-tariff */}
|
||||||
|
{userSubscriptions.length > 1 && (
|
||||||
|
<div className="rounded-xl bg-dark-800/50 p-4">
|
||||||
|
<div className="mb-2 text-sm text-dark-400">
|
||||||
|
{t('admin.users.detail.sync.selectSubscription')}
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
value={activeSubscriptionId || ''}
|
||||||
|
onChange={(e) => setActiveSubscriptionId(Number(e.target.value) || null)}
|
||||||
|
className="w-full rounded-lg border border-dark-600 bg-dark-700 px-3 py-2 text-sm text-dark-100"
|
||||||
|
>
|
||||||
|
{userSubscriptions.map((sub) => (
|
||||||
|
<option key={sub.id} value={sub.id}>
|
||||||
|
{sub.tariff_name || `#${sub.id}`} — {sub.status}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Sync status */}
|
{/* Sync status */}
|
||||||
{syncStatus && (
|
{syncStatus && (
|
||||||
<div
|
<div
|
||||||
@@ -2431,6 +2459,11 @@ export default function AdminUserDetail() {
|
|||||||
|
|
||||||
{/* UUID info */}
|
{/* UUID info */}
|
||||||
<div className="rounded-xl bg-dark-800/50 p-4">
|
<div className="rounded-xl bg-dark-800/50 p-4">
|
||||||
|
{syncStatus?.subscription_tariff_name && (
|
||||||
|
<div className="mb-2 text-xs text-dark-500">
|
||||||
|
{syncStatus.subscription_tariff_name}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="mb-1 text-sm text-dark-400">Remnawave UUID</div>
|
<div className="mb-1 text-sm text-dark-400">Remnawave UUID</div>
|
||||||
<div className="break-all font-mono text-sm text-dark-100">
|
<div className="break-all font-mono text-sm text-dark-100">
|
||||||
{syncStatus?.remnawave_uuid ||
|
{syncStatus?.remnawave_uuid ||
|
||||||
|
|||||||
Reference in New Issue
Block a user