mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 08:51:13 -05:00
feat: Extract moderation filter and sort logic
This commit is contained in:
@@ -8,3 +8,12 @@
|
||||
export { useEntityCache } from './useEntityCache';
|
||||
export { useProfileCache } from './useProfileCache';
|
||||
export type { CachedProfile } from './useProfileCache';
|
||||
|
||||
export { useModerationFilters } from './useModerationFilters';
|
||||
export type { ModerationFilters, ModerationFiltersConfig } from './useModerationFilters';
|
||||
|
||||
export { useModerationSort } from './useModerationSort';
|
||||
export type { ModerationSort, ModerationSortConfig } from './useModerationSort';
|
||||
|
||||
export { usePagination } from './usePagination';
|
||||
export type { PaginationState, PaginationConfig } from './usePagination';
|
||||
|
||||
191
src/hooks/moderation/useModerationFilters.ts
Normal file
191
src/hooks/moderation/useModerationFilters.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* Moderation Queue Filters Hook
|
||||
*
|
||||
* Manages filter state for the moderation queue, including:
|
||||
* - Entity type filtering (all, reviews, submissions, photos)
|
||||
* - Status filtering (pending, approved, rejected, etc.)
|
||||
* - Tab management (main queue vs archive)
|
||||
* - Filter persistence and clearing
|
||||
*/
|
||||
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useDebounce } from '@/hooks/useDebounce';
|
||||
import type { EntityFilter, StatusFilter, QueueTab } from '@/types/moderation';
|
||||
|
||||
export interface ModerationFiltersConfig {
|
||||
/** Initial entity filter */
|
||||
initialEntityFilter?: EntityFilter;
|
||||
|
||||
/** Initial status filter */
|
||||
initialStatusFilter?: StatusFilter;
|
||||
|
||||
/** Initial active tab */
|
||||
initialTab?: QueueTab;
|
||||
|
||||
/** Debounce delay for filter changes (ms) */
|
||||
debounceDelay?: number;
|
||||
|
||||
/** Whether to persist filters to localStorage */
|
||||
persist?: boolean;
|
||||
|
||||
/** localStorage key prefix for persistence */
|
||||
storageKey?: string;
|
||||
}
|
||||
|
||||
export interface ModerationFilters {
|
||||
/** Current entity type filter */
|
||||
entityFilter: EntityFilter;
|
||||
|
||||
/** Current status filter */
|
||||
statusFilter: StatusFilter;
|
||||
|
||||
/** Current active tab */
|
||||
activeTab: QueueTab;
|
||||
|
||||
/** Debounced entity filter (for API calls) */
|
||||
debouncedEntityFilter: EntityFilter;
|
||||
|
||||
/** Debounced status filter (for API calls) */
|
||||
debouncedStatusFilter: StatusFilter;
|
||||
|
||||
/** Set entity filter */
|
||||
setEntityFilter: (filter: EntityFilter) => void;
|
||||
|
||||
/** Set status filter */
|
||||
setStatusFilter: (filter: StatusFilter) => void;
|
||||
|
||||
/** Set active tab */
|
||||
setActiveTab: (tab: QueueTab) => void;
|
||||
|
||||
/** Reset all filters to defaults */
|
||||
clearFilters: () => void;
|
||||
|
||||
/** Check if any non-default filters are active */
|
||||
hasActiveFilters: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing moderation queue filters
|
||||
*
|
||||
* @param config - Configuration options
|
||||
* @returns Filter state and actions
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const filters = useModerationFilters({
|
||||
* persist: true,
|
||||
* debounceDelay: 300
|
||||
* });
|
||||
*
|
||||
* // Use in component
|
||||
* <Select value={filters.entityFilter} onValueChange={filters.setEntityFilter}>
|
||||
* ...
|
||||
* </Select>
|
||||
* ```
|
||||
*/
|
||||
export function useModerationFilters(config: ModerationFiltersConfig = {}): ModerationFilters {
|
||||
const {
|
||||
initialEntityFilter = 'all',
|
||||
initialStatusFilter = 'pending',
|
||||
initialTab = 'mainQueue',
|
||||
debounceDelay = 300,
|
||||
persist = true,
|
||||
storageKey = 'moderationQueue_filters',
|
||||
} = config;
|
||||
|
||||
// Load persisted filters on mount
|
||||
const loadPersistedFilters = useCallback(() => {
|
||||
if (!persist) return null;
|
||||
|
||||
try {
|
||||
const saved = localStorage.getItem(storageKey);
|
||||
if (saved) {
|
||||
return JSON.parse(saved);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load persisted filters:', error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [persist, storageKey]);
|
||||
|
||||
const persisted = loadPersistedFilters();
|
||||
|
||||
// Filter state
|
||||
const [entityFilter, setEntityFilterState] = useState<EntityFilter>(
|
||||
persisted?.entityFilter || initialEntityFilter
|
||||
);
|
||||
const [statusFilter, setStatusFilterState] = useState<StatusFilter>(
|
||||
persisted?.statusFilter || initialStatusFilter
|
||||
);
|
||||
const [activeTab, setActiveTabState] = useState<QueueTab>(
|
||||
persisted?.activeTab || initialTab
|
||||
);
|
||||
|
||||
// Debounced filters for API calls
|
||||
const debouncedEntityFilter = useDebounce(entityFilter, debounceDelay);
|
||||
const debouncedStatusFilter = useDebounce(statusFilter, debounceDelay);
|
||||
|
||||
// Persist filters to localStorage
|
||||
useEffect(() => {
|
||||
if (persist) {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
storageKey,
|
||||
JSON.stringify({
|
||||
entityFilter,
|
||||
statusFilter,
|
||||
activeTab,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to persist filters:', error);
|
||||
}
|
||||
}
|
||||
}, [entityFilter, statusFilter, activeTab, persist, storageKey]);
|
||||
|
||||
// Set entity filter with logging
|
||||
const setEntityFilter = useCallback((filter: EntityFilter) => {
|
||||
console.log('🔍 Entity filter changed:', filter);
|
||||
setEntityFilterState(filter);
|
||||
}, []);
|
||||
|
||||
// Set status filter with logging
|
||||
const setStatusFilter = useCallback((filter: StatusFilter) => {
|
||||
console.log('🔍 Status filter changed:', filter);
|
||||
setStatusFilterState(filter);
|
||||
}, []);
|
||||
|
||||
// Set active tab with logging
|
||||
const setActiveTab = useCallback((tab: QueueTab) => {
|
||||
console.log('🔍 Tab changed:', tab);
|
||||
setActiveTabState(tab);
|
||||
}, []);
|
||||
|
||||
// Clear all filters
|
||||
const clearFilters = useCallback(() => {
|
||||
console.log('🔍 Filters cleared');
|
||||
setEntityFilterState(initialEntityFilter);
|
||||
setStatusFilterState(initialStatusFilter);
|
||||
setActiveTabState(initialTab);
|
||||
}, [initialEntityFilter, initialStatusFilter, initialTab]);
|
||||
|
||||
// Check if non-default filters are active
|
||||
const hasActiveFilters =
|
||||
entityFilter !== initialEntityFilter ||
|
||||
statusFilter !== initialStatusFilter ||
|
||||
activeTab !== initialTab;
|
||||
|
||||
return {
|
||||
entityFilter,
|
||||
statusFilter,
|
||||
activeTab,
|
||||
debouncedEntityFilter,
|
||||
debouncedStatusFilter,
|
||||
setEntityFilter,
|
||||
setStatusFilter,
|
||||
setActiveTab,
|
||||
clearFilters,
|
||||
hasActiveFilters,
|
||||
};
|
||||
}
|
||||
145
src/hooks/moderation/useModerationSort.ts
Normal file
145
src/hooks/moderation/useModerationSort.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Moderation Queue Sort Hook
|
||||
*
|
||||
* Manages sort configuration for the moderation queue with persistence.
|
||||
*/
|
||||
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import type { SortConfig, SortField, SortDirection } from '@/types/moderation';
|
||||
import {
|
||||
getDefaultSortConfig,
|
||||
loadSortConfig,
|
||||
saveSortConfig,
|
||||
toggleSortDirection as toggleDirection,
|
||||
isDefaultSortConfig,
|
||||
} from '@/lib/moderation/sorting';
|
||||
|
||||
export interface ModerationSortConfig {
|
||||
/** Initial sort configuration */
|
||||
initialConfig?: SortConfig;
|
||||
|
||||
/** Whether to persist sort config */
|
||||
persist?: boolean;
|
||||
|
||||
/** localStorage key for persistence */
|
||||
storageKey?: string;
|
||||
|
||||
/** Callback when sort config changes */
|
||||
onChange?: (config: SortConfig) => void;
|
||||
}
|
||||
|
||||
export interface ModerationSort {
|
||||
/** Current sort configuration */
|
||||
config: SortConfig;
|
||||
|
||||
/** Sort field */
|
||||
field: SortField;
|
||||
|
||||
/** Sort direction */
|
||||
direction: SortDirection;
|
||||
|
||||
/** Set sort field */
|
||||
setField: (field: SortField) => void;
|
||||
|
||||
/** Set sort direction */
|
||||
setDirection: (direction: SortDirection) => void;
|
||||
|
||||
/** Toggle sort direction */
|
||||
toggleDirection: () => void;
|
||||
|
||||
/** Set both field and direction */
|
||||
setConfig: (config: SortConfig) => void;
|
||||
|
||||
/** Reset to default */
|
||||
reset: () => void;
|
||||
|
||||
/** Check if using default config */
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing moderation queue sort configuration
|
||||
*
|
||||
* @param config - Configuration options
|
||||
* @returns Sort state and actions
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const sort = useModerationSort({
|
||||
* persist: true,
|
||||
* onChange: (config) => fetchItems(config)
|
||||
* });
|
||||
*
|
||||
* // Use in component
|
||||
* <Select value={sort.field} onValueChange={sort.setField}>
|
||||
* <SelectItem value="created_at">Date</SelectItem>
|
||||
* <SelectItem value="username">User</SelectItem>
|
||||
* </Select>
|
||||
* ```
|
||||
*/
|
||||
export function useModerationSort(config: ModerationSortConfig = {}): ModerationSort {
|
||||
const {
|
||||
initialConfig,
|
||||
persist = true,
|
||||
storageKey = 'moderationQueue_sortConfig',
|
||||
onChange,
|
||||
} = config;
|
||||
|
||||
// Load persisted or use initial/default config
|
||||
const [sortConfig, setSortConfig] = useState<SortConfig>(() => {
|
||||
if (initialConfig) return initialConfig;
|
||||
if (persist) return loadSortConfig(storageKey);
|
||||
return getDefaultSortConfig();
|
||||
});
|
||||
|
||||
// Persist changes
|
||||
useEffect(() => {
|
||||
if (persist) {
|
||||
saveSortConfig(sortConfig, storageKey);
|
||||
}
|
||||
onChange?.(sortConfig);
|
||||
}, [sortConfig, persist, storageKey, onChange]);
|
||||
|
||||
// Set sort field (keep direction)
|
||||
const setField = useCallback((field: SortField) => {
|
||||
setSortConfig((prev) => ({ ...prev, field }));
|
||||
}, []);
|
||||
|
||||
// Set sort direction (keep field)
|
||||
const setDirection = useCallback((direction: SortDirection) => {
|
||||
setSortConfig((prev) => ({ ...prev, direction }));
|
||||
}, []);
|
||||
|
||||
// Toggle sort direction
|
||||
const toggleSortDirection = useCallback(() => {
|
||||
setSortConfig((prev) => ({
|
||||
...prev,
|
||||
direction: toggleDirection(prev.direction),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// Set entire config
|
||||
const setConfig = useCallback((newConfig: SortConfig) => {
|
||||
setSortConfig(newConfig);
|
||||
}, []);
|
||||
|
||||
// Reset to default
|
||||
const reset = useCallback(() => {
|
||||
setSortConfig(getDefaultSortConfig());
|
||||
}, []);
|
||||
|
||||
// Check if using default config
|
||||
const isDefault = isDefaultSortConfig(sortConfig);
|
||||
|
||||
return {
|
||||
config: sortConfig,
|
||||
field: sortConfig.field,
|
||||
direction: sortConfig.direction,
|
||||
setField,
|
||||
setDirection,
|
||||
toggleDirection: toggleSortDirection,
|
||||
setConfig,
|
||||
reset,
|
||||
isDefault,
|
||||
};
|
||||
}
|
||||
253
src/hooks/moderation/usePagination.ts
Normal file
253
src/hooks/moderation/usePagination.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Pagination Hook
|
||||
*
|
||||
* Manages pagination state and actions for the moderation queue.
|
||||
*/
|
||||
|
||||
import { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
export interface PaginationConfig {
|
||||
/** Initial page number (1-indexed) */
|
||||
initialPage?: number;
|
||||
|
||||
/** Initial page size */
|
||||
initialPageSize?: number;
|
||||
|
||||
/** Whether to persist pagination state */
|
||||
persist?: boolean;
|
||||
|
||||
/** localStorage key for persistence */
|
||||
storageKey?: string;
|
||||
|
||||
/** Callback when page changes */
|
||||
onPageChange?: (page: number) => void;
|
||||
|
||||
/** Callback when page size changes */
|
||||
onPageSizeChange?: (pageSize: number) => void;
|
||||
}
|
||||
|
||||
export interface PaginationState {
|
||||
/** Current page (1-indexed) */
|
||||
currentPage: number;
|
||||
|
||||
/** Items per page */
|
||||
pageSize: number;
|
||||
|
||||
/** Total number of items */
|
||||
totalCount: number;
|
||||
|
||||
/** Total number of pages */
|
||||
totalPages: number;
|
||||
|
||||
/** Start index for current page (0-indexed) */
|
||||
startIndex: number;
|
||||
|
||||
/** End index for current page (0-indexed) */
|
||||
endIndex: number;
|
||||
|
||||
/** Whether there is a previous page */
|
||||
hasPrevPage: boolean;
|
||||
|
||||
/** Whether there is a next page */
|
||||
hasNextPage: boolean;
|
||||
|
||||
/** Set current page */
|
||||
setCurrentPage: (page: number) => void;
|
||||
|
||||
/** Set page size */
|
||||
setPageSize: (size: number) => void;
|
||||
|
||||
/** Set total count */
|
||||
setTotalCount: (count: number) => void;
|
||||
|
||||
/** Go to next page */
|
||||
nextPage: () => void;
|
||||
|
||||
/** Go to previous page */
|
||||
prevPage: () => void;
|
||||
|
||||
/** Go to first page */
|
||||
firstPage: () => void;
|
||||
|
||||
/** Go to last page */
|
||||
lastPage: () => void;
|
||||
|
||||
/** Reset pagination */
|
||||
reset: () => void;
|
||||
|
||||
/** Get page range for display */
|
||||
getPageRange: (maxPages?: number) => number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing pagination state
|
||||
*
|
||||
* @param config - Configuration options
|
||||
* @returns Pagination state and actions
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const pagination = usePagination({
|
||||
* initialPageSize: 25,
|
||||
* persist: true,
|
||||
* onPageChange: (page) => fetchData(page)
|
||||
* });
|
||||
*
|
||||
* // Set total count from API
|
||||
* pagination.setTotalCount(response.count);
|
||||
*
|
||||
* // Use in query
|
||||
* const { startIndex, endIndex } = pagination;
|
||||
* query.range(startIndex, endIndex);
|
||||
* ```
|
||||
*/
|
||||
export function usePagination(config: PaginationConfig = {}): PaginationState {
|
||||
const {
|
||||
initialPage = 1,
|
||||
initialPageSize = 25,
|
||||
persist = false,
|
||||
storageKey = 'pagination_state',
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
} = config;
|
||||
|
||||
// Load persisted state
|
||||
const loadPersistedState = useCallback(() => {
|
||||
if (!persist) return null;
|
||||
|
||||
try {
|
||||
const saved = localStorage.getItem(storageKey);
|
||||
if (saved) {
|
||||
return JSON.parse(saved);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load pagination state:', error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [persist, storageKey]);
|
||||
|
||||
const persisted = loadPersistedState();
|
||||
|
||||
// State
|
||||
const [currentPage, setCurrentPageState] = useState<number>(
|
||||
persisted?.currentPage || initialPage
|
||||
);
|
||||
const [pageSize, setPageSizeState] = useState<number>(
|
||||
persisted?.pageSize || initialPageSize
|
||||
);
|
||||
const [totalCount, setTotalCount] = useState<number>(0);
|
||||
|
||||
// Computed values
|
||||
const totalPages = useMemo(() => Math.ceil(totalCount / pageSize), [totalCount, pageSize]);
|
||||
const startIndex = useMemo(() => (currentPage - 1) * pageSize, [currentPage, pageSize]);
|
||||
const endIndex = useMemo(() => startIndex + pageSize - 1, [startIndex, pageSize]);
|
||||
const hasPrevPage = currentPage > 1;
|
||||
const hasNextPage = currentPage < totalPages;
|
||||
|
||||
// Persist state
|
||||
useEffect(() => {
|
||||
if (persist) {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
storageKey,
|
||||
JSON.stringify({
|
||||
currentPage,
|
||||
pageSize,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to persist pagination state:', error);
|
||||
}
|
||||
}
|
||||
}, [currentPage, pageSize, persist, storageKey]);
|
||||
|
||||
// Set current page with bounds checking
|
||||
const setCurrentPage = useCallback(
|
||||
(page: number) => {
|
||||
const boundedPage = Math.max(1, Math.min(page, totalPages || 1));
|
||||
setCurrentPageState(boundedPage);
|
||||
onPageChange?.(boundedPage);
|
||||
},
|
||||
[totalPages, onPageChange]
|
||||
);
|
||||
|
||||
// Set page size and reset to first page
|
||||
const setPageSize = useCallback(
|
||||
(size: number) => {
|
||||
setPageSizeState(size);
|
||||
setCurrentPageState(1);
|
||||
onPageSizeChange?.(size);
|
||||
},
|
||||
[onPageSizeChange]
|
||||
);
|
||||
|
||||
// Navigation actions
|
||||
const nextPage = useCallback(() => {
|
||||
if (hasNextPage) {
|
||||
setCurrentPage(currentPage + 1);
|
||||
}
|
||||
}, [currentPage, hasNextPage, setCurrentPage]);
|
||||
|
||||
const prevPage = useCallback(() => {
|
||||
if (hasPrevPage) {
|
||||
setCurrentPage(currentPage - 1);
|
||||
}
|
||||
}, [currentPage, hasPrevPage, setCurrentPage]);
|
||||
|
||||
const firstPage = useCallback(() => {
|
||||
setCurrentPage(1);
|
||||
}, [setCurrentPage]);
|
||||
|
||||
const lastPage = useCallback(() => {
|
||||
setCurrentPage(totalPages);
|
||||
}, [totalPages, setCurrentPage]);
|
||||
|
||||
// Reset pagination
|
||||
const reset = useCallback(() => {
|
||||
setCurrentPageState(initialPage);
|
||||
setPageSizeState(initialPageSize);
|
||||
setTotalCount(0);
|
||||
}, [initialPage, initialPageSize]);
|
||||
|
||||
// Get page range for pagination controls
|
||||
const getPageRange = useCallback(
|
||||
(maxPages: number = 5): number[] => {
|
||||
if (totalPages <= maxPages) {
|
||||
return Array.from({ length: totalPages }, (_, i) => i + 1);
|
||||
}
|
||||
|
||||
const half = Math.floor(maxPages / 2);
|
||||
let start = Math.max(1, currentPage - half);
|
||||
let end = Math.min(totalPages, start + maxPages - 1);
|
||||
|
||||
// Adjust start if we're near the end
|
||||
if (end - start < maxPages - 1) {
|
||||
start = Math.max(1, end - maxPages + 1);
|
||||
}
|
||||
|
||||
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
||||
},
|
||||
[currentPage, totalPages]
|
||||
);
|
||||
|
||||
return {
|
||||
currentPage,
|
||||
pageSize,
|
||||
totalCount,
|
||||
totalPages,
|
||||
startIndex,
|
||||
endIndex,
|
||||
hasPrevPage,
|
||||
hasNextPage,
|
||||
setCurrentPage,
|
||||
setPageSize,
|
||||
setTotalCount,
|
||||
nextPage,
|
||||
prevPage,
|
||||
firstPage,
|
||||
lastPage,
|
||||
reset,
|
||||
getPageRange,
|
||||
};
|
||||
}
|
||||
@@ -42,3 +42,16 @@ export type {
|
||||
ModerationConfig,
|
||||
DeleteSubmissionConfig,
|
||||
} from './actions';
|
||||
|
||||
// Sorting utilities
|
||||
export {
|
||||
sortModerationItems,
|
||||
getDefaultSortConfig,
|
||||
loadSortConfig,
|
||||
saveSortConfig,
|
||||
toggleSortDirection,
|
||||
getSortFieldLabel,
|
||||
isDefaultSortConfig,
|
||||
} from './sorting';
|
||||
|
||||
export type { SortConfig, SortField, SortDirection } from '@/types/moderation';
|
||||
|
||||
151
src/lib/moderation/sorting.ts
Normal file
151
src/lib/moderation/sorting.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Moderation Queue Sorting Utilities
|
||||
*
|
||||
* Provides sorting functions and utilities for moderation queue items.
|
||||
*/
|
||||
|
||||
import type { ModerationItem, SortField, SortDirection, SortConfig } from '@/types/moderation';
|
||||
|
||||
/**
|
||||
* Sort moderation items based on configuration
|
||||
*
|
||||
* @param items - Array of moderation items to sort
|
||||
* @param config - Sort configuration
|
||||
* @returns Sorted array of items
|
||||
*/
|
||||
export function sortModerationItems(
|
||||
items: ModerationItem[],
|
||||
config: SortConfig
|
||||
): ModerationItem[] {
|
||||
const { field, direction } = config;
|
||||
|
||||
return [...items].sort((a, b) => {
|
||||
let comparison = 0;
|
||||
|
||||
switch (field) {
|
||||
case 'created_at':
|
||||
comparison = new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
|
||||
break;
|
||||
|
||||
case 'username':
|
||||
const usernameA = a.user_profile?.username || a.user_profile?.display_name || '';
|
||||
const usernameB = b.user_profile?.username || b.user_profile?.display_name || '';
|
||||
comparison = usernameA.localeCompare(usernameB);
|
||||
break;
|
||||
|
||||
case 'submission_type':
|
||||
comparison = (a.submission_type || '').localeCompare(b.submission_type || '');
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
comparison = a.status.localeCompare(b.status);
|
||||
break;
|
||||
|
||||
case 'escalated':
|
||||
const escalatedA = a.escalated ? 1 : 0;
|
||||
const escalatedB = b.escalated ? 1 : 0;
|
||||
comparison = escalatedB - escalatedA; // Escalated items first
|
||||
break;
|
||||
|
||||
default:
|
||||
comparison = 0;
|
||||
}
|
||||
|
||||
return direction === 'asc' ? comparison : -comparison;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default sort configuration
|
||||
*
|
||||
* @returns Default sort config
|
||||
*/
|
||||
export function getDefaultSortConfig(): SortConfig {
|
||||
return {
|
||||
field: 'created_at',
|
||||
direction: 'asc',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load sort configuration from localStorage
|
||||
*
|
||||
* @param key - localStorage key
|
||||
* @returns Saved sort config or default
|
||||
*/
|
||||
export function loadSortConfig(key: string = 'moderationQueue_sortConfig'): SortConfig {
|
||||
try {
|
||||
const saved = localStorage.getItem(key);
|
||||
if (saved) {
|
||||
return JSON.parse(saved);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load sort config:', error);
|
||||
}
|
||||
|
||||
return getDefaultSortConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save sort configuration to localStorage
|
||||
*
|
||||
* @param config - Sort configuration to save
|
||||
* @param key - localStorage key
|
||||
*/
|
||||
export function saveSortConfig(
|
||||
config: SortConfig,
|
||||
key: string = 'moderationQueue_sortConfig'
|
||||
): void {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(config));
|
||||
} catch (error) {
|
||||
console.error('Failed to save sort config:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle sort direction
|
||||
*
|
||||
* @param currentDirection - Current sort direction
|
||||
* @returns Toggled direction
|
||||
*/
|
||||
export function toggleSortDirection(currentDirection: SortDirection): SortDirection {
|
||||
return currentDirection === 'asc' ? 'desc' : 'asc';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human-readable label for sort field
|
||||
*
|
||||
* @param field - Sort field
|
||||
* @returns Human-readable label
|
||||
*/
|
||||
export function getSortFieldLabel(field: SortField): string {
|
||||
switch (field) {
|
||||
case 'created_at':
|
||||
return 'Date Created';
|
||||
case 'username':
|
||||
return 'Submitter';
|
||||
case 'submission_type':
|
||||
return 'Type';
|
||||
case 'status':
|
||||
return 'Status';
|
||||
case 'escalated':
|
||||
return 'Escalated';
|
||||
default:
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if sort config is default
|
||||
*
|
||||
* @param config - Sort configuration to check
|
||||
* @returns True if config matches default
|
||||
*/
|
||||
export function isDefaultSortConfig(config: SortConfig): boolean {
|
||||
const defaultConfig = getDefaultSortConfig();
|
||||
return (
|
||||
config.field === defaultConfig.field &&
|
||||
config.direction === defaultConfig.direction
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user