mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 16:11:12 -05:00
Enhance moderation queue sorting with visual indicators and logging
Add loading spinners, detailed sort query logging, result preview logging, disable sort controls during loading, and mobile-specific loading text changes. Also, fix type mismatch in QueueSortControls and correct refresh strategy logic. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ef7037e7-a631-48a2-94d1-9a4b52d7c35a Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7cdf4e95-3f41-4180-b8e3-8ef56d032c0e/ef7037e7-a631-48a2-94d1-9a4b52d7c35a/kq6AhNt
This commit is contained in:
4
.replit
4
.replit
@@ -41,3 +41,7 @@ externalPort = 3000
|
|||||||
[[ports]]
|
[[ports]]
|
||||||
localPort = 37143
|
localPort = 37143
|
||||||
externalPort = 3001
|
externalPort = 3001
|
||||||
|
|
||||||
|
[[ports]]
|
||||||
|
localPort = 41349
|
||||||
|
externalPort = 3002
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ ThrillWiki is a community-driven web application for discovering, reviewing, and
|
|||||||
|
|
||||||
## Recent Changes
|
## Recent Changes
|
||||||
**October 2025 - Critical Bug Fixes & Stability Improvements (Latest)**
|
**October 2025 - Critical Bug Fixes & Stability Improvements (Latest)**
|
||||||
|
- **Moderation Queue Sorting Enhancement**: Enhanced sorting controls with visual loading indicators and diagnostic logging:
|
||||||
|
- Added animated spinners to sort controls (label and direction button) during data fetch operations
|
||||||
|
- Added comprehensive logging to track multi-level sort queries (escalated DESC → user-selected sort → created_at tertiary)
|
||||||
|
- Added detailed result preview logging showing first 3 items with sort field values for debugging
|
||||||
|
- Disabled sort controls during loading to prevent duplicate requests
|
||||||
|
- Mobile-specific loading text changes from "Ascending/Descending" to "Loading..." for clarity
|
||||||
- **Moderation Queue Sorting Fix**: Resolved sorting controls not updating the UI by fixing type mismatch in QueueSortControls (Radix Select passes string, handler expected SortField) and correcting refresh strategy logic (user-initiated sort/filter changes now bypass "notify" freeze mode and always update display)
|
- **Moderation Queue Sorting Fix**: Resolved sorting controls not updating the UI by fixing type mismatch in QueueSortControls (Radix Select passes string, handler expected SortField) and correcting refresh strategy logic (user-initiated sort/filter changes now bypass "notify" freeze mode and always update display)
|
||||||
- **Auth Loading State Fix**: Resolved perpetual loading state issue in auth buttons by simplifying useAuth hook's loading state management, removing blocking conditional logic, adding explicit setLoading(false) calls in all code paths, and ensuring pending email state cleanup occurs before early returns
|
- **Auth Loading State Fix**: Resolved perpetual loading state issue in auth buttons by simplifying useAuth hook's loading state management, removing blocking conditional logic, adding explicit setLoading(false) calls in all code paths, and ensuring pending email state cleanup occurs before early returns
|
||||||
- **React Hooks Violation Fix**: Resolved critical hooks ordering issue in useSearch that caused app crashes during hot module reload (HMR) by using stable useMemo with entire options object as dependency
|
- **React Hooks Violation Fix**: Resolved critical hooks ordering issue in useSearch that caused app crashes during hot module reload (HMR) by using stable useMemo with entire options object as dependency
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
activeStatusFilter={queueManager.filters.statusFilter}
|
activeStatusFilter={queueManager.filters.statusFilter}
|
||||||
sortConfig={queueManager.sort.config}
|
sortConfig={queueManager.sort.config}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
|
isLoading={queueManager.loadingState === 'loading'}
|
||||||
onEntityFilterChange={queueManager.filters.setEntityFilter}
|
onEntityFilterChange={queueManager.filters.setEntityFilter}
|
||||||
onStatusFilterChange={queueManager.filters.setStatusFilter}
|
onStatusFilterChange={queueManager.filters.setStatusFilter}
|
||||||
onSortChange={queueManager.sort.setConfig}
|
onSortChange={queueManager.sort.setConfig}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface QueueFiltersProps {
|
|||||||
activeStatusFilter: StatusFilter;
|
activeStatusFilter: StatusFilter;
|
||||||
sortConfig: SortConfig;
|
sortConfig: SortConfig;
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
onEntityFilterChange: (filter: EntityFilter) => void;
|
onEntityFilterChange: (filter: EntityFilter) => void;
|
||||||
onStatusFilterChange: (filter: StatusFilter) => void;
|
onStatusFilterChange: (filter: StatusFilter) => void;
|
||||||
onSortChange: (config: SortConfig) => void;
|
onSortChange: (config: SortConfig) => void;
|
||||||
@@ -31,6 +32,7 @@ export const QueueFilters = ({
|
|||||||
activeStatusFilter,
|
activeStatusFilter,
|
||||||
sortConfig,
|
sortConfig,
|
||||||
isMobile,
|
isMobile,
|
||||||
|
isLoading = false,
|
||||||
onEntityFilterChange,
|
onEntityFilterChange,
|
||||||
onStatusFilterChange,
|
onStatusFilterChange,
|
||||||
onSortChange,
|
onSortChange,
|
||||||
@@ -118,6 +120,7 @@ export const QueueFilters = ({
|
|||||||
sortConfig={sortConfig}
|
sortConfig={sortConfig}
|
||||||
onSortChange={onSortChange}
|
onSortChange={onSortChange}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ArrowUp, ArrowDown } from 'lucide-react';
|
import { ArrowUp, ArrowDown, Loader2 } from 'lucide-react';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -8,6 +8,7 @@ interface QueueSortControlsProps {
|
|||||||
sortConfig: SortConfig;
|
sortConfig: SortConfig;
|
||||||
onSortChange: (config: SortConfig) => void;
|
onSortChange: (config: SortConfig) => void;
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SORT_FIELD_LABELS: Record<SortField, string> = {
|
const SORT_FIELD_LABELS: Record<SortField, string> = {
|
||||||
@@ -19,7 +20,8 @@ const SORT_FIELD_LABELS: Record<SortField, string> = {
|
|||||||
export const QueueSortControls = ({
|
export const QueueSortControls = ({
|
||||||
sortConfig,
|
sortConfig,
|
||||||
onSortChange,
|
onSortChange,
|
||||||
isMobile
|
isMobile,
|
||||||
|
isLoading = false
|
||||||
}: QueueSortControlsProps) => {
|
}: QueueSortControlsProps) => {
|
||||||
const handleFieldChange = (value: string) => {
|
const handleFieldChange = (value: string) => {
|
||||||
const validFields: SortField[] = ['created_at', 'submission_type', 'status'];
|
const validFields: SortField[] = ['created_at', 'submission_type', 'status'];
|
||||||
@@ -48,14 +50,16 @@ export const QueueSortControls = ({
|
|||||||
return (
|
return (
|
||||||
<div className={`flex gap-2 ${isMobile ? 'flex-col' : 'items-end'}`}>
|
<div className={`flex gap-2 ${isMobile ? 'flex-col' : 'items-end'}`}>
|
||||||
<div className={`space-y-2 ${isMobile ? 'w-full' : 'min-w-[160px]'}`}>
|
<div className={`space-y-2 ${isMobile ? 'w-full' : 'min-w-[160px]'}`}>
|
||||||
<Label className={`font-medium ${isMobile ? 'text-xs' : 'text-sm'}`}>
|
<Label className={`font-medium ${isMobile ? 'text-xs' : 'text-sm'} flex items-center gap-2`}>
|
||||||
Sort By
|
Sort By
|
||||||
|
{isLoading && <Loader2 className="w-3 h-3 animate-spin text-primary" />}
|
||||||
</Label>
|
</Label>
|
||||||
<Select
|
<Select
|
||||||
value={sortConfig.field}
|
value={sortConfig.field}
|
||||||
onValueChange={handleFieldChange}
|
onValueChange={handleFieldChange}
|
||||||
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
<SelectTrigger className={isMobile ? "h-10" : ""}>
|
<SelectTrigger className={isMobile ? "h-10" : ""} disabled={isLoading}>
|
||||||
<SelectValue>
|
<SelectValue>
|
||||||
{SORT_FIELD_LABELS[sortConfig.field]}
|
{SORT_FIELD_LABELS[sortConfig.field]}
|
||||||
</SelectValue>
|
</SelectValue>
|
||||||
@@ -75,12 +79,19 @@ export const QueueSortControls = ({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size={isMobile ? "default" : "icon"}
|
size={isMobile ? "default" : "icon"}
|
||||||
onClick={handleDirectionToggle}
|
onClick={handleDirectionToggle}
|
||||||
|
disabled={isLoading}
|
||||||
className={`flex items-center gap-2 ${isMobile ? 'w-full h-10' : 'h-10 w-10'}`}
|
className={`flex items-center gap-2 ${isMobile ? 'w-full h-10' : 'h-10 w-10'}`}
|
||||||
title={sortConfig.direction === 'asc' ? 'Ascending' : 'Descending'}
|
title={sortConfig.direction === 'asc' ? 'Ascending' : 'Descending'}
|
||||||
>
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
) : (
|
||||||
<DirectionIcon className="w-4 h-4" />
|
<DirectionIcon className="w-4 h-4" />
|
||||||
|
)}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<span className="capitalize">{sortConfig.direction}ending</span>
|
<span className="capitalize">
|
||||||
|
{isLoading ? 'Loading...' : `${sortConfig.direction}ending`}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -224,6 +224,12 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
|||||||
);
|
);
|
||||||
|
|
||||||
// CRITICAL: Multi-level ordering
|
// CRITICAL: Multi-level ordering
|
||||||
|
console.log('📊 [SORT QUERY] Applying multi-level sort:', {
|
||||||
|
level1: 'escalated DESC',
|
||||||
|
level2: `${sort.debouncedConfig.field} ${sort.debouncedConfig.direction.toUpperCase()}`,
|
||||||
|
level3: sort.debouncedConfig.field !== 'created_at' ? 'created_at ASC' : 'none'
|
||||||
|
});
|
||||||
|
|
||||||
// Level 1: Always sort by escalated first (descending)
|
// Level 1: Always sort by escalated first (descending)
|
||||||
submissionsQuery = submissionsQuery.order('escalated', { ascending: false });
|
submissionsQuery = submissionsQuery.order('escalated', { ascending: false });
|
||||||
|
|
||||||
@@ -291,6 +297,17 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
|||||||
|
|
||||||
if (submissionsError) throw submissionsError;
|
if (submissionsError) throw submissionsError;
|
||||||
|
|
||||||
|
// Log the actual data returned to verify sort order
|
||||||
|
if (submissions && submissions.length > 0) {
|
||||||
|
const sortField = sort.debouncedConfig.field;
|
||||||
|
const preview = submissions.slice(0, 3).map(s => ({
|
||||||
|
id: s.id.substring(0, 8),
|
||||||
|
[sortField]: s[sortField],
|
||||||
|
escalated: s.escalated
|
||||||
|
}));
|
||||||
|
console.log(`📋 [SORT RESULT] First 3 items by ${sortField}:`, preview);
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch related profiles and entities
|
// Fetch related profiles and entities
|
||||||
const userIds = [
|
const userIds = [
|
||||||
...new Set([
|
...new Set([
|
||||||
|
|||||||
Reference in New Issue
Block a user