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:
c0mrade
2026-05-26 13:19:28 +03:00
parent 5ee97e0a9f
commit 6f7bd10d61

View File

@@ -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;