Reverted to commit 96a961d95c

This commit is contained in:
gpt-engineer-app[bot]
2025-10-11 15:58:11 +00:00
parent 092337ee9e
commit 1df9ada8ae
37 changed files with 173 additions and 2697 deletions

View File

@@ -62,12 +62,6 @@ interface ModerationItem {
assigned_to?: string;
locked_until?: string;
_removing?: boolean;
submission_items?: Array<{
id: string;
item_type: string;
item_data: any;
status: string;
}>;
}
type EntityFilter = 'all' | 'reviews' | 'submissions' | 'photos';
@@ -130,7 +124,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
const isMountingRef = useRef(true);
const initialFetchCompleteRef = useRef(false);
const FETCH_COOLDOWN_MS = 1000;
const isPageVisible = useRef(true);
// Pagination state
const [currentPage, setCurrentPage] = useState(1);
@@ -191,16 +184,11 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
localStorage.setItem('moderationQueue_sortConfig', JSON.stringify(sortConfig));
}, [sortConfig]);
// Sync itemsRef with items state (after React commits)
// Only sync itemsRef (not loadedIdsRef) to avoid breaking silent polling logic
useEffect(() => {
itemsRef.current = items;
}, [items]);
// Sync loadedIdsRef with items state (after React commits)
useEffect(() => {
loadedIdsRef.current = new Set(items.map(item => item.id));
}, [items]);
// Enable transitions after initial render
useEffect(() => {
if (loadingState === 'ready' && items.length > 0 && !hasRenderedOnce) {
@@ -213,34 +201,8 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
}
}, [loadingState, items.length, hasRenderedOnce]);
// Track page visibility to prevent tab-switch refreshes
useEffect(() => {
const handleVisibilityChange = () => {
const wasHidden = !isPageVisible.current;
isPageVisible.current = !document.hidden;
if (wasHidden && isPageVisible.current) {
console.log('📄 Page became visible - NOT auto-refreshing queue');
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
}, []);
const fetchItems = useCallback(async (entityFilter: EntityFilter = 'all', statusFilter: StatusFilter = 'pending', silent = false, tab: QueueTab = 'mainQueue') => {
console.log('🔍 fetchItems called:', {
hasUser: !!userRef.current,
entityFilter,
statusFilter,
silent,
tab,
loadingState,
timestamp: new Date().toISOString()
});
if (!userRef.current) {
console.warn('⚠️ fetchItems: No user available yet, cannot fetch');
return;
}
@@ -250,12 +212,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
return;
}
// Skip fetch if triggered during tab visibility change (unless manual refresh)
if (!silent && !isPageVisible.current) {
console.log('👁️ Skipping fetch while page is hidden');
return;
}
// Cooldown check - prevent rapid-fire calls
const now = Date.now();
const timeSinceLastFetch = now - lastFetchTimeRef.current;
@@ -299,13 +255,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
reviewer_notes,
escalated,
assigned_to,
locked_until,
submission_items (
id,
item_type,
item_data,
status
)
locked_until
`)
.order('escalated', { ascending: false })
.order('created_at', { ascending: true });
@@ -610,8 +560,11 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
const existingIds = new Set(prev.map(p => p.id));
const uniqueNew = newSubmissions.filter(item => !existingIds.has(item.id));
// Track count increment (loadedIdsRef will sync automatically via useEffect)
// Track these IDs as loaded to prevent re-counting on next poll
if (uniqueNew.length > 0) {
const newIds = uniqueNew.map(item => item.id);
const currentLoadedIds = loadedIdsRef.current;
loadedIdsRef.current = new Set([...currentLoadedIds, ...newIds]);
setNewItemsCount(prev => prev + uniqueNew.length);
}
@@ -652,6 +605,8 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
});
if (mergeResult.hasChanges) {
// Update ref BEFORE setState to prevent race conditions
itemsRef.current = mergeResult.items;
setItems(mergeResult.items);
console.log('🔄 Queue updated (replace mode):', {
added: mergeResult.changes.added.length,
@@ -693,6 +648,8 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
});
if (mergeResult.hasChanges) {
// Update ref BEFORE setState to prevent race conditions
itemsRef.current = mergeResult.items;
setItems(mergeResult.items);
console.log('🔄 Queue updated (manual refresh):', {
added: mergeResult.changes.added.length,
@@ -773,28 +730,15 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
// Initial fetch on mount and filter changes
useEffect(() => {
console.log('🎯 Initial fetch effect:', {
hasUser: !!user,
loadingState,
hasInitialFetchRef: hasInitialFetchRef.current,
initialFetchComplete: initialFetchCompleteRef.current,
isMounting: isMountingRef.current
});
if (!user) {
console.log('⏳ Waiting for user to be available...');
return;
}
if (!user) return;
// Phase 1: Initial fetch (run once)
if (!hasInitialFetchRef.current) {
console.log('✅ Triggering initial fetch');
hasInitialFetchRef.current = true;
isMountingRef.current = true;
fetchItems(debouncedEntityFilter, debouncedStatusFilter, false)
.then(() => {
console.log('✅ Initial fetch complete');
initialFetchCompleteRef.current = true;
// Wait for DOM to paint before allowing subsequent fetches
requestAnimationFrame(() => {
@@ -806,7 +750,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
// Phase 2: Filter changes (only after initial fetch completes)
if (!isMountingRef.current && initialFetchCompleteRef.current) {
console.log('🔄 Filter changed, fetching with debounce');
debouncedFetchItems(debouncedEntityFilter, debouncedStatusFilter, true, activeTab);
}
}, [debouncedEntityFilter, debouncedStatusFilter, user, activeTab, fetchItems, debouncedFetchItems]);
@@ -881,13 +824,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
.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
)
reviewed_at, reviewer_id, reviewer_notes, escalated, assigned_to, locked_until
`)
.eq('id', newSubmission.id)
.single();
@@ -995,7 +932,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
const newTimeout = setTimeout(() => {
updateFn();
realtimeUpdateDebounceRef.current.delete(submissionId);
}, 1000); // Wait 1000ms after last event
}, 500); // Wait 500ms after last event
realtimeUpdateDebounceRef.current.set(submissionId, newTimeout);
}, []);
@@ -1067,13 +1004,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
.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
)
reviewed_at, reviewer_id, reviewer_notes, escalated, assigned_to, locked_until
`)
.eq('id', updatedSubmission.id)
.single();
@@ -1106,7 +1037,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
escalated: submission.escalated,
assigned_to: submission.assigned_to || undefined,
locked_until: submission.locked_until || undefined,
submission_items: submission.submission_items || undefined,
};
// Update or add to queue
@@ -1122,61 +1052,26 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
const hasChanged =
currentItem.status !== fullItem.status ||
currentItem.reviewed_at !== fullItem.reviewed_at ||
currentItem.reviewed_by !== fullItem.reviewed_by ||
currentItem.reviewer_notes !== fullItem.reviewer_notes ||
currentItem.assigned_to !== fullItem.assigned_to ||
currentItem.locked_until !== fullItem.locked_until ||
currentItem.escalated !== fullItem.escalated;
JSON.stringify(currentItem.content) !== JSON.stringify(fullItem.content);
// Only check content if critical fields match (performance optimization)
let contentChanged = false;
if (!hasChanged && currentItem.content && fullItem.content) {
// Compare content reference first
if (currentItem.content !== fullItem.content) {
// Check each key for actual value changes (one level deep)
const currentKeys = Object.keys(currentItem.content).sort();
const fullKeys = Object.keys(fullItem.content).sort();
if (currentKeys.length !== fullKeys.length ||
!currentKeys.every((key, i) => key === fullKeys[i])) {
contentChanged = true;
} else {
for (const key of currentKeys) {
if (currentItem.content[key] !== fullItem.content[key]) {
contentChanged = true;
break;
}
}
}
}
}
if (!hasChanged && !contentChanged) {
if (!hasChanged) {
console.log('✅ Realtime UPDATE: No changes detected for', fullItem.id);
return prev; // Keep existing array reference - PREVENTS RE-RENDER
return prev; // Keep existing array reference
}
console.log('🔄 Realtime UPDATE: Changes detected for', fullItem.id);
// Update ONLY changed fields to preserve object stability
return prev.map(i => {
if (i.id !== fullItem.id) return i;
// Create minimal update object with only changed fields
const updates: Partial<ModerationItem> = {};
if (i.status !== fullItem.status) updates.status = fullItem.status;
if (i.reviewed_at !== fullItem.reviewed_at) updates.reviewed_at = fullItem.reviewed_at;
if (i.reviewer_notes !== fullItem.reviewer_notes) updates.reviewer_notes = fullItem.reviewer_notes;
if (i.assigned_to !== fullItem.assigned_to) updates.assigned_to = fullItem.assigned_to;
if (i.locked_until !== fullItem.locked_until) updates.locked_until = fullItem.locked_until;
if (i.escalated !== fullItem.escalated) updates.escalated = fullItem.escalated;
if (contentChanged) updates.content = fullItem.content;
if (fullItem.submission_items) updates.submission_items = fullItem.submission_items;
// Only create new object if there are actual updates
return Object.keys(updates).length > 0 ? { ...i, ...updates } : i;
});
const newItems = prev.map(i => i.id === fullItem.id ? fullItem : i);
itemsRef.current = newItems; // Update ref immediately
return newItems;
} else {
console.log('🆕 Realtime UPDATE: Adding new item', fullItem.id);
return [fullItem, ...prev];
const newItems = [fullItem, ...prev];
itemsRef.current = newItems; // Update ref immediately
return newItems;
}
});
} catch (error) {
@@ -2310,13 +2205,23 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
variant="default"
size="sm"
onClick={() => {
// Instant merge without loading state
// Smooth merge with loading state
if (pendingNewItems.length > 0) {
setItems(prev => [...pendingNewItems, ...prev]);
setPendingNewItems([]);
setNewItemsCount(0);
console.log('✅ New items merged into queue:', pendingNewItems.length);
setLoadingState('loading');
// After 150ms, merge items
setTimeout(() => {
setItems(prev => [...pendingNewItems, ...prev]);
setPendingNewItems([]);
setNewItemsCount(0);
// Show content again after brief pause
setTimeout(() => {
setLoadingState('ready');
}, 100);
}, 150);
}
console.log('✅ New items merged into queue');
}}
className="ml-4"
>