mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
perf(admin-user-detail): migrate remaining 9 leaf loaders to React Query
Drops manual activeTab/userId loader triggers — query enabled gating now handles all per-tab fetching. Wrappers around refetch keep mutation handler call sites unchanged. Removes 4 dead wrappers (loadTariffs/Referrals/Gifts/PromoGroups) only called by the now-deleted activeTab useEffect.
This commit is contained in:
@@ -408,39 +408,129 @@ export default function AdminUserDetail() {
|
|||||||
[userQuery.refetch],
|
[userQuery.refetch],
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadSyncStatus = useCallback(async () => {
|
// ---- React Query hooks for the rest of the leaf loaders -----------------
|
||||||
if (!userId) return;
|
// Each loader callback below becomes a thin refetch wrapper so existing call
|
||||||
try {
|
// sites (mutation handlers, useEffects) continue to work unchanged. queryKey
|
||||||
const data = await adminUsersApi.getSyncStatus(userId, activeSubscriptionId ?? undefined);
|
// includes the inputs that drive the request (userId / activeSubscriptionId)
|
||||||
setSyncStatus(data);
|
// so changing them auto-invalidates.
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load sync status:', error);
|
|
||||||
}
|
|
||||||
}, [userId, activeSubscriptionId]);
|
|
||||||
|
|
||||||
const loadTariffs = useCallback(async () => {
|
const syncStatusQuery = useQuery({
|
||||||
if (!userId) return;
|
queryKey: ['admin-user-sync-status', userId, activeSubscriptionId] as const,
|
||||||
try {
|
queryFn: () => adminUsersApi.getSyncStatus(userId as number, activeSubscriptionId ?? undefined),
|
||||||
const data = await adminUsersApi.getAvailableTariffs(userId, true);
|
enabled: !!userId && !isNaN(userId) && activeTab === 'sync' && hasPermission('users:sync'),
|
||||||
setTariffs(data.tariffs);
|
});
|
||||||
} catch (error) {
|
const tariffsQuery = useQuery({
|
||||||
console.error('Failed to load tariffs:', error);
|
queryKey: ['admin-user-tariffs', userId] as const,
|
||||||
}
|
queryFn: () => adminUsersApi.getAvailableTariffs(userId as number, true),
|
||||||
}, [userId]);
|
enabled: !!userId && !isNaN(userId) && activeTab === 'subscription',
|
||||||
|
});
|
||||||
|
const ticketsQuery = useQuery({
|
||||||
|
queryKey: ['admin-user-tickets', userId] as const,
|
||||||
|
queryFn: () => adminApi.getTickets({ user_id: userId as number, per_page: 50 }),
|
||||||
|
enabled: !!userId && !isNaN(userId) && activeTab === 'tickets',
|
||||||
|
});
|
||||||
|
const referralsQuery = useQuery({
|
||||||
|
queryKey: ['admin-user-referrals', userId] as const,
|
||||||
|
queryFn: () => adminUsersApi.getReferrals(userId as number, 0, 50),
|
||||||
|
enabled: !!userId && !isNaN(userId) && activeTab === 'info',
|
||||||
|
});
|
||||||
|
const referralsListQuery = useQuery({
|
||||||
|
queryKey: ['admin-user-referrals-list', userId] as const,
|
||||||
|
queryFn: () => adminUsersApi.getReferrals(userId as number, 0, 100),
|
||||||
|
enabled: !!userId && !isNaN(userId) && activeTab === 'referrals',
|
||||||
|
});
|
||||||
|
const panelInfoQuery = useQuery({
|
||||||
|
queryKey: ['admin-user-panel-info', userId, activeSubscriptionId] as const,
|
||||||
|
queryFn: () => adminUsersApi.getPanelInfo(userId as number, activeSubscriptionId ?? undefined),
|
||||||
|
enabled: !!userId && !isNaN(userId),
|
||||||
|
});
|
||||||
|
const nodeUsageQuery = useQuery({
|
||||||
|
queryKey: ['admin-user-node-usage', userId, activeSubscriptionId] as const,
|
||||||
|
queryFn: () => adminUsersApi.getNodeUsage(userId as number, activeSubscriptionId ?? undefined),
|
||||||
|
enabled: !!userId && !isNaN(userId) && activeTab === 'subscription',
|
||||||
|
});
|
||||||
|
const devicesQuery = useQuery({
|
||||||
|
queryKey: ['admin-user-devices', userId, activeSubscriptionId] as const,
|
||||||
|
queryFn: () =>
|
||||||
|
adminUsersApi.getUserDevices(userId as number, activeSubscriptionId ?? undefined),
|
||||||
|
enabled: !!userId && !isNaN(userId) && activeTab === 'subscription',
|
||||||
|
});
|
||||||
|
const giftsQuery = useQuery({
|
||||||
|
queryKey: ['admin-user-gifts', userId] as const,
|
||||||
|
queryFn: () => adminUsersApi.getUserGifts(userId as number),
|
||||||
|
enabled: !!userId && !isNaN(userId) && activeTab === 'gifts',
|
||||||
|
});
|
||||||
|
const promoGroupsQuery = useQuery({
|
||||||
|
queryKey: ['admin-promo-groups-all'] as const,
|
||||||
|
queryFn: () => promocodesApi.getPromoGroups({ limit: 100 }),
|
||||||
|
enabled: activeTab === 'info' && hasPermission('users:promo_group'),
|
||||||
|
});
|
||||||
|
|
||||||
const loadTickets = useCallback(async () => {
|
// --- Sync each query's data + isFetching into existing state vars --------
|
||||||
if (!userId) return;
|
useEffect(() => {
|
||||||
try {
|
if (syncStatusQuery.data) setSyncStatus(syncStatusQuery.data);
|
||||||
setTicketsLoading(true);
|
}, [syncStatusQuery.data]);
|
||||||
const data = await adminApi.getTickets({ user_id: userId, per_page: 50 });
|
useEffect(() => {
|
||||||
setTickets(data.items);
|
if (tariffsQuery.data) setTariffs(tariffsQuery.data.tariffs);
|
||||||
setTicketsTotal(data.total);
|
}, [tariffsQuery.data]);
|
||||||
} catch (error) {
|
useEffect(() => {
|
||||||
console.error('Failed to load tickets:', error);
|
if (ticketsQuery.data) {
|
||||||
} finally {
|
setTickets(ticketsQuery.data.items);
|
||||||
setTicketsLoading(false);
|
setTicketsTotal(ticketsQuery.data.total);
|
||||||
}
|
}
|
||||||
}, [userId]);
|
setTicketsLoading(ticketsQuery.isFetching);
|
||||||
|
}, [ticketsQuery.data, ticketsQuery.isFetching]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (referralsQuery.data) setReferrals(referralsQuery.data.users || []);
|
||||||
|
setReferralsLoading(referralsQuery.isFetching);
|
||||||
|
}, [referralsQuery.data, referralsQuery.isFetching]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (referralsListQuery.data) {
|
||||||
|
setReferralsList(referralsListQuery.data.users || []);
|
||||||
|
setReferralsTotal(referralsListQuery.data.total || 0);
|
||||||
|
}
|
||||||
|
setReferralsListLoading(referralsListQuery.isFetching);
|
||||||
|
}, [referralsListQuery.data, referralsListQuery.isFetching]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (panelInfoQuery.data) setPanelInfo(panelInfoQuery.data);
|
||||||
|
setPanelInfoLoading(panelInfoQuery.isFetching);
|
||||||
|
}, [panelInfoQuery.data, panelInfoQuery.isFetching]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (nodeUsageQuery.data) setNodeUsage(nodeUsageQuery.data);
|
||||||
|
}, [nodeUsageQuery.data]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (devicesQuery.data) {
|
||||||
|
setDevices(devicesQuery.data.devices);
|
||||||
|
setDevicesTotal(devicesQuery.data.total);
|
||||||
|
setDeviceLimit(devicesQuery.data.device_limit);
|
||||||
|
}
|
||||||
|
setDevicesLoading(devicesQuery.isFetching);
|
||||||
|
}, [devicesQuery.data, devicesQuery.isFetching]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (giftsQuery.data) setGiftsData(giftsQuery.data);
|
||||||
|
setGiftsLoading(giftsQuery.isFetching);
|
||||||
|
}, [giftsQuery.data, giftsQuery.isFetching]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (promoGroupsQuery.data) setPromoGroups(promoGroupsQuery.data.items);
|
||||||
|
}, [promoGroupsQuery.data]);
|
||||||
|
|
||||||
|
// --- Loader callbacks: thin refetch wrappers (signatures unchanged) ------
|
||||||
|
|
||||||
|
const loadSyncStatus = useCallback(
|
||||||
|
async () => {
|
||||||
|
await syncStatusQuery.refetch();
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[syncStatusQuery.refetch],
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadTickets = useCallback(
|
||||||
|
async () => {
|
||||||
|
await ticketsQuery.refetch();
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[ticketsQuery.refetch],
|
||||||
|
);
|
||||||
|
|
||||||
const loadTicketDetail = useCallback(async (ticketId: number) => {
|
const loadTicketDetail = useCallback(async (ticketId: number) => {
|
||||||
try {
|
try {
|
||||||
@@ -454,43 +544,21 @@ export default function AdminUserDetail() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadReferrals = useCallback(async () => {
|
const loadReferralsList = useCallback(
|
||||||
if (!userId) return;
|
async () => {
|
||||||
try {
|
await referralsListQuery.refetch();
|
||||||
setReferralsLoading(true);
|
},
|
||||||
const data = await adminUsersApi.getReferrals(userId, 0, 50);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
setReferrals(data.users || []);
|
[referralsListQuery.refetch],
|
||||||
} catch {
|
);
|
||||||
} finally {
|
|
||||||
setReferralsLoading(false);
|
|
||||||
}
|
|
||||||
}, [userId]);
|
|
||||||
|
|
||||||
const loadReferralsList = useCallback(async () => {
|
const loadPanelInfo = useCallback(
|
||||||
if (!userId) return;
|
async () => {
|
||||||
setReferralsListLoading(true);
|
await panelInfoQuery.refetch();
|
||||||
try {
|
},
|
||||||
const data = await adminUsersApi.getReferrals(userId, 0, 100);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
setReferralsList(data.users || []);
|
[panelInfoQuery.refetch],
|
||||||
setReferralsTotal(data.total || 0);
|
);
|
||||||
} catch {
|
|
||||||
// silent
|
|
||||||
} finally {
|
|
||||||
setReferralsListLoading(false);
|
|
||||||
}
|
|
||||||
}, [userId]);
|
|
||||||
|
|
||||||
const loadPanelInfo = useCallback(async () => {
|
|
||||||
if (!userId) return;
|
|
||||||
try {
|
|
||||||
setPanelInfoLoading(true);
|
|
||||||
const data = await adminUsersApi.getPanelInfo(userId, activeSubscriptionId ?? undefined);
|
|
||||||
setPanelInfo(data);
|
|
||||||
} catch {
|
|
||||||
} finally {
|
|
||||||
setPanelInfoLoading(false);
|
|
||||||
}
|
|
||||||
}, [userId, activeSubscriptionId]);
|
|
||||||
|
|
||||||
const loadRequestHistory = useCallback(
|
const loadRequestHistory = useCallback(
|
||||||
async (offset = 0, append = false) => {
|
async (offset = 0, append = false) => {
|
||||||
@@ -515,51 +583,26 @@ export default function AdminUserDetail() {
|
|||||||
[userId, requestHistorySubId],
|
[userId, requestHistorySubId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadNodeUsage = useCallback(async () => {
|
const loadNodeUsage = useCallback(
|
||||||
if (!userId) return;
|
async () => {
|
||||||
try {
|
await nodeUsageQuery.refetch();
|
||||||
const data = await adminUsersApi.getNodeUsage(userId, activeSubscriptionId ?? undefined);
|
},
|
||||||
setNodeUsage(data);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
} catch {}
|
[nodeUsageQuery.refetch],
|
||||||
}, [userId, activeSubscriptionId]);
|
);
|
||||||
|
|
||||||
const loadDevices = useCallback(async () => {
|
const loadDevices = useCallback(
|
||||||
if (!userId) return;
|
async () => {
|
||||||
try {
|
await devicesQuery.refetch();
|
||||||
setDevicesLoading(true);
|
},
|
||||||
const data = await adminUsersApi.getUserDevices(userId, activeSubscriptionId ?? undefined);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
setDevices(data.devices);
|
[devicesQuery.refetch],
|
||||||
setDevicesTotal(data.total);
|
);
|
||||||
setDeviceLimit(data.device_limit);
|
|
||||||
} catch {
|
|
||||||
} finally {
|
|
||||||
setDevicesLoading(false);
|
|
||||||
}
|
|
||||||
}, [userId, activeSubscriptionId]);
|
|
||||||
|
|
||||||
const loadSubscriptionData = useCallback(async () => {
|
const loadSubscriptionData = useCallback(async () => {
|
||||||
await Promise.all([loadPanelInfo(), loadNodeUsage(), loadDevices()]);
|
await Promise.all([loadPanelInfo(), loadNodeUsage(), loadDevices()]);
|
||||||
}, [loadPanelInfo, loadNodeUsage, loadDevices]);
|
}, [loadPanelInfo, loadNodeUsage, loadDevices]);
|
||||||
|
|
||||||
const loadGifts = useCallback(async () => {
|
|
||||||
if (!userId) return;
|
|
||||||
try {
|
|
||||||
setGiftsLoading(true);
|
|
||||||
const data = await adminUsersApi.getUserGifts(userId);
|
|
||||||
setGiftsData(data);
|
|
||||||
} catch {
|
|
||||||
} finally {
|
|
||||||
setGiftsLoading(false);
|
|
||||||
}
|
|
||||||
}, [userId]);
|
|
||||||
|
|
||||||
const loadPromoGroups = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const data = await promocodesApi.getPromoGroups({ limit: 100 });
|
|
||||||
setPromoGroups(data.items);
|
|
||||||
} catch {}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleTicketReply = async () => {
|
const handleTicketReply = async () => {
|
||||||
if (!selectedTicketId || !replyText.trim()) return;
|
if (!selectedTicketId || !replyText.trim()) return;
|
||||||
setReplySending(true);
|
setReplySending(true);
|
||||||
@@ -608,12 +651,6 @@ export default function AdminUserDetail() {
|
|||||||
// user data is auto-loaded by userQuery (enabled when userId is valid)
|
// user data is auto-loaded by userQuery (enabled when userId is valid)
|
||||||
}, [userId, navigate]);
|
}, [userId, navigate]);
|
||||||
|
|
||||||
// Load panel info when subscription changes (separate from mount to avoid redundant loadUser)
|
|
||||||
useEffect(() => {
|
|
||||||
if (!userId || isNaN(userId)) return;
|
|
||||||
loadPanelInfo();
|
|
||||||
}, [userId, loadPanelInfo]);
|
|
||||||
|
|
||||||
// Reload request history when the request-history subscription selector changes
|
// Reload request history when the request-history subscription selector changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!requestHistoryExpanded || requestHistorySubId === null) return;
|
if (!requestHistoryExpanded || requestHistorySubId === null) return;
|
||||||
@@ -623,31 +660,8 @@ export default function AdminUserDetail() {
|
|||||||
loadRequestHistory(0);
|
loadRequestHistory(0);
|
||||||
}, [requestHistorySubId]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [requestHistorySubId]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
useEffect(() => {
|
// All other per-tab data fetching is driven by useQuery `enabled` gating
|
||||||
if (activeTab === 'info') {
|
// wired to userId / activeSubscriptionId / activeTab — no manual triggers needed.
|
||||||
loadReferrals();
|
|
||||||
if (hasPermission('users:promo_group')) loadPromoGroups();
|
|
||||||
}
|
|
||||||
if (activeTab === 'sync' && hasPermission('users:sync')) loadSyncStatus();
|
|
||||||
if (activeTab === 'subscription') {
|
|
||||||
loadTariffs();
|
|
||||||
loadSubscriptionData();
|
|
||||||
}
|
|
||||||
if (activeTab === 'tickets') loadTickets();
|
|
||||||
if (activeTab === 'gifts') loadGifts();
|
|
||||||
if (activeTab === 'referrals') loadReferralsList();
|
|
||||||
}, [
|
|
||||||
activeTab,
|
|
||||||
loadSyncStatus,
|
|
||||||
loadTariffs,
|
|
||||||
loadTickets,
|
|
||||||
loadReferrals,
|
|
||||||
loadSubscriptionData,
|
|
||||||
loadPromoGroups,
|
|
||||||
loadGifts,
|
|
||||||
loadReferralsList,
|
|
||||||
hasPermission,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleUpdateBalance = async (isAdd: boolean) => {
|
const handleUpdateBalance = async (isAdd: boolean) => {
|
||||||
if (balanceAmount === '' || !userId) return;
|
if (balanceAmount === '' || !userId) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user