diff --git a/src/components/moderation/ModerationQueue.tsx b/src/components/moderation/ModerationQueue.tsx index 44633d0b..7e3248fb 100644 --- a/src/components/moderation/ModerationQueue.tsx +++ b/src/components/moderation/ModerationQueue.tsx @@ -92,12 +92,14 @@ export const ModerationQueue = forwardRef((props, ref) => { companies: new Map() }); const [submissionMemo, setSubmissionMemo] = useState>(new Map()); + const [pendingNewItems, setPendingNewItems] = useState([]); const { toast } = useToast(); const { isAdmin, isSuperuser } = useUserRole(); const { user } = useAuth(); const queue = useModerationQueue(); const fetchInProgressRef = useRef(false); const itemsRef = useRef([]); + const loadedIdsRef = useRef>(new Set()); // Get admin settings for polling configuration const { @@ -111,9 +113,10 @@ export const ModerationQueue = forwardRef((props, ref) => { const refreshStrategy = getAutoRefreshStrategy(); const preserveInteraction = getPreserveInteractionState(); - // Sync itemsRef with items state + // Sync itemsRef and loadedIdsRef with items state useEffect(() => { itemsRef.current = items; + loadedIdsRef.current = new Set(items.map(item => item.id)); }, [items]); const fetchItems = useCallback(async (entityFilter: EntityFilter = 'all', statusFilter: StatusFilter = 'pending', silent = false) => { @@ -181,6 +184,19 @@ export const ModerationQueue = forwardRef((props, ref) => { submissionsQuery = submissionsQuery.neq('submission_type', 'photo'); } + // CRM-style claim filtering: moderators only see unclaimed OR self-assigned submissions + // Admins see all submissions + if (!isAdmin && !isSuperuser) { + const now = new Date().toISOString(); + // Show submissions that are: + // 1. Unclaimed (assigned_to is null) + // 2. Have expired locks (locked_until < now) + // 3. Are assigned to current user + submissionsQuery = submissionsQuery.or( + `assigned_to.is.null,locked_until.lt.${now},assigned_to.eq.${user.id}` + ); + } + const { data: submissions, error: submissionsError } = await submissionsQuery; if (submissionsError) throw submissionsError; @@ -384,22 +400,39 @@ export const ModerationQueue = forwardRef((props, ref) => { }); setSubmissionMemo(newMemoMap); - // Apply smart merge for state updates using ref for current items - const mergeResult = smartMergeArray(itemsRef.current, moderationItems, { - compareFields: ['status', 'reviewed_at', 'reviewer_notes'], - preserveOrder: silent && preserveInteraction, - addToTop: false, - }); - - if (!silent || mergeResult.hasChanges) { - setItems(mergeResult.items); + // CRM-style frozen queue logic + if (silent) { + // Background polling: ONLY detect NEW submissions, never update existing ones + const currentLoadedIds = loadedIdsRef.current; + const newSubmissions = moderationItems.filter(item => !currentLoadedIds.has(item.id)); - // Track new items for toast notification - if (silent && mergeResult.changes.added.length > 0) { - setNewItemsCount(prev => prev + mergeResult.changes.added.length); - } else if (!silent) { - setNewItemsCount(0); + if (newSubmissions.length > 0) { + console.log('🆕 Detected new submissions:', newSubmissions.length); + + // Check against existing pendingNewItems to avoid double-counting + setPendingNewItems(prev => { + const existingIds = new Set(prev.map(p => p.id)); + const uniqueNew = newSubmissions.filter(item => !existingIds.has(item.id)); + + // Track these IDs as loaded to prevent re-counting on next poll + if (uniqueNew.length > 0) { + const newIds = uniqueNew.map(item => item.id); + loadedIdsRef.current = new Set([...currentLoadedIds, ...newIds]); + setNewItemsCount(prev => prev + uniqueNew.length); + } + + return [...prev, ...uniqueNew]; + }); } + + // DON'T update items array during background polling - queue stays frozen + console.log('✅ Queue frozen - existing submissions unchanged'); + } else { + // Normal fetch: Load all items and reset pending + setItems(moderationItems); + setPendingNewItems([]); + setNewItemsCount(0); + console.log('📋 Queue loaded with', moderationItems.length, 'submissions'); } } catch (error: any) { @@ -1629,6 +1662,36 @@ export const ModerationQueue = forwardRef((props, ref) => { {/* Action buttons based on status */} {(item.status === 'pending' || item.status === 'flagged') && ( <> + {/* Claim button for unclaimed submissions */} + {!lockedSubmissions.has(item.id) && queue.currentLock?.submissionId !== item.id && ( +
+ + + Unclaimed Submission + +
+ Claim this submission to lock it for 15 minutes while you review + +
+
+
+
+ )} +