mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
perf(admin): migrate AdminBanSystem tab fetching to React Query
All 10 tab data fetches + the initial status load move from manual useState + useEffect + loadTabData(switch) pattern to per-tab useQuery hooks with enabled gating. Each query syncs into the existing state vars via useEffect so the JSX and mutation handlers stay unchanged; handleSearch/handleUnban/handleToggle/handleSet now call query.refetch() directly; refresh button uses refetchActiveTab helper. Net effect: tab switches return to cached data instantly (background revalidate), no duplicate fetches, no manual loading/error wiring. The reports query has reportHours in its queryKey so changing the period auto-refetches without the extra useEffect that previously handled it.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { AdminBackButton } from '../components/admin/AdminBackButton';
|
||||
import { useFocusTrap } from '../hooks/useFocusTrap';
|
||||
import {
|
||||
@@ -211,8 +212,6 @@ export default function AdminBanSystem() {
|
||||
const [report, setReport] = useState<BanReportResponse | null>(null);
|
||||
const [health, setHealth] = useState<BanHealthResponse | null>(null);
|
||||
const [reportHours, setReportHours] = useState(24);
|
||||
const reportHoursRef = useRef(reportHours);
|
||||
reportHoursRef.current = reportHours;
|
||||
const [settingLoading, setSettingLoading] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -247,101 +246,143 @@ export default function AdminBanSystem() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [actionLoading, setActionLoading] = useState<string | null>(null);
|
||||
|
||||
const loadStatus = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await banSystemApi.getStatus();
|
||||
setStatus(data);
|
||||
if (!data.enabled || !data.configured) {
|
||||
// React Query: status once at mount; each tab fetches lazily via `enabled`.
|
||||
// Caching means switching tabs returns to cached data instantly (with background revalidate).
|
||||
const statusQuery = useQuery({
|
||||
queryKey: ['ban-status'] as const,
|
||||
queryFn: () => banSystemApi.getStatus(),
|
||||
});
|
||||
const isReady = !!(status?.enabled && status?.configured);
|
||||
|
||||
const dashboardQuery = useQuery({
|
||||
queryKey: ['ban-stats'] as const,
|
||||
queryFn: () => banSystemApi.getStats(),
|
||||
enabled: isReady && activeTab === 'dashboard',
|
||||
});
|
||||
const usersQuery = useQuery({
|
||||
queryKey: ['ban-users'] as const,
|
||||
queryFn: () => banSystemApi.getUsers({ limit: 50 }),
|
||||
enabled: isReady && activeTab === 'users',
|
||||
});
|
||||
const punishmentsQuery = useQuery({
|
||||
queryKey: ['ban-punishments'] as const,
|
||||
queryFn: () => banSystemApi.getPunishments(),
|
||||
enabled: isReady && activeTab === 'punishments',
|
||||
});
|
||||
const nodesQuery = useQuery({
|
||||
queryKey: ['ban-nodes'] as const,
|
||||
queryFn: () => banSystemApi.getNodes(),
|
||||
enabled: isReady && activeTab === 'nodes',
|
||||
});
|
||||
const agentsQuery = useQuery({
|
||||
queryKey: ['ban-agents'] as const,
|
||||
queryFn: () => banSystemApi.getAgents(),
|
||||
enabled: isReady && activeTab === 'agents',
|
||||
});
|
||||
const violationsQuery = useQuery({
|
||||
queryKey: ['ban-violations'] as const,
|
||||
queryFn: () => banSystemApi.getTrafficViolations(),
|
||||
enabled: isReady && activeTab === 'violations',
|
||||
});
|
||||
const settingsQuery = useQuery({
|
||||
queryKey: ['ban-settings'] as const,
|
||||
queryFn: () => banSystemApi.getSettings(),
|
||||
enabled: isReady && activeTab === 'settings',
|
||||
});
|
||||
const trafficQuery = useQuery({
|
||||
queryKey: ['ban-traffic'] as const,
|
||||
queryFn: () => banSystemApi.getTraffic(),
|
||||
enabled: isReady && activeTab === 'traffic',
|
||||
});
|
||||
const reportsQuery = useQuery({
|
||||
queryKey: ['ban-report', reportHours] as const,
|
||||
queryFn: () => banSystemApi.getReport(reportHours),
|
||||
enabled: isReady && activeTab === 'reports',
|
||||
});
|
||||
const healthQuery = useQuery({
|
||||
queryKey: ['ban-health'] as const,
|
||||
queryFn: () => banSystemApi.getHealth(),
|
||||
enabled: isReady && activeTab === 'health',
|
||||
});
|
||||
|
||||
// Sync query data into the existing state vars so the JSX + handlers stay unchanged
|
||||
// (handleSearch overrides `users` with search results; useEffect re-syncs on next refetch).
|
||||
useEffect(() => {
|
||||
if (statusQuery.data) {
|
||||
setStatus(statusQuery.data);
|
||||
if (!statusQuery.data.enabled || !statusQuery.data.configured) {
|
||||
setError(t('banSystem.notConfigured'));
|
||||
}
|
||||
} catch {
|
||||
setError(t('banSystem.loadError'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [t]);
|
||||
|
||||
const loadTabData = useCallback(
|
||||
async (tab: TabType) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
switch (tab) {
|
||||
case 'dashboard': {
|
||||
const statsData = await banSystemApi.getStats();
|
||||
setStats(statsData);
|
||||
break;
|
||||
}
|
||||
case 'users': {
|
||||
const usersData = await banSystemApi.getUsers({ limit: 50 });
|
||||
setUsers(usersData);
|
||||
break;
|
||||
}
|
||||
case 'punishments': {
|
||||
const punishmentsData = await banSystemApi.getPunishments();
|
||||
setPunishments(punishmentsData);
|
||||
break;
|
||||
}
|
||||
case 'nodes': {
|
||||
const nodesData = await banSystemApi.getNodes();
|
||||
setNodes(nodesData);
|
||||
break;
|
||||
}
|
||||
case 'agents': {
|
||||
const agentsData = await banSystemApi.getAgents();
|
||||
setAgents(agentsData);
|
||||
break;
|
||||
}
|
||||
case 'violations': {
|
||||
const violationsData = await banSystemApi.getTrafficViolations();
|
||||
setViolations(violationsData);
|
||||
break;
|
||||
}
|
||||
case 'settings': {
|
||||
const settingsData = await banSystemApi.getSettings();
|
||||
setSettings(settingsData);
|
||||
break;
|
||||
}
|
||||
case 'traffic': {
|
||||
const trafficData = await banSystemApi.getTraffic();
|
||||
setTraffic(trafficData);
|
||||
break;
|
||||
}
|
||||
case 'reports': {
|
||||
const reportData = await banSystemApi.getReport(reportHoursRef.current);
|
||||
setReport(reportData);
|
||||
break;
|
||||
}
|
||||
case 'health': {
|
||||
const healthData = await banSystemApi.getHealth();
|
||||
setHealth(healthData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
setError(t('banSystem.loadError'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[t],
|
||||
);
|
||||
if (statusQuery.isError) setError(t('banSystem.loadError'));
|
||||
}, [statusQuery.data, statusQuery.isError, t]);
|
||||
|
||||
useEffect(() => {
|
||||
loadStatus();
|
||||
}, [loadStatus]);
|
||||
|
||||
if (dashboardQuery.data) setStats(dashboardQuery.data);
|
||||
}, [dashboardQuery.data]);
|
||||
useEffect(() => {
|
||||
if (status?.enabled && status?.configured) {
|
||||
loadTabData(activeTab);
|
||||
}
|
||||
}, [activeTab, status, loadTabData]);
|
||||
if (usersQuery.data) setUsers(usersQuery.data);
|
||||
}, [usersQuery.data]);
|
||||
useEffect(() => {
|
||||
if (punishmentsQuery.data) setPunishments(punishmentsQuery.data);
|
||||
}, [punishmentsQuery.data]);
|
||||
useEffect(() => {
|
||||
if (nodesQuery.data) setNodes(nodesQuery.data);
|
||||
}, [nodesQuery.data]);
|
||||
useEffect(() => {
|
||||
if (agentsQuery.data) setAgents(agentsQuery.data);
|
||||
}, [agentsQuery.data]);
|
||||
useEffect(() => {
|
||||
if (violationsQuery.data) setViolations(violationsQuery.data);
|
||||
}, [violationsQuery.data]);
|
||||
useEffect(() => {
|
||||
if (settingsQuery.data) setSettings(settingsQuery.data);
|
||||
}, [settingsQuery.data]);
|
||||
useEffect(() => {
|
||||
if (trafficQuery.data) setTraffic(trafficQuery.data);
|
||||
}, [trafficQuery.data]);
|
||||
useEffect(() => {
|
||||
if (reportsQuery.data) setReport(reportsQuery.data);
|
||||
}, [reportsQuery.data]);
|
||||
useEffect(() => {
|
||||
if (healthQuery.data) setHealth(healthQuery.data);
|
||||
}, [healthQuery.data]);
|
||||
|
||||
// Map activeTab → its query (used for `loading` derivation and refetchActiveTab below).
|
||||
const activeTabQuery =
|
||||
activeTab === 'dashboard'
|
||||
? dashboardQuery
|
||||
: activeTab === 'users'
|
||||
? usersQuery
|
||||
: activeTab === 'punishments'
|
||||
? punishmentsQuery
|
||||
: activeTab === 'nodes'
|
||||
? nodesQuery
|
||||
: activeTab === 'agents'
|
||||
? agentsQuery
|
||||
: activeTab === 'violations'
|
||||
? violationsQuery
|
||||
: activeTab === 'settings'
|
||||
? settingsQuery
|
||||
: activeTab === 'traffic'
|
||||
? trafficQuery
|
||||
: activeTab === 'reports'
|
||||
? reportsQuery
|
||||
: healthQuery;
|
||||
|
||||
// Derive `loading` from status + active tab query.
|
||||
useEffect(() => {
|
||||
setLoading(statusQuery.isLoading || activeTabQuery.isFetching);
|
||||
if (activeTabQuery.isError) setError(t('banSystem.loadError'));
|
||||
}, [statusQuery.isLoading, activeTabQuery.isFetching, activeTabQuery.isError, t]);
|
||||
|
||||
const refetchActiveTab = useCallback(() => {
|
||||
void activeTabQuery.refetch();
|
||||
}, [activeTabQuery]);
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (!searchQuery.trim()) {
|
||||
loadTabData('users');
|
||||
void usersQuery.refetch();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -371,7 +412,7 @@ export default function AdminBanSystem() {
|
||||
try {
|
||||
setActionLoading(userId);
|
||||
await banSystemApi.unbanUser(userId);
|
||||
loadTabData('punishments');
|
||||
void punishmentsQuery.refetch();
|
||||
} catch {
|
||||
setError(t('banSystem.loadError'));
|
||||
} finally {
|
||||
@@ -383,7 +424,7 @@ export default function AdminBanSystem() {
|
||||
try {
|
||||
setSettingLoading(key);
|
||||
await banSystemApi.toggleSetting(key);
|
||||
loadTabData('settings');
|
||||
void settingsQuery.refetch();
|
||||
} catch {
|
||||
setError(t('banSystem.loadError'));
|
||||
} finally {
|
||||
@@ -395,7 +436,7 @@ export default function AdminBanSystem() {
|
||||
try {
|
||||
setSettingLoading(key);
|
||||
await banSystemApi.setSetting(key, value);
|
||||
loadTabData('settings');
|
||||
void settingsQuery.refetch();
|
||||
} catch {
|
||||
setError(t('banSystem.loadError'));
|
||||
} finally {
|
||||
@@ -407,11 +448,7 @@ export default function AdminBanSystem() {
|
||||
setReportHours(hours);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === 'reports' && status?.enabled) {
|
||||
loadTabData('reports');
|
||||
}
|
||||
}, [reportHours, activeTab, status, loadTabData]);
|
||||
// (reports query auto-refetches when reportHours changes — it's in the queryKey)
|
||||
|
||||
const formatBytes = (bytes: number) => {
|
||||
if (bytes === 0) return '0 B';
|
||||
@@ -572,7 +609,7 @@ export default function AdminBanSystem() {
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => loadTabData(activeTab)}
|
||||
onClick={refetchActiveTab}
|
||||
disabled={loading}
|
||||
className="flex items-center gap-2 rounded-lg bg-dark-800 px-4 py-2 text-dark-300 transition-colors hover:bg-dark-700 hover:text-dark-100 disabled:opacity-50"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user