feat: Implement client-side sorting for queues

This commit is contained in:
gpt-engineer-app[bot]
2025-10-10 17:06:39 +00:00
parent 74fbd116cb
commit 83ccc51f61
2 changed files with 263 additions and 8 deletions

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import { CheckCircle, XCircle, ExternalLink, Calendar, User, Flag } from 'lucide-react';
import { useState, useEffect, forwardRef, useImperativeHandle, useMemo, useCallback } from 'react';
import { CheckCircle, XCircle, ExternalLink, Calendar, User, Flag, ArrowUp, ArrowDown } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
@@ -52,6 +52,14 @@ const STATUS_COLORS = {
dismissed: 'outline',
} as const;
type ReportSortField = 'created_at' | 'reporter' | 'report_type' | 'entity_type';
type ReportSortDirection = 'asc' | 'desc';
interface ReportSortConfig {
field: ReportSortField;
direction: ReportSortDirection;
}
export interface ReportsQueueRef {
refresh: () => void;
}
@@ -71,6 +79,19 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
const [pageSize, setPageSize] = useState(25);
const [totalCount, setTotalCount] = useState(0);
const totalPages = Math.ceil(totalCount / pageSize);
// Sort state
const [sortConfig, setSortConfig] = useState<ReportSortConfig>(() => {
const saved = localStorage.getItem('reportsQueue_sortConfig');
if (saved) {
try {
return JSON.parse(saved);
} catch {
return { field: 'created_at', direction: 'asc' as ReportSortDirection };
}
}
return { field: 'created_at', direction: 'asc' as ReportSortDirection };
});
// Get admin settings for polling configuration
const {
@@ -86,6 +107,11 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
useImperativeHandle(ref, () => ({
refresh: () => fetchReports(false) // Manual refresh shows loading
}), []);
// Persist sort configuration
useEffect(() => {
localStorage.setItem('reportsQueue_sortConfig', JSON.stringify(sortConfig));
}, [sortConfig]);
const fetchReports = async (silent = false) => {
try {
@@ -256,6 +282,52 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
}
};
// Sort reports function
const sortReports = useCallback((reports: Report[], config: ReportSortConfig): Report[] => {
const sorted = [...reports];
sorted.sort((a, b) => {
let compareA: any;
let compareB: any;
switch (config.field) {
case 'created_at':
compareA = new Date(a.created_at).getTime();
compareB = new Date(b.created_at).getTime();
break;
case 'reporter':
compareA = (a.reporter_profile?.username || '').toLowerCase();
compareB = (b.reporter_profile?.username || '').toLowerCase();
break;
case 'report_type':
compareA = a.report_type;
compareB = b.report_type;
break;
case 'entity_type':
compareA = a.reported_entity_type;
compareB = b.reported_entity_type;
break;
default:
return 0;
}
let result = 0;
if (typeof compareA === 'string' && typeof compareB === 'string') {
result = compareA.localeCompare(compareB);
} else if (typeof compareA === 'number' && typeof compareB === 'number') {
result = compareA - compareB;
}
return config.direction === 'asc' ? result : -result;
});
return sorted;
}, []);
if (loading) {
return (
<div className="flex items-center justify-center p-8">
@@ -296,7 +368,61 @@ export const ReportsQueue = forwardRef<ReportsQueueRef>((props, ref) => {
</div>
)}
{reports.map((report) => (
{/* Sort Controls */}
<div className={`flex gap-4 bg-muted/50 rounded-lg ${isMobile ? 'p-3' : 'p-4'}`}>
<div className={`space-y-2 ${isMobile ? 'w-full' : 'flex-1 max-w-[200px]'}`}>
<Label className={`font-medium ${isMobile ? 'text-xs' : 'text-sm'}`}>Sort By</Label>
<div className="flex gap-2">
<Select
value={sortConfig.field}
onValueChange={(value) => setSortConfig(prev => ({ ...prev, field: value as ReportSortField }))}
>
<SelectTrigger className={isMobile ? "h-10 flex-1" : "flex-1"}>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="created_at">Date Reported</SelectItem>
<SelectItem value="reporter">Reporter</SelectItem>
<SelectItem value="report_type">Report Type</SelectItem>
<SelectItem value="entity_type">Entity Type</SelectItem>
</SelectContent>
</Select>
<Button
variant="outline"
size={isMobile ? "default" : "sm"}
onClick={() => setSortConfig(prev => ({
...prev,
direction: prev.direction === 'asc' ? 'desc' : 'asc'
}))}
className={isMobile ? "h-10" : ""}
title={sortConfig.direction === 'asc' ? 'Sort Descending' : 'Sort Ascending'}
>
{sortConfig.direction === 'asc' ? (
<ArrowUp className="w-4 h-4" />
) : (
<ArrowDown className="w-4 h-4" />
)}
</Button>
</div>
</div>
{sortConfig.field !== 'created_at' && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Badge variant="secondary" className="flex items-center gap-1">
{sortConfig.direction === 'asc' ? <ArrowUp className="w-3 h-3" /> : <ArrowDown className="w-3 h-3" />}
{sortConfig.field === 'reporter' ? 'Reporter' :
sortConfig.field === 'report_type' ? 'Type' :
sortConfig.field === 'entity_type' ? 'Entity' : sortConfig.field}
</Badge>
</div>
)}
</div>
{/* Apply sorting before rendering */}
{useMemo(() => {
const sortedReports = sortReports(reports, sortConfig);
return sortedReports.map((report) => (
<Card key={report.id} className="border-l-4 border-l-red-500">
<CardHeader className="pb-4">
<div className="flex items-center justify-between">