From 2bd0b414e148d9df9309965ebcb1d165857ccd4d Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:27:58 +0000 Subject: [PATCH] Refactor: Simplify architecture --- src/components/moderation/ModerationQueue.tsx | 4 +- src/hooks/moderation/index.ts | 5 - src/hooks/moderation/useModerationFilters.ts | 105 +++++++++++++++- .../moderation/useModerationQueueManager.ts | 54 ++------- src/hooks/moderation/useModerationSort.ts | 113 ------------------ 5 files changed, 117 insertions(+), 164 deletions(-) delete mode 100644 src/hooks/moderation/useModerationSort.ts diff --git a/src/components/moderation/ModerationQueue.tsx b/src/components/moderation/ModerationQueue.tsx index 53986036..dfeac859 100644 --- a/src/components/moderation/ModerationQueue.tsx +++ b/src/components/moderation/ModerationQueue.tsx @@ -105,12 +105,12 @@ export const ModerationQueue = forwardRef((props, ref) => { diff --git a/src/hooks/moderation/index.ts b/src/hooks/moderation/index.ts index 59baf489..9b69b2d0 100644 --- a/src/hooks/moderation/index.ts +++ b/src/hooks/moderation/index.ts @@ -12,14 +12,9 @@ export type { CachedProfile } from './useProfileCache'; export { useModerationFilters } from './useModerationFilters'; export type { ModerationFilters, ModerationFiltersConfig } from './useModerationFilters'; -// Removed - sorting functionality deleted - export { usePagination } from './usePagination'; export type { PaginationState, PaginationConfig } from './usePagination'; -export { useModerationSort } from './useModerationSort'; -export type { UseModerationSortReturn } from './useModerationSort'; - export { useRealtimeSubscriptions } from './useRealtimeSubscriptions'; export type { RealtimeSubscriptionConfig, diff --git a/src/hooks/moderation/useModerationFilters.ts b/src/hooks/moderation/useModerationFilters.ts index c0dc110f..44d8db5a 100644 --- a/src/hooks/moderation/useModerationFilters.ts +++ b/src/hooks/moderation/useModerationFilters.ts @@ -10,7 +10,7 @@ import { useState, useCallback, useEffect, useMemo } from 'react'; import { useDebounce } from '@/hooks/useDebounce'; -import type { EntityFilter, StatusFilter, QueueTab } from '@/types/moderation'; +import type { EntityFilter, StatusFilter, QueueTab, SortConfig, SortField } from '@/types/moderation'; export interface ModerationFiltersConfig { /** Initial entity filter */ @@ -30,6 +30,9 @@ export interface ModerationFiltersConfig { /** localStorage key prefix for persistence */ storageKey?: string; + + /** Initial sort configuration */ + initialSortConfig?: SortConfig; } export interface ModerationFilters { @@ -62,6 +65,24 @@ export interface ModerationFilters { /** Check if any non-default filters are active */ hasActiveFilters: boolean; + + /** Current sort configuration (immediate) */ + sortConfig: SortConfig; + + /** Debounced sort configuration (use this for queries) */ + debouncedSortConfig: SortConfig; + + /** Update the sort configuration */ + setSortConfig: (config: SortConfig) => void; + + /** Sort by a specific field, toggling direction if already sorting by that field */ + sortBy: (field: SortField) => void; + + /** Toggle the sort direction */ + toggleSortDirection: () => void; + + /** Reset sort to default */ + resetSort: () => void; } /** @@ -91,6 +112,7 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode debounceDelay = 300, persist = true, storageKey = 'moderationQueue_filters', + initialSortConfig = { field: 'created_at', direction: 'asc' }, } = config; // Load persisted filters on mount @@ -109,6 +131,25 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode return null; }, [persist, storageKey]); + // Load persisted sort + const loadPersistedSort = useCallback((): SortConfig => { + if (!persist) return initialSortConfig; + + try { + const saved = localStorage.getItem(`${storageKey}_sort`); + if (saved) { + const parsed = JSON.parse(saved); + if (parsed.field && parsed.direction) { + return parsed; + } + } + } catch (error) { + console.error('Failed to load persisted sort:', error); + } + + return initialSortConfig; + }, [persist, storageKey, initialSortConfig]); + const persisted = loadPersistedFilters(); // Filter state @@ -122,9 +163,15 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode persisted?.activeTab || initialTab ); + // Sort state + const [sortConfig, setSortConfigState] = useState(loadPersistedSort); + // Debounced filters for API calls const debouncedEntityFilter = useDebounce(entityFilter, debounceDelay); const debouncedStatusFilter = useDebounce(statusFilter, debounceDelay); + + // Debounced sort (0ms for immediate feedback) + const debouncedSortConfig = useDebounce(sortConfig, 0); // Persist filters to localStorage useEffect(() => { @@ -144,6 +191,17 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode } }, [entityFilter, statusFilter, activeTab, persist, storageKey]); + // Persist sort to localStorage + useEffect(() => { + if (persist) { + try { + localStorage.setItem(`${storageKey}_sort`, JSON.stringify(sortConfig)); + } catch (error) { + console.error('Failed to persist sort:', error); + } + } + }, [sortConfig, persist, storageKey]); + // Set entity filter with logging const setEntityFilter = useCallback((filter: EntityFilter) => { console.log('🔍 Entity filter changed:', filter); @@ -162,19 +220,48 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode setActiveTabState(tab); }, []); + // Sort callbacks + const setSortConfig = useCallback((config: SortConfig) => { + console.log('📝 [SORT] Sort config changed:', config); + setSortConfigState(config); + }, []); + + const sortBy = useCallback((field: SortField) => { + setSortConfigState(prev => ({ + field, + direction: prev.field === field + ? (prev.direction === 'asc' ? 'desc' : 'asc') + : 'asc' + })); + }, []); + + const toggleSortDirection = useCallback(() => { + setSortConfigState(prev => ({ + ...prev, + direction: prev.direction === 'asc' ? 'desc' : 'asc' + })); + }, []); + + const resetSort = useCallback(() => { + setSortConfigState(initialSortConfig); + }, [initialSortConfig]); + // Clear all filters const clearFilters = useCallback(() => { console.log('🔍 Filters cleared'); setEntityFilterState(initialEntityFilter); setStatusFilterState(initialStatusFilter); setActiveTabState(initialTab); - }, [initialEntityFilter, initialStatusFilter, initialTab]); + setSortConfigState(initialSortConfig); + }, [initialEntityFilter, initialStatusFilter, initialTab, initialSortConfig]); // Check if non-default filters are active const hasActiveFilters = entityFilter !== initialEntityFilter || statusFilter !== initialStatusFilter || - activeTab !== initialTab; + activeTab !== initialTab || + sortConfig.field !== initialSortConfig.field || + sortConfig.direction !== initialSortConfig.direction; return useMemo(() => ({ entityFilter, @@ -187,6 +274,12 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode setActiveTab, clearFilters, hasActiveFilters, + sortConfig, + debouncedSortConfig, + setSortConfig, + sortBy, + toggleSortDirection, + resetSort, }), [ entityFilter, statusFilter, @@ -198,5 +291,11 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode setActiveTab, clearFilters, hasActiveFilters, + sortConfig, + debouncedSortConfig, + setSortConfig, + sortBy, + toggleSortDirection, + resetSort, ]); } diff --git a/src/hooks/moderation/useModerationQueueManager.ts b/src/hooks/moderation/useModerationQueueManager.ts index ceceb06a..0e3df0fe 100644 --- a/src/hooks/moderation/useModerationQueueManager.ts +++ b/src/hooks/moderation/useModerationQueueManager.ts @@ -7,11 +7,10 @@ import { useProfileCache, useModerationFilters, usePagination, - useModerationSort, useRealtimeSubscriptions, } from "./index"; import { useModerationQueue } from "@/hooks/useModerationQueue"; -import { smartMergeArray } from "@/lib/smartStateUpdate"; + import type { ModerationItem, EntityFilter, StatusFilter, LoadingState } from "@/types/moderation"; /** @@ -43,7 +42,6 @@ export interface ModerationQueueManager { // Sub-hooks (exposed for granular control) filters: ReturnType; pagination: ReturnType; - sort: ReturnType; queue: ReturnType; // Realtime @@ -110,8 +108,6 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): }, }); - const sort = useModerationSort(); - const queue = useModerationQueue(); const entityCache = useEntityCache(); const profileCache = useProfileCache(); @@ -133,10 +129,10 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): const FETCH_COOLDOWN_MS = 1000; - // Store settings, filters, sort, and pagination in refs to stabilize fetchItems + // Store settings, filters, and pagination in refs to stabilize fetchItems const settingsRef = useRef(settings); const filtersRef = useRef(filters); - const sortRef = useRef(sort.debouncedConfig); + const sortRef = useRef(filters.debouncedSortConfig); const paginationRef = useRef(pagination); // Sync refs with state @@ -149,8 +145,8 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): }, [filters]); useEffect(() => { - sortRef.current = sort.debouncedConfig; - }, [sort.debouncedConfig]); + sortRef.current = filters.debouncedSortConfig; + }, [filters.debouncedSortConfig]); useEffect(() => { paginationRef.current = pagination; @@ -465,25 +461,10 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): break; case "replace": - const mergeResult = smartMergeArray(items, moderationItems, { - compareFields: [ - "status", - "content", - "reviewed_at", - "reviewed_by", - "reviewer_notes", - "assigned_to", - "locked_until", - ], - preserveOrder: false, - addToTop: false, - }); - - if (mergeResult.hasChanges) { - setItems(mergeResult.items); - console.log("🔄 Queue updated (replace mode)"); - } - + // Full replace - simple and predictable + setItems(moderationItems); + console.log("🔄 Queue updated (replace mode)"); + if (!currentPreserveInteraction) { setPendingNewItems([]); setNewItemsCount(0); @@ -515,14 +496,6 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): user, isAdmin, isSuperuser, - filters.debouncedEntityFilter, - filters.debouncedStatusFilter, - filters.activeTab, - pagination.setTotalCount, - pagination.startIndex, - pagination.endIndex, - sort.debouncedConfig.field, - sort.debouncedConfig.direction, profileCache, entityCache, toast @@ -856,8 +829,8 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): console.log('🔄 [SORT/FILTER CHANGE] Detected change:', { entityFilter: filters.debouncedEntityFilter, statusFilter: filters.debouncedStatusFilter, - sortField: sort.debouncedConfig.field, - sortDirection: sort.debouncedConfig.direction, + sortField: filters.debouncedSortConfig.field, + sortDirection: filters.debouncedSortConfig.direction, timestamp: new Date().toISOString() }); @@ -866,8 +839,8 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): }, [ filters.debouncedEntityFilter, filters.debouncedStatusFilter, - sort.debouncedConfig.field, - sort.debouncedConfig.direction, + filters.debouncedSortConfig.field, + filters.debouncedSortConfig.direction, user, fetchItems, pagination @@ -948,7 +921,6 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): actionLoading, filters, pagination, - sort, queue, newItemsCount, pendingNewItems, diff --git a/src/hooks/moderation/useModerationSort.ts b/src/hooks/moderation/useModerationSort.ts deleted file mode 100644 index d78fda52..00000000 --- a/src/hooks/moderation/useModerationSort.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { useState, useCallback, useEffect } from 'react'; -import { useDebounce } from '@/hooks/useDebounce'; -import type { SortConfig, SortField } from '@/types/moderation'; - -const STORAGE_KEY = 'moderationQueue_sortConfig'; -const SORT_DEBOUNCE_MS = 0; // Changed from 300ms to 0ms for immediate feedback - -/** - * Default sort configuration - * Sorts by creation date ascending (oldest first) - */ -const DEFAULT_SORT: SortConfig = { - field: 'created_at', - direction: 'asc' -}; - -/** - * Load sort configuration from localStorage - */ -function loadSortConfig(): SortConfig { - try { - const stored = localStorage.getItem(STORAGE_KEY); - if (stored) { - const parsed = JSON.parse(stored); - // Validate structure - if (parsed.field && parsed.direction) { - return parsed; - } - } - } catch (error) { - console.warn('Failed to load sort config:', error); - } - return DEFAULT_SORT; -} - -/** - * Save sort configuration to localStorage - */ -function saveSortConfig(config: SortConfig): void { - try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(config)); - } catch (error) { - console.warn('Failed to save sort config:', error); - } -} - -export interface UseModerationSortReturn { - /** Current sort configuration (immediate) */ - config: SortConfig; - /** Debounced sort configuration (use this for queries) */ - debouncedConfig: SortConfig; - /** Update the sort configuration */ - setConfig: (config: SortConfig) => void; - /** Sort by a specific field, toggling direction if already sorting by that field */ - sortBy: (field: SortField) => void; - /** Toggle the sort direction */ - toggleDirection: () => void; - /** Reset to default sort */ - reset: () => void; -} - -/** - * Hook for managing moderation queue sort state - * - * Provides sort configuration with localStorage persistence - * and convenient methods for updating sort settings. - */ -export function useModerationSort(): UseModerationSortReturn { - const [config, setConfigState] = useState(loadSortConfig); - - // Debounce the config to prevent rapid-fire fetches - const debouncedConfig = useDebounce(config, SORT_DEBOUNCE_MS); - - // Persist to localStorage whenever config changes - useEffect(() => { - saveSortConfig(config); - }, [config]); - - const setConfig = useCallback((newConfig: SortConfig) => { - console.log('📝 [SORT] setConfig called:', newConfig); - setConfigState(newConfig); - }, []); - - const sortBy = useCallback((field: SortField) => { - setConfigState(prev => ({ - field, - // Toggle direction if clicking the same field, otherwise default to ascending - direction: prev.field === field - ? (prev.direction === 'asc' ? 'desc' : 'asc') - : 'asc' - })); - }, []); - - const toggleDirection = useCallback(() => { - setConfigState(prev => ({ - ...prev, - direction: prev.direction === 'asc' ? 'desc' : 'asc' - })); - }, []); - - const reset = useCallback(() => { - setConfigState(DEFAULT_SORT); - }, []); - - return { - config, - debouncedConfig, - setConfig, - sortBy, - toggleDirection, - reset - }; -}