mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +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;
|
||||
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<PanelSyncStatusResponse> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/users/${userId}/sync/status`);
|
||||
getSyncStatus: async (
|
||||
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;
|
||||
},
|
||||
|
||||
@@ -570,8 +576,12 @@ export const adminUsersApi = {
|
||||
syncFromPanel: async (
|
||||
userId: number,
|
||||
data: SyncFromPanelRequest = {},
|
||||
subscriptionId?: number,
|
||||
): 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;
|
||||
},
|
||||
|
||||
@@ -579,8 +589,12 @@ export const adminUsersApi = {
|
||||
syncToPanel: async (
|
||||
userId: number,
|
||||
data: SyncToPanelRequest = {},
|
||||
subscriptionId?: number,
|
||||
): 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;
|
||||
},
|
||||
|
||||
|
||||
@@ -2950,6 +2950,7 @@
|
||||
"recentTransactions": "Recent transactions"
|
||||
},
|
||||
"sync": {
|
||||
"selectSubscription": "Select subscription",
|
||||
"hasDifferences": "Differences found",
|
||||
"synced": "Synchronized",
|
||||
"bot": "Bot",
|
||||
|
||||
@@ -2578,6 +2578,7 @@
|
||||
"recentTransactions": "تراکنشهای اخیر"
|
||||
},
|
||||
"sync": {
|
||||
"selectSubscription": "انتخاب اشتراک",
|
||||
"hasDifferences": "اختلاف وجود دارد",
|
||||
"synced": "همگامسازی شده",
|
||||
"bot": "ربات",
|
||||
|
||||
@@ -3471,6 +3471,7 @@
|
||||
"recentTransactions": "Последние транзакции"
|
||||
},
|
||||
"sync": {
|
||||
"selectSubscription": "Выберите подписку",
|
||||
"hasDifferences": "Есть расхождения",
|
||||
"synced": "Синхронизировано",
|
||||
"bot": "Бот",
|
||||
|
||||
@@ -2577,6 +2577,7 @@
|
||||
"recentTransactions": "最近交易"
|
||||
},
|
||||
"sync": {
|
||||
"selectSubscription": "选择订阅",
|
||||
"hasDifferences": "存在差异",
|
||||
"synced": "已同步",
|
||||
"bot": "机器人",
|
||||
|
||||
@@ -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' && (
|
||||
<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 */}
|
||||
{syncStatus && (
|
||||
<div
|
||||
@@ -2431,6 +2459,11 @@ export default function AdminUserDetail() {
|
||||
|
||||
{/* UUID info */}
|
||||
<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="break-all font-mono text-sm text-dark-100">
|
||||
{syncStatus?.remnawave_uuid ||
|
||||
|
||||
Reference in New Issue
Block a user