mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 19:51:13 -05:00
feat: Implement database-level sorting for moderation queue
This commit is contained in:
@@ -17,6 +17,9 @@ export type { ModerationFilters, ModerationFiltersConfig } from './useModeration
|
||||
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,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
useProfileCache,
|
||||
useModerationFilters,
|
||||
usePagination,
|
||||
useModerationSort,
|
||||
useRealtimeSubscriptions,
|
||||
} from "./index";
|
||||
import { useModerationQueue } from "@/hooks/useModerationQueue";
|
||||
@@ -43,6 +44,7 @@ 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
|
||||
@@ -97,7 +99,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
},
|
||||
});
|
||||
|
||||
// Removed - sorting functionality deleted
|
||||
const sort = useModerationSort();
|
||||
|
||||
const queue = useModerationQueue();
|
||||
const entityCache = useEntityCache();
|
||||
@@ -217,6 +219,21 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
`,
|
||||
);
|
||||
|
||||
// CRITICAL: Multi-level ordering
|
||||
// Level 1: Always sort by escalated first (descending)
|
||||
submissionsQuery = submissionsQuery.order('escalated', { ascending: false });
|
||||
|
||||
// Level 2: Apply user-selected sort
|
||||
submissionsQuery = submissionsQuery.order(
|
||||
sort.config.field,
|
||||
{ ascending: sort.config.direction === 'asc' }
|
||||
);
|
||||
|
||||
// Level 3: Tertiary sort by created_at (if not already primary)
|
||||
if (sort.config.field !== 'created_at') {
|
||||
submissionsQuery = submissionsQuery.order('created_at', { ascending: true });
|
||||
}
|
||||
|
||||
// Apply tab-based status filtering
|
||||
const tab = filters.activeTab;
|
||||
const statusFilter = filters.debouncedStatusFilter;
|
||||
@@ -444,7 +461,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
setLoadingState("ready");
|
||||
}
|
||||
},
|
||||
[user, isAdmin, isSuperuser, filters, pagination, profileCache, entityCache, toast],
|
||||
[user, isAdmin, isSuperuser, filters, pagination, sort, profileCache, entityCache, toast],
|
||||
);
|
||||
|
||||
// Store fetchItems in ref to avoid re-creating visibility listener
|
||||
@@ -772,13 +789,13 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [user?.id]);
|
||||
|
||||
// Filter changes trigger refetch
|
||||
// Filter and sort changes trigger refetch
|
||||
useEffect(() => {
|
||||
if (!user || !initialFetchCompleteRef.current || isMountingRef.current) return;
|
||||
|
||||
pagination.reset();
|
||||
fetchItems(true);
|
||||
}, [filters.debouncedEntityFilter, filters.debouncedStatusFilter]);
|
||||
}, [filters.debouncedEntityFilter, filters.debouncedStatusFilter, sort.config]);
|
||||
|
||||
// Pagination changes trigger refetch
|
||||
useEffect(() => {
|
||||
@@ -921,6 +938,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
actionLoading,
|
||||
filters,
|
||||
pagination,
|
||||
sort,
|
||||
queue,
|
||||
newItemsCount,
|
||||
pendingNewItems,
|
||||
|
||||
104
src/hooks/moderation/useModerationSort.ts
Normal file
104
src/hooks/moderation/useModerationSort.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import type { SortConfig, SortField } from '@/types/moderation';
|
||||
|
||||
const STORAGE_KEY = 'moderationQueue_sortConfig';
|
||||
|
||||
/**
|
||||
* 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 */
|
||||
config: 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);
|
||||
|
||||
// Persist to localStorage whenever config changes
|
||||
useEffect(() => {
|
||||
saveSortConfig(config);
|
||||
}, [config]);
|
||||
|
||||
const setConfig = useCallback((newConfig: SortConfig) => {
|
||||
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,
|
||||
setConfig,
|
||||
sortBy,
|
||||
toggleDirection,
|
||||
reset
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user