mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 15:51:13 -05:00
feat: Implement client-side sorting for queues
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user