mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 19:51:14 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
228
src-old/components/submission/SubmissionQueueIndicator.tsx
Normal file
228
src-old/components/submission/SubmissionQueueIndicator.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import { useState } from 'react';
|
||||
import { Clock, RefreshCw, Trash2, CheckCircle2, XCircle, ChevronDown } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
|
||||
export interface QueuedSubmission {
|
||||
id: string;
|
||||
type: string;
|
||||
entityName: string;
|
||||
timestamp: Date;
|
||||
status: 'pending' | 'retrying' | 'failed';
|
||||
retryCount?: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface SubmissionQueueIndicatorProps {
|
||||
queuedItems: QueuedSubmission[];
|
||||
lastSyncTime?: Date;
|
||||
onRetryItem?: (id: string) => Promise<void>;
|
||||
onRetryAll?: () => Promise<void>;
|
||||
onClearQueue?: () => Promise<void>;
|
||||
onRemoveItem?: (id: string) => void;
|
||||
}
|
||||
|
||||
export function SubmissionQueueIndicator({
|
||||
queuedItems,
|
||||
lastSyncTime,
|
||||
onRetryItem,
|
||||
onRetryAll,
|
||||
onClearQueue,
|
||||
onRemoveItem,
|
||||
}: SubmissionQueueIndicatorProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [retryingIds, setRetryingIds] = useState<Set<string>>(new Set());
|
||||
|
||||
const handleRetryItem = async (id: string) => {
|
||||
if (!onRetryItem) return;
|
||||
|
||||
setRetryingIds(prev => new Set(prev).add(id));
|
||||
try {
|
||||
await onRetryItem(id);
|
||||
} finally {
|
||||
setRetryingIds(prev => {
|
||||
const next = new Set(prev);
|
||||
next.delete(id);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusIcon = (status: QueuedSubmission['status']) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return <Clock className="h-3.5 w-3.5 text-muted-foreground" />;
|
||||
case 'retrying':
|
||||
return <RefreshCw className="h-3.5 w-3.5 text-primary animate-spin" />;
|
||||
case 'failed':
|
||||
return <XCircle className="h-3.5 w-3.5 text-destructive" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status: QueuedSubmission['status']) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return 'bg-secondary text-secondary-foreground';
|
||||
case 'retrying':
|
||||
return 'bg-primary/10 text-primary';
|
||||
case 'failed':
|
||||
return 'bg-destructive/10 text-destructive';
|
||||
}
|
||||
};
|
||||
|
||||
if (queuedItems.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="relative gap-2 h-9"
|
||||
>
|
||||
<Clock className="h-4 w-4" />
|
||||
<span className="text-sm font-medium">
|
||||
Queue
|
||||
</span>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="h-5 min-w-[20px] px-1.5 bg-primary text-primary-foreground"
|
||||
>
|
||||
{queuedItems.length}
|
||||
</Badge>
|
||||
<ChevronDown className={cn(
|
||||
"h-3.5 w-3.5 transition-transform",
|
||||
isOpen && "rotate-180"
|
||||
)} />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-96 p-0"
|
||||
align="end"
|
||||
sideOffset={8}
|
||||
>
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<div>
|
||||
<h3 className="font-semibold text-sm">Submission Queue</h3>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
{queuedItems.length} pending submission{queuedItems.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
{lastSyncTime && (
|
||||
<p className="text-xs text-muted-foreground mt-0.5 flex items-center gap-1">
|
||||
<CheckCircle2 className="h-3 w-3" />
|
||||
Last sync {formatDistanceToNow(lastSyncTime, { addSuffix: true })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-1.5">
|
||||
{onRetryAll && queuedItems.length > 0 && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onRetryAll}
|
||||
className="h-8"
|
||||
>
|
||||
<RefreshCw className="h-3.5 w-3.5 mr-1.5" />
|
||||
Retry All
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="max-h-[400px]">
|
||||
<div className="p-2 space-y-1">
|
||||
{queuedItems.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={cn(
|
||||
"group rounded-md p-3 border transition-colors hover:bg-accent/50",
|
||||
getStatusColor(item.status)
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
{getStatusIcon(item.status)}
|
||||
<span className="text-sm font-medium truncate">
|
||||
{item.entityName}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span className="capitalize">{item.type}</span>
|
||||
<span>•</span>
|
||||
<span>{formatDistanceToNow(item.timestamp, { addSuffix: true })}</span>
|
||||
{item.retryCount && item.retryCount > 0 && (
|
||||
<>
|
||||
<span>•</span>
|
||||
<span>{item.retryCount} {item.retryCount === 1 ? 'retry' : 'retries'}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{item.error && (
|
||||
<p className="text-xs text-destructive mt-1.5 truncate">
|
||||
{item.error}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
{onRetryItem && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleRetryItem(item.id)}
|
||||
disabled={retryingIds.has(item.id)}
|
||||
className="h-7 w-7 p-0"
|
||||
>
|
||||
<RefreshCw className={cn(
|
||||
"h-3.5 w-3.5",
|
||||
retryingIds.has(item.id) && "animate-spin"
|
||||
)} />
|
||||
<span className="sr-only">Retry</span>
|
||||
</Button>
|
||||
)}
|
||||
{onRemoveItem && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => onRemoveItem(item.id)}
|
||||
className="h-7 w-7 p-0 hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
<span className="sr-only">Remove</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
{onClearQueue && queuedItems.length > 0 && (
|
||||
<div className="p-3 border-t">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onClearQueue}
|
||||
className="w-full h-8 text-destructive hover:bg-destructive/10"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5 mr-1.5" />
|
||||
Clear Queue
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user