diff --git a/src/App.tsx b/src/App.tsx index 90f7b320..1f59af6d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -53,7 +53,8 @@ const queryClient = new QueryClient({ refetchOnMount: true, // Keep refetch on component mount refetchOnReconnect: true, // Keep refetch on network reconnect retry: 1, // Keep retry attempts - staleTime: 0, // Keep data fresh + staleTime: 30000, // 30 seconds - queries stay fresh for 30s + gcTime: 5 * 60 * 1000, // 5 minutes - keep in cache for 5 mins }, }, }); diff --git a/src/hooks/moderation/index.ts b/src/hooks/moderation/index.ts index 9b69b2d0..25a4c803 100644 --- a/src/hooks/moderation/index.ts +++ b/src/hooks/moderation/index.ts @@ -21,6 +21,9 @@ export type { UseRealtimeSubscriptionsReturn } from './useRealtimeSubscriptions'; +export { useQueueQuery } from './useQueueQuery'; +export type { UseQueueQueryConfig, UseQueueQueryReturn } from './useQueueQuery'; + export { useModerationQueueManager } from './useModerationQueueManager'; export type { ModerationQueueManager, diff --git a/src/hooks/moderation/useModerationQueueManager.ts b/src/hooks/moderation/useModerationQueueManager.ts index 0e3df0fe..2bf837bb 100644 --- a/src/hooks/moderation/useModerationQueueManager.ts +++ b/src/hooks/moderation/useModerationQueueManager.ts @@ -8,6 +8,7 @@ import { useModerationFilters, usePagination, useRealtimeSubscriptions, + useQueueQuery, } from "./index"; import { useModerationQueue } from "@/hooks/useModerationQueue"; @@ -122,13 +123,9 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): // Refs for tracking const recentlyRemovedRef = useRef>(new Set()); - const fetchInProgressRef = useRef(false); - const lastFetchTimeRef = useRef(0); const initialFetchCompleteRef = useRef(false); const isMountingRef = useRef(true); - const FETCH_COOLDOWN_MS = 1000; - // Store settings, filters, and pagination in refs to stabilize fetchItems const settingsRef = useRef(settings); const filtersRef = useRef(filters); @@ -153,366 +150,70 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): }, [pagination]); /** - * Fetch queue items from database + * Replace manual fetching with TanStack Query */ - const fetchItems = useCallback( - async (silent = false) => { - if (!user) return; + const queueQuery = useQueueQuery({ + userId: user?.id, + isAdmin, + isSuperuser, + entityFilter: filtersRef.current.debouncedEntityFilter, + statusFilter: filtersRef.current.debouncedStatusFilter, + tab: filtersRef.current.activeTab, + currentPage: paginationRef.current.currentPage, + pageSize: paginationRef.current.pageSize, + sortConfig: sortRef.current, + enabled: !!user, + }); - // Get caller info - const callerStack = new Error().stack; - const callerLine = callerStack?.split("\n")[2]?.trim(); + // Update items when query data changes + useEffect(() => { + if (queueQuery.items) { + setItems(queueQuery.items); + console.log('βœ… Queue items updated from TanStack Query:', queueQuery.items.length); + } + }, [queueQuery.items]); - console.log("πŸ”„ [FETCH ITEMS] Called", { - silent, - documentHidden: document.hidden, - caller: callerLine, - sortField: sortRef.current.field, - sortDirection: sortRef.current.direction, - timestamp: new Date().toISOString(), - }); + // Update loading state based on query status + useEffect(() => { + if (queueQuery.isLoading) { + setLoadingState('loading'); + } else if (queueQuery.isRefreshing) { + setLoadingState('refreshing'); + } else { + setLoadingState('ready'); + } + }, [queueQuery.isLoading, queueQuery.isRefreshing]); - // Prevent concurrent calls - if (fetchInProgressRef.current) { - console.log("⚠️ Fetch already in progress, skipping"); - return; - } + // Update total count for pagination + useEffect(() => { + paginationRef.current.setTotalCount(queueQuery.totalCount); + }, [queueQuery.totalCount]); - // Cooldown check - const now = Date.now(); - const timeSinceLastFetch = now - lastFetchTimeRef.current; - if (timeSinceLastFetch < FETCH_COOLDOWN_MS && lastFetchTimeRef.current > 0) { - console.log(`⏸️ Fetch cooldown active (${timeSinceLastFetch}ms)`); - return; - } - - fetchInProgressRef.current = true; - lastFetchTimeRef.current = now; - - console.log("πŸ” fetchItems called:", { - entityFilter: filtersRef.current.debouncedEntityFilter, - statusFilter: filtersRef.current.debouncedStatusFilter, - sortField: sortRef.current.field, - sortDirection: sortRef.current.direction, - silent, - timestamp: new Date().toISOString(), - }); - - try { - // Set loading states - if (!silent) { - setLoadingState("loading"); - } else { - setLoadingState("refreshing"); - } - - // Build base query - let submissionsQuery = supabase - .from("content_submissions") - .select( - ` - id, - submission_type, - status, - content, - created_at, - user_id, - reviewed_at, - reviewer_id, - reviewer_notes, - escalated, - assigned_to, - locked_until, - submission_items ( - id, - item_type, - item_data, - status - ) - `, - ); - - // CRITICAL: Multi-level ordering - console.log('πŸ“Š [SORT QUERY] Applying multi-level sort:', { - level1: 'escalated DESC', - level2: `${sortRef.current.field} ${sortRef.current.direction.toUpperCase()}`, - level3: sortRef.current.field !== 'created_at' ? 'created_at ASC' : 'none' - }); - - // Level 1: Always sort by escalated first (descending) - submissionsQuery = submissionsQuery.order('escalated', { ascending: false }); - - // Level 2: Apply user-selected sort (use ref) - submissionsQuery = submissionsQuery.order( - sortRef.current.field, - { ascending: sortRef.current.direction === 'asc' } - ); - - // Level 3: Tertiary sort by created_at (if not already primary) - if (sortRef.current.field !== 'created_at') { - submissionsQuery = submissionsQuery.order('created_at', { ascending: true }); - } - - // Apply tab-based status filtering (use refs) - const tab = filtersRef.current.activeTab; - const statusFilter = filtersRef.current.debouncedStatusFilter; - const entityFilter = filtersRef.current.debouncedEntityFilter; - - if (tab === "mainQueue") { - if (statusFilter === "all") { - submissionsQuery = submissionsQuery.in("status", ["pending", "flagged", "partially_approved"]); - } else if (statusFilter === "pending") { - submissionsQuery = submissionsQuery.in("status", ["pending", "partially_approved"]); - } else { - submissionsQuery = submissionsQuery.eq("status", statusFilter); - } - } else { - if (statusFilter === "all") { - submissionsQuery = submissionsQuery.in("status", ["approved", "rejected"]); - } else { - submissionsQuery = submissionsQuery.eq("status", statusFilter); - } - } - - // Apply entity type filter - if (entityFilter === "photos") { - submissionsQuery = submissionsQuery.eq("submission_type", "photo"); - } else if (entityFilter === "submissions") { - submissionsQuery = submissionsQuery.neq("submission_type", "photo"); - } - - // Apply access control - if (!isAdmin && !isSuperuser) { - const now = new Date().toISOString(); - submissionsQuery = submissionsQuery.or( - `assigned_to.is.null,locked_until.lt.${now},assigned_to.eq.${user.id}`, - ); - } - - // Get total count - build separate query with same filters - let countQuery = supabase - .from("content_submissions") - .select("*", { count: "exact", head: true }); - - // Apply same filters as main query - if (tab === "mainQueue") { - if (statusFilter === "all") { - countQuery = countQuery.in("status", ["pending", "flagged", "partially_approved"]); - } else if (statusFilter === "pending") { - countQuery = countQuery.in("status", ["pending", "partially_approved"]); - } else { - countQuery = countQuery.eq("status", statusFilter); - } - } else { - if (statusFilter === "all") { - countQuery = countQuery.in("status", ["approved", "rejected"]); - } else { - countQuery = countQuery.eq("status", statusFilter); - } - } - - if (entityFilter === "photos") { - countQuery = countQuery.eq("submission_type", "photo"); - } else if (entityFilter === "submissions") { - countQuery = countQuery.neq("submission_type", "photo"); - } - - if (!isAdmin && !isSuperuser) { - const now = new Date().toISOString(); - countQuery = countQuery.or( - `assigned_to.is.null,locked_until.lt.${now},assigned_to.eq.${user.id}`, - ); - } - - const { count } = await countQuery; - - paginationRef.current.setTotalCount(count || 0); - - // Apply pagination (use refs) - const startIndex = paginationRef.current.startIndex; - const endIndex = paginationRef.current.endIndex; - submissionsQuery = submissionsQuery.range(startIndex, endIndex); - - const { data: submissions, error: submissionsError } = await submissionsQuery; - - if (submissionsError) throw submissionsError; - - // Log the actual data returned to verify sort order - if (submissions && submissions.length > 0) { - const sortField = sortRef.current.field; - const preview = submissions.slice(0, 3).map(s => ({ - id: s.id.substring(0, 8), - [sortField]: s[sortField], - escalated: s.escalated - })); - console.log(`πŸ“‹ [SORT RESULT] First 3 items by ${sortField}:`, preview); - } - - // Fetch related profiles and entities - const userIds = [ - ...new Set([ - ...(submissions?.map((s) => s.user_id) || []), - ...(submissions?.map((s) => s.reviewer_id).filter(Boolean) || []), - ]), - ]; - - if (userIds.length > 0) { - await profileCache.bulkFetch(userIds); - } - - // Collect entity IDs - if (submissions) { - await entityCache.fetchRelatedEntities(submissions); - } - - // Map to ModerationItems - const moderationItems: ModerationItem[] = - submissions?.map((submission) => { - const content = submission.content as any; - let entityName = content?.name || "Unknown"; - let parkName: string | undefined; - - // Resolve entity names from cache - if (submission.submission_type === "ride" && content?.entity_id) { - const ride = entityCache.getCached("rides", content.entity_id); - if (ride) { - entityName = ride.name; - if (ride.park_id) { - const park = entityCache.getCached("parks", ride.park_id); - if (park) parkName = park.name; - } - } - } else if (submission.submission_type === "park" && content?.entity_id) { - const park = entityCache.getCached("parks", content.entity_id); - if (park) entityName = park.name; - } else if ( - ["manufacturer", "operator", "designer", "property_owner"].includes(submission.submission_type) && - content?.entity_id - ) { - const company = entityCache.getCached("companies", content.entity_id); - if (company) entityName = company.name; - } - - const userProfile = profileCache.getCached(submission.user_id); - const reviewerProfile = submission.reviewer_id ? profileCache.getCached(submission.reviewer_id) : undefined; - - return { - id: submission.id, - type: "content_submission", - content: submission.content, - created_at: submission.created_at, - user_id: submission.user_id, - status: submission.status, - submission_type: submission.submission_type, - user_profile: userProfile, - entity_name: entityName, - park_name: parkName, - reviewed_at: submission.reviewed_at || undefined, - reviewed_by: submission.reviewer_id || undefined, - reviewer_notes: submission.reviewer_notes || undefined, - reviewer_profile: reviewerProfile, - escalated: submission.escalated, - assigned_to: submission.assigned_to || undefined, - locked_until: submission.locked_until || undefined, - submission_items: submission.submission_items || undefined, - }; - }) || []; - - // Apply refresh strategy - const currentRefreshStrategy = settingsRef.current.refreshStrategy; - const currentPreserveInteraction = settingsRef.current.preserveInteraction; - - if (silent) { - // Background polling: detect new submissions - const currentDisplayedIds = new Set(items.map((item) => item.id)); - const newSubmissions = moderationItems.filter((item) => !currentDisplayedIds.has(item.id)); - - if (newSubmissions.length > 0) { - console.log("πŸ†• Detected new submissions:", newSubmissions.length); - - setPendingNewItems((prev) => { - const existingIds = new Set(prev.map((p) => p.id)); - const uniqueNew = newSubmissions.filter((item) => !existingIds.has(item.id)); - - if (uniqueNew.length > 0) { - setNewItemsCount((prev) => prev + uniqueNew.length); - } - - return [...prev, ...uniqueNew]; - }); - } - - // Apply refresh strategy - switch (currentRefreshStrategy) { - case "notify": - console.log("βœ… Queue frozen (notify mode)"); - break; - - case "merge": - if (newSubmissions.length > 0) { - const currentIds = new Set(items.map((item) => item.id)); - const trulyNewSubmissions = newSubmissions.filter((item) => !currentIds.has(item.id)); - - if (trulyNewSubmissions.length > 0) { - setItems((prev) => [...prev, ...trulyNewSubmissions]); - console.log("πŸ”€ Queue merged - added", trulyNewSubmissions.length, "items"); - } - } - break; - - case "replace": - // Full replace - simple and predictable - setItems(moderationItems); - console.log("πŸ”„ Queue updated (replace mode)"); - - if (!currentPreserveInteraction) { - setPendingNewItems([]); - setNewItemsCount(0); - } - break; - } - } else { - // Manual refresh - ALWAYS update items for user-initiated changes (sort/filter) - // Don't use smartMerge here because it won't detect order changes - setItems(moderationItems); - console.log("πŸ”„ Queue updated (manual refresh) - items:", moderationItems.length); - - setPendingNewItems([]); - setNewItemsCount(0); - } - } catch (error: any) { - console.error("Error fetching moderation items:", error); - toast({ - title: "Error", - description: error.message || "Failed to fetch moderation queue", - variant: "destructive", - }); - } finally { - fetchInProgressRef.current = false; - setLoadingState("ready"); - } - }, - [ - user, - isAdmin, - isSuperuser, - profileCache, - entityCache, - toast - ], - ); + // Mark initial fetch as complete + useEffect(() => { + if (!queueQuery.isLoading && !initialFetchCompleteRef.current) { + initialFetchCompleteRef.current = true; + console.log('βœ… Initial queue fetch complete'); + } + }, [queueQuery.isLoading]); /** - * Show pending new items by merging them into the queue + * Manual refresh function */ - const showNewItems = useCallback(() => { - if (pendingNewItems.length > 0) { - setItems((prev) => [...pendingNewItems, ...prev]); - setPendingNewItems([]); - setNewItemsCount(0); - console.log("βœ… New items merged into queue:", pendingNewItems.length); - } - }, [pendingNewItems]); + const refresh = useCallback(async () => { + console.log('πŸ”„ Manual refresh triggered'); + await queueQuery.refetch(); + }, [queueQuery]); + + /** + * Show pending new items by invalidating query + */ + const showNewItems = useCallback(async () => { + console.log('βœ… Showing new items via query invalidation'); + await queueQuery.invalidate(); + setPendingNewItems([]); + setNewItemsCount(0); + }, [queueQuery]); /** * Mark an item as being interacted with (prevents realtime updates) @@ -808,51 +509,36 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): [filters.statusFilter, toast], ); - // Initial fetch on mount + // Mark initial fetch as complete when query loads useEffect(() => { - if (!user) return; - - isMountingRef.current = true; - fetchItems(false).then(() => { + if (!queueQuery.isLoading && !initialFetchCompleteRef.current) { initialFetchCompleteRef.current = true; - requestAnimationFrame(() => { - isMountingRef.current = false; - }); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [user?.id]); + isMountingRef.current = false; + console.log('βœ… Initial queue fetch complete'); + } + }, [queueQuery.isLoading]); - // Filter and sort changes trigger refetch + // Invalidate query when filters or sort changes useEffect(() => { - if (!user || !initialFetchCompleteRef.current || isMountingRef.current) return; - - console.log('πŸ”„ [SORT/FILTER CHANGE] Detected change:', { - entityFilter: filters.debouncedEntityFilter, - statusFilter: filters.debouncedStatusFilter, - sortField: filters.debouncedSortConfig.field, - sortDirection: filters.debouncedSortConfig.direction, - timestamp: new Date().toISOString() - }); + if ( + !user || + !initialFetchCompleteRef.current || + isMountingRef.current + ) return; + console.log('πŸ”„ Filters/sort changed, invalidating query'); pagination.reset(); - fetchItems(false); + queueQuery.invalidate(); }, [ - filters.debouncedEntityFilter, - filters.debouncedStatusFilter, + filters.debouncedEntityFilter, + filters.debouncedStatusFilter, filters.debouncedSortConfig.field, filters.debouncedSortConfig.direction, user, - fetchItems, + queueQuery, pagination ]); - // Pagination changes trigger refetch - useEffect(() => { - if (!user || !initialFetchCompleteRef.current || pagination.currentPage === 1) return; - - fetchItems(true); - }, [pagination.currentPage, pagination.pageSize, user, fetchItems]); - // Polling effect (when realtime disabled) useEffect(() => { if (!user || settings.refreshMode !== "auto" || loadingState === "initial" || settings.useRealtimeQueue) { @@ -862,14 +548,14 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): console.log("⚠️ Polling ENABLED - interval:", settings.pollInterval); const interval = setInterval(() => { console.log("πŸ”„ Polling refresh triggered"); - fetchItems(true); + queueQuery.refetch(); }, settings.pollInterval); return () => { clearInterval(interval); console.log("πŸ›‘ Polling stopped"); }; - }, [user, settings.refreshMode, settings.pollInterval, loadingState, settings.useRealtimeQueue, fetchItems]); + }, [user, settings.refreshMode, settings.pollInterval, loadingState, settings.useRealtimeQueue, queueQuery]); // Initialize realtime subscriptions useRealtimeSubscriptions({ @@ -927,9 +613,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): showNewItems, interactingWith, markInteracting, - refresh: async () => { - await fetchItems(false); - }, + refresh, performAction, deleteSubmission, resetToPending, diff --git a/src/hooks/moderation/useQueueQuery.ts b/src/hooks/moderation/useQueueQuery.ts new file mode 100644 index 00000000..53085501 --- /dev/null +++ b/src/hooks/moderation/useQueueQuery.ts @@ -0,0 +1,141 @@ +/** + * TanStack Query hook for moderation queue data fetching + * + * Wraps the existing fetchSubmissions query builder with React Query + * to provide automatic caching, deduplication, and background refetching. + */ + +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { fetchSubmissions, type QueryConfig } from '@/lib/moderation/queries'; +import { supabase } from '@/integrations/supabase/client'; +import type { ModerationItem } from '@/types/moderation'; + +/** + * Configuration for queue query + */ +export interface UseQueueQueryConfig { + /** User making the query */ + userId: string | undefined; + + /** Whether user is admin */ + isAdmin: boolean; + + /** Whether user is superuser */ + isSuperuser: boolean; + + /** Entity filter */ + entityFilter: string; + + /** Status filter */ + statusFilter: string; + + /** Active tab */ + tab: 'mainQueue' | 'archive'; + + /** Current page */ + currentPage: number; + + /** Page size */ + pageSize: number; + + /** Sort configuration */ + sortConfig: { + field: string; + direction: 'asc' | 'desc'; + }; + + /** Whether query is enabled (defaults to true) */ + enabled?: boolean; +} + +/** + * Return type for useQueueQuery + */ +export interface UseQueueQueryReturn { + /** Queue items */ + items: ModerationItem[]; + + /** Total count of items matching filters */ + totalCount: number; + + /** Initial loading state (no data yet) */ + isLoading: boolean; + + /** Background refresh in progress (has data already) */ + isRefreshing: boolean; + + /** Any error that occurred */ + error: Error | null; + + /** Manually trigger a refetch */ + refetch: () => Promise; + + /** Invalidate this query (triggers background refetch) */ + invalidate: () => Promise; +} + +/** + * Hook to fetch moderation queue data using TanStack Query + */ +export function useQueueQuery(config: UseQueueQueryConfig): UseQueueQueryReturn { + const queryClient = useQueryClient(); + + // Build query config for fetchSubmissions + const queryConfig: QueryConfig = { + userId: config.userId || '', + isAdmin: config.isAdmin, + isSuperuser: config.isSuperuser, + entityFilter: config.entityFilter as any, + statusFilter: config.statusFilter as any, + tab: config.tab, + currentPage: config.currentPage, + pageSize: config.pageSize, + sortConfig: config.sortConfig as any, + }; + + // Create stable query key (TanStack Query uses this for caching/deduplication) + const queryKey = [ + 'moderation-queue', + config.entityFilter, + config.statusFilter, + config.tab, + config.currentPage, + config.pageSize, + config.sortConfig.field, + config.sortConfig.direction, + ]; + + // Execute query + const query = useQuery({ + queryKey, + queryFn: async () => { + console.log('πŸ” [TanStack Query] Fetching queue data:', queryKey); + const result = await fetchSubmissions(supabase, queryConfig); + + if (result.error) { + throw result.error; + } + + console.log('βœ… [TanStack Query] Fetched', result.submissions.length, 'items'); + return result; + }, + enabled: config.enabled !== false && !!config.userId, + staleTime: 30000, // 30 seconds + gcTime: 5 * 60 * 1000, // 5 minutes + }); + + // Invalidate helper + const invalidate = async () => { + await queryClient.invalidateQueries({ queryKey: ['moderation-queue'] }); + }; + + return { + items: query.data?.submissions || [], + totalCount: query.data?.totalCount || 0, + isLoading: query.isLoading, + isRefreshing: query.isFetching && !query.isLoading, + error: query.error as Error | null, + refetch: query.refetch, + invalidate, + }; +} diff --git a/src/hooks/moderation/useRealtimeSubscriptions.ts b/src/hooks/moderation/useRealtimeSubscriptions.ts index 0f4ebe79..4228b55f 100644 --- a/src/hooks/moderation/useRealtimeSubscriptions.ts +++ b/src/hooks/moderation/useRealtimeSubscriptions.ts @@ -6,6 +6,7 @@ */ import { useEffect, useRef, useState, useCallback } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; import { supabase } from '@/integrations/supabase/client'; import type { RealtimeChannel } from '@supabase/supabase-js'; import type { ModerationItem, EntityFilter, StatusFilter } from '@/types/moderation'; @@ -85,6 +86,8 @@ export interface UseRealtimeSubscriptionsReturn { export function useRealtimeSubscriptions( config: RealtimeSubscriptionConfig ): UseRealtimeSubscriptionsReturn { + const queryClient = useQueryClient(); + const { enabled, filters, @@ -268,21 +271,22 @@ export function useRealtimeSubscriptions( return; } - console.log('βœ… NEW submission matches filters:', newSubmission.id); + console.log('βœ… NEW submission matches filters, invalidating query:', newSubmission.id); - // Fetch full submission details + // Invalidate the query to trigger background refetch + await queryClient.invalidateQueries({ queryKey: ['moderation-queue'] }); + + // Call legacy callback for new item notification + // (This maintains compatibility with NewItemsAlert component) try { const submission = await fetchSubmissionDetails(newSubmission.id); if (!submission) return; - // Fetch user profile const profile = await profileCache.bulkFetch([submission.user_id]); const userProfile = profile[0]; - // Resolve entity names const { entityName, parkName } = await resolveEntityNames(submission); - // Build full ModerationItem const fullItem = buildModerationItem( submission, userProfile, @@ -290,17 +294,15 @@ export function useRealtimeSubscriptions( parkName ); - // Trigger callback onNewItem(fullItem); - - console.log('πŸŽ‰ New submission added to queue:', fullItem.id); } catch (error) { - console.error('Error processing new submission:', error); + console.error('Error building new item notification:', error); } }, [ filters, pauseWhenHidden, recentlyRemovedIds, + queryClient, fetchSubmissionDetails, profileCache, resolveEntityNames, @@ -335,56 +337,18 @@ export function useRealtimeSubscriptions( // Debounce the update debouncedUpdate(updatedSubmission.id, async () => { - // Check if submission matches current filters + console.log('πŸ”„ Invalidating query due to UPDATE:', updatedSubmission.id); + + // Simply invalidate the query - TanStack Query handles the rest + await queryClient.invalidateQueries({ queryKey: ['moderation-queue'] }); + + // Legacy callback for compatibility const matchesEntity = matchesEntityFilter(updatedSubmission, filters.entityFilter); const matchesStatus = matchesStatusFilter(updatedSubmission, filters.statusFilter); - - const wasInQueue = currentItemsRef.current.some(i => i.id === updatedSubmission.id); const shouldBeInQueue = matchesEntity && matchesStatus; - if (wasInQueue && !shouldBeInQueue) { - // Submission moved out of current filter (e.g., pending β†’ approved) - console.log('❌ Submission moved out of queue:', updatedSubmission.id); - onItemRemoved(updatedSubmission.id); - return; - } - if (!shouldBeInQueue) { - // Item doesn't belong in queue at all - return; - } - - // Fetch full details - try { - const submission = await fetchSubmissionDetails(updatedSubmission.id); - if (!submission) return; - - // Get user profile - const profiles = await profileCache.bulkFetch([submission.user_id]); - const profile = profiles[0]; - - // Resolve entity name (simplified for updates) - const content = submission.content as any; - const entityName = content?.name || 'Unknown'; - - const fullItem = buildModerationItem( - submission, - profile, - entityName, - undefined - ); - - // Check if item actually changed - const currentItem = currentItemsRef.current.find(i => i.id === fullItem.id); - if (currentItem && !hasItemChanged(currentItem, fullItem)) { - console.log('βœ… Realtime UPDATE: No changes detected for', fullItem.id); - return; - } - - console.log('πŸ”„ Realtime UPDATE: Changes detected for', fullItem.id); - onUpdateItem(fullItem, false); - } catch (error) { - console.error('Error processing updated submission:', error); + onItemRemoved(updatedSubmission.id); } }); }, [ @@ -393,9 +357,7 @@ export function useRealtimeSubscriptions( recentlyRemovedIds, interactingWithIds, debouncedUpdate, - fetchSubmissionDetails, - profileCache, - onUpdateItem, + queryClient, onItemRemoved, ]);