mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 04:51:13 -05:00
Transitioned from Plan to Build mode
Replit-Commit-Author: Agent Replit-Commit-Session-Id: e14c2292-b0e5-43fe-b301-a4ad668949e9 Replit-Commit-Checkpoint-Type: full_checkpoint
This commit is contained in:
@@ -97,6 +97,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const queue = useModerationQueue();
|
const queue = useModerationQueue();
|
||||||
const fetchInProgressRef = useRef(false);
|
const fetchInProgressRef = useRef(false);
|
||||||
|
const itemsRef = useRef<ModerationItem[]>([]);
|
||||||
|
|
||||||
// Get admin settings for polling configuration
|
// Get admin settings for polling configuration
|
||||||
const {
|
const {
|
||||||
@@ -110,6 +111,11 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
const refreshStrategy = getAutoRefreshStrategy();
|
const refreshStrategy = getAutoRefreshStrategy();
|
||||||
const preserveInteraction = getPreserveInteractionState();
|
const preserveInteraction = getPreserveInteractionState();
|
||||||
|
|
||||||
|
// Sync itemsRef with items state
|
||||||
|
useEffect(() => {
|
||||||
|
itemsRef.current = items;
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
const fetchItems = useCallback(async (entityFilter: EntityFilter = 'all', statusFilter: StatusFilter = 'pending', silent = false) => {
|
const fetchItems = useCallback(async (entityFilter: EntityFilter = 'all', statusFilter: StatusFilter = 'pending', silent = false) => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return;
|
return;
|
||||||
@@ -378,8 +384,8 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
});
|
});
|
||||||
setSubmissionMemo(newMemoMap);
|
setSubmissionMemo(newMemoMap);
|
||||||
|
|
||||||
// Apply smart merge for state updates
|
// Apply smart merge for state updates using ref for current items
|
||||||
const mergeResult = smartMergeArray(items, moderationItems, {
|
const mergeResult = smartMergeArray(itemsRef.current, moderationItems, {
|
||||||
compareFields: ['status', 'reviewed_at', 'reviewer_notes'],
|
compareFields: ['status', 'reviewed_at', 'reviewer_notes'],
|
||||||
preserveOrder: silent && preserveInteraction,
|
preserveOrder: silent && preserveInteraction,
|
||||||
addToTop: false,
|
addToTop: false,
|
||||||
@@ -413,7 +419,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
user,
|
user,
|
||||||
refreshStrategy,
|
refreshStrategy,
|
||||||
preserveInteraction,
|
preserveInteraction,
|
||||||
interactingWith,
|
|
||||||
toast
|
toast
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -421,32 +426,40 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
const debouncedEntityFilter = useDebounce(activeEntityFilter, 500);
|
const debouncedEntityFilter = useDebounce(activeEntityFilter, 500);
|
||||||
const debouncedStatusFilter = useDebounce(activeStatusFilter, 500);
|
const debouncedStatusFilter = useDebounce(activeStatusFilter, 500);
|
||||||
|
|
||||||
|
// Store latest filter values in ref to avoid dependency issues
|
||||||
|
const filtersRef = useRef({ entityFilter: debouncedEntityFilter, statusFilter: debouncedStatusFilter });
|
||||||
|
useEffect(() => {
|
||||||
|
filtersRef.current = { entityFilter: debouncedEntityFilter, statusFilter: debouncedStatusFilter };
|
||||||
|
}, [debouncedEntityFilter, debouncedStatusFilter]);
|
||||||
|
|
||||||
// Expose refresh method via ref
|
// Expose refresh method via ref
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
refresh: () => {
|
refresh: () => {
|
||||||
fetchItems(debouncedEntityFilter, debouncedStatusFilter, false); // Manual refresh shows loading
|
fetchItems(filtersRef.current.entityFilter, filtersRef.current.statusFilter, false); // Manual refresh shows loading
|
||||||
}
|
}
|
||||||
}), [debouncedEntityFilter, debouncedStatusFilter, fetchItems]);
|
}), [fetchItems]);
|
||||||
|
|
||||||
// Initial fetch on mount and filter changes
|
// Initial fetch on mount and filter changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
fetchItems(debouncedEntityFilter, debouncedStatusFilter, false); // Show loading
|
fetchItems(debouncedEntityFilter, debouncedStatusFilter, false); // Show loading
|
||||||
}
|
}
|
||||||
}, [debouncedEntityFilter, debouncedStatusFilter, user, fetchItems]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [debouncedEntityFilter, debouncedStatusFilter, user]);
|
||||||
|
|
||||||
// Polling for auto-refresh
|
// Polling for auto-refresh
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user || refreshMode !== 'auto' || isInitialLoad) return;
|
if (!user || refreshMode !== 'auto' || isInitialLoad) return;
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
fetchItems(debouncedEntityFilter, debouncedStatusFilter, true); // Silent refresh
|
fetchItems(filtersRef.current.entityFilter, filtersRef.current.statusFilter, true); // Silent refresh
|
||||||
}, pollInterval);
|
}, pollInterval);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
};
|
};
|
||||||
}, [user, refreshMode, pollInterval, debouncedEntityFilter, debouncedStatusFilter, isInitialLoad, fetchItems]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [user, refreshMode, pollInterval, isInitialLoad]);
|
||||||
|
|
||||||
// Real-time subscription for lock status
|
// Real-time subscription for lock status
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1623,6 +1636,12 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
placeholder="Add notes about your moderation decision..."
|
placeholder="Add notes about your moderation decision..."
|
||||||
value={notes[item.id] || ''}
|
value={notes[item.id] || ''}
|
||||||
onChange={(e) => setNotes(prev => ({ ...prev, [item.id]: e.target.value }))}
|
onChange={(e) => setNotes(prev => ({ ...prev, [item.id]: e.target.value }))}
|
||||||
|
onFocus={() => setInteractingWith(prev => new Set(prev).add(item.id))}
|
||||||
|
onBlur={() => setInteractingWith(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
next.delete(item.id);
|
||||||
|
return next;
|
||||||
|
})}
|
||||||
rows={2}
|
rows={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1772,6 +1791,12 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
placeholder="Add notes about reversing this decision..."
|
placeholder="Add notes about reversing this decision..."
|
||||||
value={notes[`reverse-${item.id}`] || ''}
|
value={notes[`reverse-${item.id}`] || ''}
|
||||||
onChange={(e) => setNotes(prev => ({ ...prev, [`reverse-${item.id}`]: e.target.value }))}
|
onChange={(e) => setNotes(prev => ({ ...prev, [`reverse-${item.id}`]: e.target.value }))}
|
||||||
|
onFocus={() => setInteractingWith(prev => new Set(prev).add(item.id))}
|
||||||
|
onBlur={() => setInteractingWith(prev => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
next.delete(item.id);
|
||||||
|
return next;
|
||||||
|
})}
|
||||||
rows={2}
|
rows={2}
|
||||||
/>
|
/>
|
||||||
<div className={`flex gap-2 ${isMobile ? 'flex-col' : ''}`}>
|
<div className={`flex gap-2 ${isMobile ? 'flex-col' : ''}`}>
|
||||||
|
|||||||
Reference in New Issue
Block a user