diff --git a/src/hooks/useAdminSettings.ts b/src/hooks/useAdminSettings.ts index 503eb74d..882f53aa 100644 --- a/src/hooks/useAdminSettings.ts +++ b/src/hooks/useAdminSettings.ts @@ -115,6 +115,17 @@ export function useAdminSettings() { return value === true || value === 'true'; }; + const getAdminPanelRefreshMode = () => { + const value = getSettingValue('system.admin_panel_refresh_mode', 'auto'); + // Remove quotes if they exist (JSON string stored in DB) + return typeof value === 'string' ? value.replace(/"/g, '') : value; + }; + + const getAdminPanelPollInterval = () => { + const value = getSettingValue('system.admin_panel_poll_interval', 30); + return parseInt(value?.toString() || '30') * 1000; // Convert to milliseconds + }; + return { settings, isLoading, @@ -132,5 +143,7 @@ export function useAdminSettings() { getReportThreshold, getAuditRetentionDays, getAutoCleanupEnabled, + getAdminPanelRefreshMode, + getAdminPanelPollInterval, }; } \ No newline at end of file diff --git a/src/hooks/useModerationStats.ts b/src/hooks/useModerationStats.ts new file mode 100644 index 00000000..dde6a2f8 --- /dev/null +++ b/src/hooks/useModerationStats.ts @@ -0,0 +1,101 @@ +import { useEffect, useState, useRef, useCallback } from 'react'; +import { supabase } from '@/integrations/supabase/client'; + +interface ModerationStats { + pendingSubmissions: number; + openReports: number; + flaggedContent: number; +} + +interface UseModerationStatsOptions { + onStatsChange?: (stats: ModerationStats) => void; + enabled?: boolean; + pollingEnabled?: boolean; + pollingInterval?: number; +} + +export const useModerationStats = (options: UseModerationStatsOptions = {}) => { + const { + onStatsChange, + enabled = true, + pollingEnabled = true, + pollingInterval = 30000 // Default 30 seconds + } = options; + + const [stats, setStats] = useState({ + pendingSubmissions: 0, + openReports: 0, + flaggedContent: 0, + }); + + const [isLoading, setIsLoading] = useState(true); + const [lastUpdated, setLastUpdated] = useState(null); + const onStatsChangeRef = useRef(onStatsChange); + + // Update ref when callback changes + useEffect(() => { + onStatsChangeRef.current = onStatsChange; + }, [onStatsChange]); + + const fetchStats = useCallback(async () => { + if (!enabled) return; + + try { + setIsLoading(true); + + const [submissionsResult, reportsResult, reviewsResult] = await Promise.all([ + supabase + .from('content_submissions') + .select('id', { count: 'exact', head: true }) + .eq('status', 'pending'), + supabase + .from('reports') + .select('id', { count: 'exact', head: true }) + .eq('status', 'pending'), + supabase + .from('reviews') + .select('id', { count: 'exact', head: true }) + .eq('moderation_status', 'flagged'), + ]); + + const newStats = { + pendingSubmissions: submissionsResult.count || 0, + openReports: reportsResult.count || 0, + flaggedContent: reviewsResult.count || 0, + }; + + setStats(newStats); + setLastUpdated(new Date()); + onStatsChangeRef.current?.(newStats); + } catch (error) { + console.error('Error fetching moderation stats:', error); + } finally { + setIsLoading(false); + } + }, [enabled]); + + // Initial fetch + useEffect(() => { + if (enabled) { + fetchStats(); + } + }, [enabled, fetchStats]); + + // Polling + useEffect(() => { + if (!enabled || !pollingEnabled) return; + + const interval = setInterval(fetchStats, pollingInterval); + + return () => { + clearInterval(interval); + }; + }, [enabled, pollingEnabled, pollingInterval, fetchStats]); + + return { + stats, + refresh: fetchStats, + isLoading, + lastUpdated + }; +}; diff --git a/src/pages/Admin.tsx b/src/pages/Admin.tsx index 0c00c0d6..895fecde 100644 --- a/src/pages/Admin.tsx +++ b/src/pages/Admin.tsx @@ -1,6 +1,6 @@ -import { useRef, useEffect, useCallback, useState } from 'react'; +import { useRef, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Shield, Users, FileText, Flag, AlertCircle } from 'lucide-react'; +import { Shield, Users, FileText, Flag, AlertCircle, RefreshCw } from 'lucide-react'; import { useUserRole } from '@/hooks/useUserRole'; import { useAuth } from '@/hooks/useAuth'; import { useIsMobile } from '@/hooks/use-mobile'; @@ -11,8 +11,8 @@ import { ModerationQueue, ModerationQueueRef } from '@/components/moderation/Mod import { ReportsQueue } from '@/components/moderation/ReportsQueue'; import { UserManagement } from '@/components/admin/UserManagement'; import { AdminHeader } from '@/components/layout/AdminHeader'; -import { supabase } from '@/integrations/supabase/client'; -import { useRealtimeModerationStats } from '@/hooks/useRealtimeModerationStats'; +import { useModerationStats } from '@/hooks/useModerationStats'; +import { useAdminSettings } from '@/hooks/useAdminSettings'; export default function Admin() { const isMobile = useIsMobile(); @@ -21,24 +21,27 @@ export default function Admin() { const navigate = useNavigate(); const moderationQueueRef = useRef(null); - // Use realtime stats hook for live updates - const { stats: realtimeStats, refresh: refreshStats } = useRealtimeModerationStats({ - onStatsChange: (newStats) => { - console.log('Stats updated in real-time:', newStats); - }, - enabled: !!user && !authLoading && !roleLoading && isModerator(), - }); + // Get admin settings for polling configuration + const { + getAdminPanelRefreshMode, + getAdminPanelPollInterval, + isLoading: settingsLoading + } = useAdminSettings(); - const [isFetching, setIsFetching] = useState(false); - - const fetchStats = useCallback(async () => { - refreshStats(); - }, [refreshStats]); + const refreshMode = getAdminPanelRefreshMode(); + const pollInterval = getAdminPanelPollInterval(); + + // Use stats hook with configurable polling + const { stats, refresh: refreshStats, lastUpdated } = useModerationStats({ + enabled: !!user && !authLoading && !roleLoading && isModerator(), + pollingEnabled: refreshMode === 'auto', + pollingInterval: pollInterval, + }); const handleRefresh = useCallback(() => { moderationQueueRef.current?.refresh(); - fetchStats(); // Also refresh stats - }, []); + refreshStats(); + }, [refreshStats]); useEffect(() => { if (!authLoading && !roleLoading) { @@ -75,42 +78,62 @@ export default function Admin() { <>
-
- - - - Pending Submissions - - -
- {realtimeStats.pendingSubmissions} -
-
-
- - - - - Open Reports - - -
- {realtimeStats.openReports} -
-
-
- - - - - Flagged Content - - -
- {realtimeStats.flaggedContent} -
-
-
+
+ {/* Refresh status indicator */} +
+
+ + {refreshMode === 'auto' ? ( + Auto-refresh: every {pollInterval / 1000}s + ) : ( + Manual refresh only + )} + {lastUpdated && ( + + • Last updated: {lastUpdated.toLocaleTimeString()} + + )} +
+
+ + {/* Stats cards */} +
+ + + + Pending Submissions + + +
+ {stats.pendingSubmissions} +
+
+
+ + + + + Open Reports + + +
+ {stats.openReports} +
+
+
+ + + + + Flagged Content + + +
+ {stats.flaggedContent} +
+
+
+
{/* Content Moderation Section */} diff --git a/src/pages/AdminSettings.tsx b/src/pages/AdminSettings.tsx index de6d9d5e..632bdada 100644 --- a/src/pages/AdminSettings.tsx +++ b/src/pages/AdminSettings.tsx @@ -251,6 +251,79 @@ export default function AdminSettings() { ); } + // Admin panel refresh mode setting + if (setting.setting_key === 'system.admin_panel_refresh_mode') { + return ( + +
+
+ + +
+

+ Choose how the admin panel statistics refresh +

+
+ + + Current: {(typeof localValue === 'string' ? localValue.replace(/"/g, '') : localValue) === 'auto' ? 'Auto-refresh' : 'Manual'} + +
+
+
+ ); + } + + // Admin panel poll interval setting + if (setting.setting_key === 'system.admin_panel_poll_interval') { + return ( + +
+
+ + +
+

+ How often to automatically refresh admin panel statistics (when auto-refresh is enabled) +

+
+ + Current: {localValue}s +
+
+
+ ); + } + // Boolean/switch settings if (setting.setting_key.includes('email_alerts') || setting.setting_key.includes('require_approval') || diff --git a/supabase/migrations/20251003183557_e9441757-d553-4301-951c-cc21cdb7a231.sql b/supabase/migrations/20251003183557_e9441757-d553-4301-951c-cc21cdb7a231.sql new file mode 100644 index 00000000..5206b644 --- /dev/null +++ b/supabase/migrations/20251003183557_e9441757-d553-4301-951c-cc21cdb7a231.sql @@ -0,0 +1,6 @@ +-- Add admin settings for panel refresh configuration +INSERT INTO public.admin_settings (setting_key, setting_value, category, description) +VALUES + ('system.admin_panel_refresh_mode', '"auto"', 'system', 'Admin panel refresh mode: manual or auto'), + ('system.admin_panel_poll_interval', '30', 'system', 'Admin panel auto-refresh interval in seconds (10-300)') +ON CONFLICT (setting_key) DO NOTHING; \ No newline at end of file