mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 15:51:12 -05:00
feat: Implement optimistic stats updates
This commit is contained in:
@@ -25,7 +25,12 @@ import { fetchSubmissionItems, type SubmissionItemWithDeps } from '@/lib/submiss
|
||||
import type { ModerationQueueRef } from '@/types/moderation';
|
||||
import type { PhotoItem } from '@/types/photos';
|
||||
|
||||
export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
||||
interface ModerationQueueProps {
|
||||
optimisticallyUpdateStats?: (delta: Partial<{ pendingSubmissions: number; openReports: number; flaggedContent: number }>) => void;
|
||||
}
|
||||
|
||||
export const ModerationQueue = forwardRef<ModerationQueueRef, ModerationQueueProps>((props, ref) => {
|
||||
const { optimisticallyUpdateStats } = props;
|
||||
const isMobile = useIsMobile();
|
||||
const { user } = useAuth();
|
||||
const { toast } = useToast();
|
||||
@@ -54,6 +59,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
||||
isAdmin: isAdmin(),
|
||||
isSuperuser: isSuperuser(),
|
||||
toast,
|
||||
optimisticallyUpdateStats,
|
||||
settings,
|
||||
});
|
||||
|
||||
|
||||
@@ -18,6 +18,12 @@ import { useModerationQueue } from "@/hooks/useModerationQueue";
|
||||
|
||||
import type { ModerationItem, EntityFilter, StatusFilter, LoadingState } from "@/types/moderation";
|
||||
|
||||
interface ModerationStats {
|
||||
pendingSubmissions: number;
|
||||
openReports: number;
|
||||
flaggedContent: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for useModerationQueueManager
|
||||
*/
|
||||
@@ -26,6 +32,7 @@ export interface ModerationQueueManagerConfig {
|
||||
isAdmin: boolean;
|
||||
isSuperuser: boolean;
|
||||
toast: ReturnType<typeof useToast>["toast"];
|
||||
optimisticallyUpdateStats?: (delta: Partial<ModerationStats>) => void;
|
||||
settings: {
|
||||
refreshMode: "auto" | "manual";
|
||||
pollInterval: number;
|
||||
@@ -81,7 +88,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
const { user, isAdmin, isSuperuser, toast, settings } = config;
|
||||
const { user, isAdmin, isSuperuser, toast, optimisticallyUpdateStats, settings } = config;
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Initialize sub-hooks
|
||||
@@ -279,6 +286,18 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate stat delta for optimistic update
|
||||
const statDelta: Partial<ModerationStats> = {};
|
||||
|
||||
if (action === 'approved' || action === 'rejected') {
|
||||
statDelta.pendingSubmissions = -1;
|
||||
}
|
||||
|
||||
// Optimistically update stats IMMEDIATELY
|
||||
if (optimisticallyUpdateStats) {
|
||||
optimisticallyUpdateStats(statDelta);
|
||||
}
|
||||
|
||||
// Optimistic update
|
||||
const shouldRemove =
|
||||
(filters.statusFilter === "pending" || filters.statusFilter === "flagged") &&
|
||||
|
||||
@@ -38,6 +38,13 @@ export const useModerationStats = (options: UseModerationStatsOptions = {}) => {
|
||||
flaggedContent: 0,
|
||||
});
|
||||
|
||||
// Optimistic deltas for immediate UI updates
|
||||
const [optimisticDeltas, setOptimisticDeltas] = useState<ModerationStats>({
|
||||
pendingSubmissions: 0,
|
||||
openReports: 0,
|
||||
flaggedContent: 0,
|
||||
});
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
||||
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
||||
@@ -49,6 +56,15 @@ export const useModerationStats = (options: UseModerationStatsOptions = {}) => {
|
||||
onStatsChangeRef.current = onStatsChange;
|
||||
}, [onStatsChange]);
|
||||
|
||||
// Optimistic update function
|
||||
const optimisticallyUpdateStats = useCallback((delta: Partial<ModerationStats>) => {
|
||||
setOptimisticDeltas(prev => ({
|
||||
pendingSubmissions: (prev.pendingSubmissions || 0) + (delta.pendingSubmissions || 0),
|
||||
openReports: (prev.openReports || 0) + (delta.openReports || 0),
|
||||
flaggedContent: (prev.flaggedContent || 0) + (delta.flaggedContent || 0),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const fetchStats = useCallback(async (silent = false) => {
|
||||
if (!enabled) return;
|
||||
|
||||
@@ -82,6 +98,13 @@ export const useModerationStats = (options: UseModerationStatsOptions = {}) => {
|
||||
setStats(newStats);
|
||||
setLastUpdated(new Date());
|
||||
onStatsChangeRef.current?.(newStats);
|
||||
|
||||
// Clear optimistic deltas when real data arrives
|
||||
setOptimisticDeltas({
|
||||
pendingSubmissions: 0,
|
||||
openReports: 0,
|
||||
flaggedContent: 0,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching moderation stats:', error);
|
||||
} finally {
|
||||
@@ -180,9 +203,17 @@ export const useModerationStats = (options: UseModerationStatsOptions = {}) => {
|
||||
};
|
||||
}, [enabled, pollingEnabled, realtimeEnabled, pollingInterval, fetchStats, isInitialLoad]);
|
||||
|
||||
// Combine real stats with optimistic deltas for display
|
||||
const displayStats = {
|
||||
pendingSubmissions: Math.max(0, stats.pendingSubmissions + optimisticDeltas.pendingSubmissions),
|
||||
openReports: Math.max(0, stats.openReports + optimisticDeltas.openReports),
|
||||
flaggedContent: Math.max(0, stats.flaggedContent + optimisticDeltas.flaggedContent),
|
||||
};
|
||||
|
||||
return {
|
||||
stats,
|
||||
stats: displayStats,
|
||||
refresh: fetchStats,
|
||||
optimisticallyUpdateStats,
|
||||
isLoading,
|
||||
lastUpdated
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function AdminDashboard() {
|
||||
const refreshMode = getAdminPanelRefreshMode();
|
||||
const pollInterval = getAdminPanelPollInterval();
|
||||
|
||||
const { stats, refresh: refreshStats, lastUpdated } = useModerationStats({
|
||||
const { stats, refresh: refreshStats, optimisticallyUpdateStats, lastUpdated } = useModerationStats({
|
||||
enabled: !!user && !authLoading && !roleLoading && isModerator(),
|
||||
pollingEnabled: refreshMode === 'auto',
|
||||
pollingInterval: pollInterval,
|
||||
@@ -293,7 +293,7 @@ export default function AdminDashboard() {
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="moderation" className="mt-6" forceMount={true} hidden={activeTab !== 'moderation'}>
|
||||
<ModerationQueue ref={moderationQueueRef} />
|
||||
<ModerationQueue ref={moderationQueueRef} optimisticallyUpdateStats={optimisticallyUpdateStats} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="reports" className="mt-6" forceMount={true} hidden={activeTab !== 'reports'}>
|
||||
|
||||
Reference in New Issue
Block a user