Refactor polling to prevent interruptions

This commit is contained in:
gpt-engineer-app[bot]
2025-10-03 19:12:50 +00:00
parent a2d3ed5ea4
commit b221c75d4a
3 changed files with 57 additions and 23 deletions

View File

@@ -56,6 +56,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [items, setItems] = useState<ModerationItem[]>([]); const [items, setItems] = useState<ModerationItem[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [isInitialLoad, setIsInitialLoad] = useState(true);
const [actionLoading, setActionLoading] = useState<string | null>(null); const [actionLoading, setActionLoading] = useState<string | null>(null);
const [notes, setNotes] = useState<Record<string, string>>({}); const [notes, setNotes] = useState<Record<string, string>>({});
const [activeEntityFilter, setActiveEntityFilter] = useState<EntityFilter>('all'); const [activeEntityFilter, setActiveEntityFilter] = useState<EntityFilter>('all');
@@ -77,17 +78,20 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
// Expose refresh method via ref // Expose refresh method via ref
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
refresh: () => { refresh: () => {
fetchItems(activeEntityFilter, activeStatusFilter); fetchItems(activeEntityFilter, activeStatusFilter, false); // Manual refresh shows loading
} }
}), [activeEntityFilter, activeStatusFilter]); }), [activeEntityFilter, activeStatusFilter]);
const fetchItems = async (entityFilter: EntityFilter = 'all', statusFilter: StatusFilter = 'pending') => { const fetchItems = async (entityFilter: EntityFilter = 'all', statusFilter: StatusFilter = 'pending', silent = false) => {
if (!user) { if (!user) {
return; return;
} }
try { try {
setLoading(true); // Only show loading on initial load or filter change
if (!silent) {
setLoading(true);
}
let reviewStatuses: string[] = []; let reviewStatuses: string[] = [];
let submissionStatuses: string[] = []; let submissionStatuses: string[] = [];
@@ -346,29 +350,35 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
variant: "destructive", variant: "destructive",
}); });
} finally { } finally {
setLoading(false); // Only clear loading if it was set
if (!silent) {
setLoading(false);
}
if (isInitialLoad) {
setIsInitialLoad(false);
}
} }
}; };
// Initial fetch on mount and filter changes // Initial fetch on mount and filter changes
useEffect(() => { useEffect(() => {
if (user) { if (user) {
fetchItems(activeEntityFilter, activeStatusFilter); fetchItems(activeEntityFilter, activeStatusFilter, false); // Show loading
} }
}, [activeEntityFilter, activeStatusFilter, user]); }, [activeEntityFilter, activeStatusFilter, user]);
// Polling for auto-refresh // Polling for auto-refresh
useEffect(() => { useEffect(() => {
if (!user || refreshMode !== 'auto') return; if (!user || refreshMode !== 'auto' || isInitialLoad) return;
const interval = setInterval(() => { const interval = setInterval(() => {
fetchItems(activeEntityFilter, activeStatusFilter); fetchItems(activeEntityFilter, activeStatusFilter, true); // Silent refresh
}, pollInterval); }, pollInterval);
return () => { return () => {
clearInterval(interval); clearInterval(interval);
}; };
}, [user, refreshMode, pollInterval, activeEntityFilter, activeStatusFilter]); }, [user, refreshMode, pollInterval, activeEntityFilter, activeStatusFilter, isInitialLoad]);
const handleResetToPending = async (item: ModerationItem) => { const handleResetToPending = async (item: ModerationItem) => {
setActionLoading(item.id); setActionLoading(item.id);

View File

@@ -47,6 +47,7 @@ export interface ReportsQueueRef {
export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => { export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
const [reports, setReports] = useState<Report[]>([]); const [reports, setReports] = useState<Report[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [isInitialLoad, setIsInitialLoad] = useState(true);
const [actionLoading, setActionLoading] = useState<string | null>(null); const [actionLoading, setActionLoading] = useState<string | null>(null);
const { toast } = useToast(); const { toast } = useToast();
const { user } = useAuth(); const { user } = useAuth();
@@ -58,11 +59,16 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
// Expose refresh method via ref // Expose refresh method via ref
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
refresh: fetchReports refresh: () => fetchReports(false) // Manual refresh shows loading
}), []); }), []);
const fetchReports = async () => { const fetchReports = async (silent = false) => {
try { try {
// Only show loading on initial load
if (!silent) {
setLoading(true);
}
const { data, error } = await supabase const { data, error } = await supabase
.from('reports') .from('reports')
.select(` .select(`
@@ -123,29 +129,35 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
variant: "destructive", variant: "destructive",
}); });
} finally { } finally {
setLoading(false); // Only clear loading if it was set
if (!silent) {
setLoading(false);
}
if (isInitialLoad) {
setIsInitialLoad(false);
}
} }
}; };
// Initial fetch on mount // Initial fetch on mount
useEffect(() => { useEffect(() => {
if (user) { if (user) {
fetchReports(); fetchReports(false); // Show loading
} }
}, [user]); }, [user]);
// Polling for auto-refresh // Polling for auto-refresh
useEffect(() => { useEffect(() => {
if (!user || refreshMode !== 'auto') return; if (!user || refreshMode !== 'auto' || isInitialLoad) return;
const interval = setInterval(() => { const interval = setInterval(() => {
fetchReports(); fetchReports(true); // Silent refresh
}, pollInterval); }, pollInterval);
return () => { return () => {
clearInterval(interval); clearInterval(interval);
}; };
}, [user, refreshMode, pollInterval]); }, [user, refreshMode, pollInterval, isInitialLoad]);
const handleReportAction = async (reportId: string, action: 'reviewed' | 'dismissed') => { const handleReportAction = async (reportId: string, action: 'reviewed' | 'dismissed') => {
setActionLoading(reportId); setActionLoading(reportId);

View File

@@ -29,6 +29,7 @@ export const useModerationStats = (options: UseModerationStatsOptions = {}) => {
}); });
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isInitialLoad, setIsInitialLoad] = useState(true);
const [lastUpdated, setLastUpdated] = useState<Date | null>(null); const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
const onStatsChangeRef = useRef(onStatsChange); const onStatsChangeRef = useRef(onStatsChange);
@@ -37,11 +38,14 @@ export const useModerationStats = (options: UseModerationStatsOptions = {}) => {
onStatsChangeRef.current = onStatsChange; onStatsChangeRef.current = onStatsChange;
}, [onStatsChange]); }, [onStatsChange]);
const fetchStats = useCallback(async () => { const fetchStats = useCallback(async (silent = false) => {
if (!enabled) return; if (!enabled) return;
try { try {
setIsLoading(true); // Only show loading on initial load
if (!silent) {
setIsLoading(true);
}
const [submissionsResult, reportsResult, reviewsResult] = await Promise.all([ const [submissionsResult, reportsResult, reviewsResult] = await Promise.all([
supabase supabase
@@ -70,27 +74,35 @@ export const useModerationStats = (options: UseModerationStatsOptions = {}) => {
} catch (error) { } catch (error) {
console.error('Error fetching moderation stats:', error); console.error('Error fetching moderation stats:', error);
} finally { } finally {
setIsLoading(false); // Only clear loading if it was set
if (!silent) {
setIsLoading(false);
}
if (isInitialLoad) {
setIsInitialLoad(false);
}
} }
}, [enabled]); }, [enabled, isInitialLoad]);
// Initial fetch // Initial fetch
useEffect(() => { useEffect(() => {
if (enabled) { if (enabled) {
fetchStats(); fetchStats(false); // Show loading
} }
}, [enabled, fetchStats]); }, [enabled, fetchStats]);
// Polling // Polling
useEffect(() => { useEffect(() => {
if (!enabled || !pollingEnabled) return; if (!enabled || !pollingEnabled || isInitialLoad) return;
const interval = setInterval(fetchStats, pollingInterval); const interval = setInterval(() => {
fetchStats(true); // Silent refresh
}, pollingInterval);
return () => { return () => {
clearInterval(interval); clearInterval(interval);
}; };
}, [enabled, pollingEnabled, pollingInterval, fetchStats]); }, [enabled, pollingEnabled, pollingInterval, fetchStats, isInitialLoad]);
return { return {
stats, stats,