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 { useTranslation } from 'react-i18next';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { AdminBackButton } from '../components/admin/AdminBackButton';
|
import { AdminBackButton } from '../components/admin/AdminBackButton';
|
||||||
import { useFocusTrap } from '../hooks/useFocusTrap';
|
import { useFocusTrap } from '../hooks/useFocusTrap';
|
||||||
import {
|
import {
|
||||||
@@ -211,8 +212,6 @@ export default function AdminBanSystem() {
|
|||||||
const [report, setReport] = useState<BanReportResponse | null>(null);
|
const [report, setReport] = useState<BanReportResponse | null>(null);
|
||||||
const [health, setHealth] = useState<BanHealthResponse | null>(null);
|
const [health, setHealth] = useState<BanHealthResponse | null>(null);
|
||||||
const [reportHours, setReportHours] = useState(24);
|
const [reportHours, setReportHours] = useState(24);
|
||||||
const reportHoursRef = useRef(reportHours);
|
|
||||||
reportHoursRef.current = reportHours;
|
|
||||||
const [settingLoading, setSettingLoading] = useState<string | null>(null);
|
const [settingLoading, setSettingLoading] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
@@ -247,101 +246,143 @@ export default function AdminBanSystem() {
|
|||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [actionLoading, setActionLoading] = useState<string | null>(null);
|
const [actionLoading, setActionLoading] = useState<string | null>(null);
|
||||||
|
|
||||||
const loadStatus = useCallback(async () => {
|
// React Query: status once at mount; each tab fetches lazily via `enabled`.
|
||||||
try {
|
// Caching means switching tabs returns to cached data instantly (with background revalidate).
|
||||||
setLoading(true);
|
const statusQuery = useQuery({
|
||||||
const data = await banSystemApi.getStatus();
|
queryKey: ['ban-status'] as const,
|
||||||
setStatus(data);
|
queryFn: () => banSystemApi.getStatus(),
|
||||||
if (!data.enabled || !data.configured) {
|
});
|
||||||
|
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'));
|
setError(t('banSystem.notConfigured'));
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
setError(t('banSystem.loadError'));
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
}, [t]);
|
if (statusQuery.isError) setError(t('banSystem.loadError'));
|
||||||
|
}, [statusQuery.data, statusQuery.isError, 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],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadStatus();
|
if (dashboardQuery.data) setStats(dashboardQuery.data);
|
||||||
}, [loadStatus]);
|
}, [dashboardQuery.data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status?.enabled && status?.configured) {
|
if (usersQuery.data) setUsers(usersQuery.data);
|
||||||
loadTabData(activeTab);
|
}, [usersQuery.data]);
|
||||||
}
|
useEffect(() => {
|
||||||
}, [activeTab, status, loadTabData]);
|
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 () => {
|
const handleSearch = async () => {
|
||||||
if (!searchQuery.trim()) {
|
if (!searchQuery.trim()) {
|
||||||
loadTabData('users');
|
void usersQuery.refetch();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -371,7 +412,7 @@ export default function AdminBanSystem() {
|
|||||||
try {
|
try {
|
||||||
setActionLoading(userId);
|
setActionLoading(userId);
|
||||||
await banSystemApi.unbanUser(userId);
|
await banSystemApi.unbanUser(userId);
|
||||||
loadTabData('punishments');
|
void punishmentsQuery.refetch();
|
||||||
} catch {
|
} catch {
|
||||||
setError(t('banSystem.loadError'));
|
setError(t('banSystem.loadError'));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -383,7 +424,7 @@ export default function AdminBanSystem() {
|
|||||||
try {
|
try {
|
||||||
setSettingLoading(key);
|
setSettingLoading(key);
|
||||||
await banSystemApi.toggleSetting(key);
|
await banSystemApi.toggleSetting(key);
|
||||||
loadTabData('settings');
|
void settingsQuery.refetch();
|
||||||
} catch {
|
} catch {
|
||||||
setError(t('banSystem.loadError'));
|
setError(t('banSystem.loadError'));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -395,7 +436,7 @@ export default function AdminBanSystem() {
|
|||||||
try {
|
try {
|
||||||
setSettingLoading(key);
|
setSettingLoading(key);
|
||||||
await banSystemApi.setSetting(key, value);
|
await banSystemApi.setSetting(key, value);
|
||||||
loadTabData('settings');
|
void settingsQuery.refetch();
|
||||||
} catch {
|
} catch {
|
||||||
setError(t('banSystem.loadError'));
|
setError(t('banSystem.loadError'));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -407,11 +448,7 @@ export default function AdminBanSystem() {
|
|||||||
setReportHours(hours);
|
setReportHours(hours);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
// (reports query auto-refetches when reportHours changes — it's in the queryKey)
|
||||||
if (activeTab === 'reports' && status?.enabled) {
|
|
||||||
loadTabData('reports');
|
|
||||||
}
|
|
||||||
}, [reportHours, activeTab, status, loadTabData]);
|
|
||||||
|
|
||||||
const formatBytes = (bytes: number) => {
|
const formatBytes = (bytes: number) => {
|
||||||
if (bytes === 0) return '0 B';
|
if (bytes === 0) return '0 B';
|
||||||
@@ -572,7 +609,7 @@ export default function AdminBanSystem() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => loadTabData(activeTab)}
|
onClick={refetchActiveTab}
|
||||||
disabled={loading}
|
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"
|
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