mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 18:31:12 -05:00
Refactor: Simplify architecture
This commit is contained in:
@@ -105,12 +105,12 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
||||
<QueueFilters
|
||||
activeEntityFilter={queueManager.filters.entityFilter}
|
||||
activeStatusFilter={queueManager.filters.statusFilter}
|
||||
sortConfig={queueManager.sort.config}
|
||||
sortConfig={queueManager.filters.sortConfig}
|
||||
isMobile={isMobile}
|
||||
isLoading={queueManager.loadingState === 'loading'}
|
||||
onEntityFilterChange={queueManager.filters.setEntityFilter}
|
||||
onStatusFilterChange={queueManager.filters.setStatusFilter}
|
||||
onSortChange={queueManager.sort.setConfig}
|
||||
onSortChange={queueManager.filters.setSortConfig}
|
||||
onClearFilters={queueManager.filters.clearFilters}
|
||||
showClearButton={queueManager.filters.hasActiveFilters}
|
||||
/>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,10 +163,16 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode
|
||||
persisted?.activeTab || initialTab
|
||||
);
|
||||
|
||||
// Sort state
|
||||
const [sortConfig, setSortConfigState] = useState<SortConfig>(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(() => {
|
||||
if (persist) {
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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<typeof useModerationFilters>;
|
||||
pagination: ReturnType<typeof usePagination>;
|
||||
sort: ReturnType<typeof useModerationSort>;
|
||||
queue: ReturnType<typeof useModerationQueue>;
|
||||
|
||||
// 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,24 +461,9 @@ 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);
|
||||
// Full replace - simple and predictable
|
||||
setItems(moderationItems);
|
||||
console.log("🔄 Queue updated (replace mode)");
|
||||
}
|
||||
|
||||
if (!currentPreserveInteraction) {
|
||||
setPendingNewItems([]);
|
||||
@@ -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,
|
||||
|
||||
@@ -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<SortConfig>(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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user